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) {