From b9d8b9373c4ce28d11472f0cfcc58cea0940de35 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 13 Dec 2022 22:21:42 -0500 Subject: [PATCH] Show fallback thumbnail if there is no image Some thumbnails are just as good as scaling down the real image, but some (especially blurhash) are not. So if nothing that's "good enough" to go in the main cache is present, try the fallback. If there is a thumbnail we can render right now (as of this commit, just blurhash) then render that alongside the download button. Tapping the image starts the download just like the button. --- build.gradle | 1 + .../persistance/FileBackend.java | 38 +++++++++++++ .../siacs/conversations/ui/XmppActivity.java | 10 +++- .../ui/adapter/MessageAdapter.java | 56 +++++++++++++++---- 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 79c4737acb03a5bfed8adcf642c1de5a634333c9..5a7b41a48febd0617335a793610d608ca4693400 100644 --- a/build.gradle +++ b/build.gradle @@ -100,6 +100,7 @@ dependencies { implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'com.github.singpolyma:android-identicons:master-SNAPSHOT' implementation 'org.snikket:webrtc-android:107.0.0' + implementation 'com.github.woltapp:blurhash:master' // INSERT } diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 067bd3ed6e1d6184da9775519486689ba34e0a94..7dd89d718bfe58c37760affb3c53678df12d3aa5 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -41,6 +41,8 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; +import com.wolt.blurhashkt.BlurHashDecoder; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -78,6 +80,7 @@ import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.FileWriterException; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.xmpp.pep.Avatar; +import eu.siacs.conversations.xml.Element; public class FileBackend { @@ -1000,6 +1003,41 @@ public class FileBackend { } } + public BitmapDrawable getFallbackThumbnail(final Message message, int size) { + List thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null; + if (thumbs != null && !thumbs.isEmpty()) { + for (Element thumb : thumbs) { + Uri uri = Uri.parse(thumb.getAttribute("uri")); + if (uri.getScheme().equals("data")) { + String[] parts = uri.getSchemeSpecificPart().split(",", 2); + if (parts[0].equals("image/blurhash")) { + final LruCache cache = mXmppConnectionService.getDrawableCache(); + BitmapDrawable cached = (BitmapDrawable) cache.get(parts[1]); + if (cached != null) return cached; + + int width = message.getFileParams().width; + if (width < 1 && thumb.getAttribute("width") != null) width = Integer.parseInt(thumb.getAttribute("width")); + if (width < 1) width = 1920; + + int height = message.getFileParams().height; + if (height < 1 && thumb.getAttribute("height") != null) height = Integer.parseInt(thumb.getAttribute("height")); + if (height < 1) height = 1080; + Rect r = rectForSize(width, height, size); + + Bitmap blurhash = BlurHashDecoder.INSTANCE.decode(parts[1], r.width(), r.height(), 1.0f, false); + if (blurhash != null) { + cached = new BitmapDrawable(blurhash); + cache.put(parts[1], cached); + return cached; + } + } + } + } + } + + return null; + } + public Drawable getThumbnail(Message message, Resources res, int size, boolean cacheOnly) throws IOException { return getThumbnail(getFile(message), res, size, cacheOnly); } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index d828101eee0ca317edf97b14bbb0794d1c55b3c4..4490a41757643af940b6a738a27b7c63df600b77 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -916,8 +916,9 @@ public abstract class XmppActivity extends ActionBarActivity { imageView.setBackgroundColor(0xff333333); imageView.setImageDrawable(null); final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final BitmapDrawable fallbackThumb = xmppConnectionService.getFileBackend().getFallbackThumbnail(message, (int) (metrics.density * 288)); final AsyncDrawable asyncDrawable = new AsyncDrawable( - getResources(), null, task); + getResources(), fallbackThumb != null ? fallbackThumb.getBitmap() : null, task); imageView.setImageDrawable(asyncDrawable); try { task.execute(message); @@ -995,7 +996,12 @@ public abstract class XmppActivity extends ActionBarActivity { if (!isCancelled()) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { - imageView.setImageDrawable(drawable); + Drawable old = imageView.getDrawable(); + if (drawable == null && old instanceof AsyncDrawable) { + imageView.setImageDrawable(new BitmapDrawable(((AsyncDrawable) old).getBitmap())); + } else { + imageView.setImageDrawable(drawable); + } imageView.setBackgroundColor(drawable == null ? 0xff333333 : 0x00000000); if (Build.VERSION.SDK_INT >= 28 && drawable instanceof AnimatedImageDrawable) { ((AnimatedImageDrawable) drawable).start(); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 1e44e08373324f97beea339a7e92eb8fd1d048b8..a92591937ecba73b6335fc9c0087f6b13171ae7c 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -586,6 +586,34 @@ public class MessageAdapter extends ArrayAdapter { private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text, final boolean darkBackground, final int type) { displayTextMessage(viewHolder, message, darkBackground, type); viewHolder.image.setVisibility(View.GONE); + List thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null; + if (thumbs != null && !thumbs.isEmpty()) { + for (Element thumb : thumbs) { + Uri uri = Uri.parse(thumb.getAttribute("uri")); + if (uri.getScheme().equals("data")) { + String[] parts = uri.getSchemeSpecificPart().split(",", 2); + parts = parts[0].split(";"); + if (!parts[0].equals("image/blurhash")) continue; + } else { + continue; + } + + int width = message.getFileParams().width; + if (width < 1 && thumb.getAttribute("width") != null) width = Integer.parseInt(thumb.getAttribute("width")); + if (width < 1) width = 1920; + + int height = message.getFileParams().height; + if (height < 1 && thumb.getAttribute("height") != null) height = Integer.parseInt(thumb.getAttribute("height")); + if (height < 1) height = 1080; + + viewHolder.image.setVisibility(View.VISIBLE); + imagePreviewLayout(width, height, viewHolder.image); + activity.loadBitmap(message, viewHolder.image); + viewHolder.image.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message)); + + break; + } + } viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(text); @@ -626,27 +654,31 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); final FileParams params = message.getFileParams(); + imagePreviewLayout(params.width, params.height, viewHolder.image); + activity.loadBitmap(message, viewHolder.image); + viewHolder.image.setOnClickListener(v -> openDownloadable(message)); + } + + private void imagePreviewLayout(int w, int h, ImageView image) { final float target = activity.getResources().getDimension(R.dimen.image_preview_width); final int scaledW; final int scaledH; - if (Math.max(params.height, params.width) * metrics.density <= target) { - scaledW = (int) (params.width * metrics.density); - scaledH = (int) (params.height * metrics.density); - } else if (Math.max(params.height, params.width) <= target) { - scaledW = params.width; - scaledH = params.height; - } else if (params.width <= params.height) { - scaledW = (int) (params.width / ((double) params.height / target)); + if (Math.max(h, w) * metrics.density <= target) { + scaledW = (int) (w * metrics.density); + scaledH = (int) (h * metrics.density); + } else if (Math.max(h, w) <= target) { + scaledW = w; + scaledH = h; + } else if (w <= h) { + scaledW = (int) (w / ((double) h / target)); scaledH = (int) target; } else { scaledW = (int) target; - scaledH = (int) (params.height / ((double) params.width / target)); + scaledH = (int) (h / ((double) w / target)); } final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); - viewHolder.image.setLayoutParams(layoutParams); - activity.loadBitmap(message, viewHolder.image); - viewHolder.image.setOnClickListener(v -> openDownloadable(message)); + image.setLayoutParams(layoutParams); } private void toggleWhisperInfo(ViewHolder viewHolder, final Message message, final boolean darkBackground) {