Detailed changes
@@ -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
}
@@ -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<Element> 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<String, Drawable> 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);
}
@@ -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();
@@ -586,6 +586,34 @@ public class MessageAdapter extends ArrayAdapter<Message> {
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<Element> 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<Message> {
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) {