mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
Speed up thumbnail transition.
This commit is contained in:
committed by
Greyson Parrelli
parent
d9c31a6cd6
commit
d7c3112602
@@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
@@ -29,9 +30,11 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
private ViewGroup albumCellContainer;
|
private ViewGroup albumCellContainer;
|
||||||
private Stub<TransferControlView> transferControls;
|
private Stub<TransferControlView> transferControls;
|
||||||
|
private Bitmap bitmap;
|
||||||
|
|
||||||
private final SlideClickListener defaultThumbnailClickListener = (v, slide) -> {
|
private final SlideClickListener defaultThumbnailClickListener = (v, slide) -> {
|
||||||
if (thumbnailClickListener != null) {
|
if (thumbnailClickListener != null) {
|
||||||
|
bitmap = ((ThumbnailView) v).getBitmap();
|
||||||
thumbnailClickListener.onClick(v, slide);
|
thumbnailClickListener.onClick(v, slide);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -84,6 +87,10 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
showSlides(glideRequests, slides);
|
showSlides(glideRequests, slides);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Bitmap getBitmap() {
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCellBackgroundColor(@ColorInt int color) {
|
public void setCellBackgroundColor(@ColorInt int color) {
|
||||||
ViewGroup cellRoot = findViewById(R.id.album_thumbnail_root);
|
ViewGroup cellRoot = findViewById(R.id.album_thumbnail_root);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -58,10 +59,10 @@ public class BorderlessImageView extends FrameLayout {
|
|||||||
boolean showControls = slide.asAttachment().getUri() == null;
|
boolean showControls = slide.asAttachment().getUri() == null;
|
||||||
|
|
||||||
if (slide.hasSticker()) {
|
if (slide.hasSticker()) {
|
||||||
image.setFit(new CenterInside());
|
image.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||||
image.setImageResource(glideRequests, slide, showControls, false);
|
image.setImageResource(glideRequests, slide, showControls, false);
|
||||||
} else {
|
} else {
|
||||||
image.setFit(new CenterCrop());
|
image.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
image.setImageResource(glideRequests, slide, showControls, false, slide.asAttachment().getWidth(), slide.asAttachment().getHeight());
|
image.setImageResource(glideRequests, slide, showControls, false, slide.asAttachment().getWidth(), slide.asAttachment().getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.components
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
@@ -130,6 +131,14 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||||||
state.applyState(thumbnail, album)
|
state.applyState(thumbnail, album)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBitmap(): Bitmap? {
|
||||||
|
return if (thumbnail.resolved()) {
|
||||||
|
thumbnail.get().bitmap
|
||||||
|
} else {
|
||||||
|
album.get().bitmap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun hideThumbnailView() {
|
fun hideThumbnailView() {
|
||||||
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 0f))
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 0f))
|
||||||
state.thumbnailViewState.applyState(thumbnail)
|
state.thumbnailViewState.applyState(thumbnail)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import org.signal.core.util.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
import org.thoughtcrime.securesms.database.MediaTable;
|
import org.thoughtcrime.securesms.database.MediaTable;
|
||||||
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
@@ -97,6 +98,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
imageView.setOnClickListener(v -> {
|
imageView.setOnClickListener(v -> {
|
||||||
|
MediaPreviewCache.INSTANCE.setBitmap(imageView.getBitmap());
|
||||||
if (clickedListener != null) clickedListener.onItemClicked(imageView, mediaRecord);
|
if (clickedListener != null) clickedListener.onItemClicked(imageView, mediaRecord);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.components;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -23,12 +25,10 @@ import androidx.appcompat.widget.AppCompatImageView;
|
|||||||
import com.bumptech.glide.RequestBuilder;
|
import com.bumptech.glide.RequestBuilder;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
|
||||||
import com.bumptech.glide.request.Request;
|
import com.bumptech.glide.request.Request;
|
||||||
import com.bumptech.glide.request.RequestListener;
|
import com.bumptech.glide.request.RequestListener;
|
||||||
import com.bumptech.glide.request.RequestOptions;
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
@@ -67,6 +67,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
private static final int MIN_HEIGHT = 2;
|
private static final int MIN_HEIGHT = 2;
|
||||||
private static final int MAX_HEIGHT = 3;
|
private static final int MAX_HEIGHT = 3;
|
||||||
|
|
||||||
|
private Bitmap imageBitmap;
|
||||||
private final ImageView image;
|
private final ImageView image;
|
||||||
private final ImageView blurhash;
|
private final ImageView blurhash;
|
||||||
private final View playOverlay;
|
private final View playOverlay;
|
||||||
@@ -79,13 +80,13 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
private final int[] bounds = new int[4];
|
private final int[] bounds = new int[4];
|
||||||
private final int[] measureDimens = new int[2];
|
private final int[] measureDimens = new int[2];
|
||||||
|
|
||||||
|
private final CornerMask cornerMask;
|
||||||
|
|
||||||
private Optional<TransferControlView> transferControls = Optional.empty();
|
private Optional<TransferControlView> transferControls = Optional.empty();
|
||||||
private SlideClickListener thumbnailClickListener = null;
|
private SlideClickListener thumbnailClickListener = null;
|
||||||
private SlidesClickedListener downloadClickListener = null;
|
private SlidesClickedListener downloadClickListener = null;
|
||||||
private Slide slide = null;
|
private Slide slide = null;
|
||||||
private BitmapTransformation fit = new CenterCrop();
|
|
||||||
|
|
||||||
private int radius;
|
|
||||||
|
|
||||||
public ThumbnailView(Context context) {
|
public ThumbnailView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -105,6 +106,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
this.playOverlay = findViewById(R.id.play_overlay);
|
this.playOverlay = findViewById(R.id.play_overlay);
|
||||||
this.captionIcon = findViewById(R.id.thumbnail_caption_icon);
|
this.captionIcon = findViewById(R.id.thumbnail_caption_icon);
|
||||||
this.errorImage = findViewById(R.id.thumbnail_error);
|
this.errorImage = findViewById(R.id.thumbnail_error);
|
||||||
|
this.cornerMask = new CornerMask(this);
|
||||||
|
|
||||||
super.setOnClickListener(new ThumbnailClickDispatcher());
|
super.setOnClickListener(new ThumbnailClickDispatcher());
|
||||||
|
|
||||||
@@ -114,8 +116,9 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
||||||
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
||||||
radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius));
|
|
||||||
fit = typedArray.getInt(R.styleable.ThumbnailView_thumbnail_fit, 0) == 1 ? new FitCenter() : new CenterCrop();
|
float radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius));
|
||||||
|
cornerMask.setRadius((int) radius);
|
||||||
|
|
||||||
int transparentOverlayColor = typedArray.getColor(R.styleable.ThumbnailView_transparent_overlay_color, -1);
|
int transparentOverlayColor = typedArray.getColor(R.styleable.ThumbnailView_transparent_overlay_color, -1);
|
||||||
if (transparentOverlayColor > 0) {
|
if (transparentOverlayColor > 0) {
|
||||||
@@ -126,7 +129,8 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
} else {
|
} else {
|
||||||
radius = getResources().getDimensionPixelSize(R.dimen.message_corner_collapse_radius);
|
float radius = getResources().getDimensionPixelSize(R.dimen.message_corner_collapse_radius);
|
||||||
|
cornerMask.setRadius((int) radius);
|
||||||
image.setColorFilter(null);
|
image.setColorFilter(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +170,13 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
captionIcon.setScaleY(captionIconScale);
|
captionIcon.setScaleY(captionIconScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
|
super.dispatchDraw(canvas);
|
||||||
|
|
||||||
|
cornerMask.mask(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMinimumThumbnailWidth(@Px int width) {
|
public void setMinimumThumbnailWidth(@Px int width) {
|
||||||
bounds[MIN_WIDTH] = width;
|
bounds[MIN_WIDTH] = width;
|
||||||
invalidate();
|
invalidate();
|
||||||
@@ -274,6 +285,10 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
if (transferControls.isPresent()) transferControls.get().setClickable(clickable);
|
if (transferControls.isPresent()) transferControls.get().setClickable(clickable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Bitmap getBitmap() {
|
||||||
|
return imageBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
private TransferControlView getTransferControls() {
|
private TransferControlView getTransferControls() {
|
||||||
if (!transferControls.isPresent()) {
|
if (!transferControls.isPresent()) {
|
||||||
transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub));
|
transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub));
|
||||||
@@ -400,7 +415,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
thumbnailFuture.addListener(new BlurhashClearListener(glideRequests, blurhash));
|
thumbnailFuture.addListener(new BlurhashClearListener(glideRequests, blurhash));
|
||||||
}
|
}
|
||||||
|
|
||||||
buildThumbnailGlideRequest(glideRequests, slide).into(new GlideDrawableListeningTarget(image, result));
|
buildThumbnailGlideRequest(glideRequests, slide).into(new BitmapCaptor(image, result));
|
||||||
|
|
||||||
resultHandled = true;
|
resultHandled = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -440,13 +455,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
request = request.override(width, height);
|
request = request.override(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radius > 0) {
|
GlideDrawableListeningTarget target = new BitmapCaptor(image, future);
|
||||||
request = request.transforms(new CenterCrop(), new RoundedCorners(radius));
|
|
||||||
} else {
|
|
||||||
request = request.transforms(new CenterCrop());
|
|
||||||
}
|
|
||||||
|
|
||||||
GlideDrawableListeningTarget target = new GlideDrawableListeningTarget(image, future);
|
|
||||||
Request previousRequest = target.getRequest();
|
Request previousRequest = target.getRequest();
|
||||||
boolean previousRequestRunning = previousRequest != null && previousRequest.isRunning();
|
boolean previousRequestRunning = previousRequest != null && previousRequest.isRunning();
|
||||||
request.into(target);
|
request.into(target);
|
||||||
@@ -476,13 +485,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
request = request.override(width, height);
|
request = request.override(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radius > 0) {
|
request.into(new BitmapCaptor(image, future));
|
||||||
request = request.transforms(new CenterCrop(), new RoundedCorners(radius));
|
|
||||||
} else {
|
|
||||||
request = request.transforms(new CenterCrop());
|
|
||||||
}
|
|
||||||
|
|
||||||
request.into(new GlideDrawableListeningTarget(image, future));
|
|
||||||
blurhash.setImageDrawable(null);
|
blurhash.setImageDrawable(null);
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
@@ -518,23 +521,27 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
getTransferControls().showProgressSpinner();
|
getTransferControls().showProgressSpinner();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFit(@NonNull BitmapTransformation fit) {
|
public void setScaleType(@NonNull ImageView.ScaleType scaleType) {
|
||||||
this.fit = fit;
|
image.setScaleType(scaleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setRadius(int radius) {
|
protected void setRadius(int radius) {
|
||||||
this.radius = radius;
|
cornerMask.setRadius(radius);
|
||||||
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||||
GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getUri()))
|
GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getUri()))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.transition(withCrossFade()), fit);
|
.transition(withCrossFade()));
|
||||||
|
|
||||||
boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23;
|
boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23;
|
||||||
|
|
||||||
if (slide.isInProgress() || doNotShowMissingThumbnailImage) return request;
|
if (slide.isInProgress() || doNotShowMissingThumbnailImage) {
|
||||||
else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
return request;
|
||||||
|
} else {
|
||||||
|
return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||||
@@ -547,10 +554,10 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
bitmap = bitmap.load(slide.getPlaceholderRes(getContext().getTheme()));
|
bitmap = bitmap.load(slide.getPlaceholderRes(getContext().getTheme()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return applySizing(bitmap.diskCacheStrategy(DiskCacheStrategy.NONE), new CenterCrop());
|
return applySizing(bitmap.diskCacheStrategy(DiskCacheStrategy.NONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapTransformation fitting) {
|
private GlideRequest applySizing(@NonNull GlideRequest request) {
|
||||||
int[] size = new int[2];
|
int[] size = new int[2];
|
||||||
fillTargetDimensions(size, dimens, bounds);
|
fillTargetDimensions(size, dimens, bounds);
|
||||||
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
||||||
@@ -558,13 +565,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
size[HEIGHT] = getDefaultHeight();
|
size[HEIGHT] = getDefaultHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
request = request.override(size[WIDTH], size[HEIGHT]);
|
return request.override(size[WIDTH], size[HEIGHT]);
|
||||||
|
|
||||||
if (radius > 0) {
|
|
||||||
return request.transforms(fitting, new RoundedCorners(radius));
|
|
||||||
} else {
|
|
||||||
return request.transforms(fitting);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getDefaultWidth() {
|
private int getDefaultWidth() {
|
||||||
@@ -585,6 +586,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
|
|
||||||
public interface ThumbnailRequestListener extends RequestListener<Drawable> {
|
public interface ThumbnailRequestListener extends RequestListener<Drawable> {
|
||||||
void onLoadCanceled();
|
void onLoadCanceled();
|
||||||
|
|
||||||
void onLoadScheduled();
|
void onLoadScheduled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,4 +641,22 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
blurhash.setImageDrawable(null);
|
blurhash.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class BitmapCaptor extends GlideDrawableListeningTarget {
|
||||||
|
|
||||||
|
public BitmapCaptor(@NonNull ImageView view, @NonNull SettableFuture<Boolean> loaded) {
|
||||||
|
super(view, loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
||||||
|
imageBitmap = ((BitmapDrawable) resource).getBitmap();
|
||||||
|
super.onResourceReady(resource, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||||
|
imageBitmap = null;
|
||||||
|
super.onLoadCleared(placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
|||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||||
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
@@ -2367,6 +2368,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||||||
} else if (!canPlayContent && mediaItem != null && eventListener != null) {
|
} else if (!canPlayContent && mediaItem != null && eventListener != null) {
|
||||||
eventListener.onPlayInlineContent(conversationMessage);
|
eventListener.onPlayInlineContent(conversationMessage);
|
||||||
} else if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
} else if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||||
|
if (eventListener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||||
messageRecord.getThreadId(),
|
messageRecord.getThreadId(),
|
||||||
messageRecord.getTimestamp(),
|
messageRecord.getTimestamp(),
|
||||||
@@ -2388,6 +2393,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||||||
mediaThumbnailStub.require().getCorners().getBottomRight(),
|
mediaThumbnailStub.require().getCorners().getBottomRight(),
|
||||||
mediaThumbnailStub.require().getCorners().getBottomLeft()
|
mediaThumbnailStub.require().getCorners().getBottomLeft()
|
||||||
));
|
));
|
||||||
|
MediaPreviewCache.INSTANCE.setBitmap(mediaThumbnailStub.require().getBitmap());
|
||||||
eventListener.goToMediaPreview(ConversationItem.this, mediaThumbnailStub.require(), args);
|
eventListener.goToMediaPreview(ConversationItem.this, mediaThumbnailStub.require(), args);
|
||||||
} else if (slide.getUri() != null) {
|
} else if (slide.getUri() != null) {
|
||||||
Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
|
Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
|||||||
import org.thoughtcrime.securesms.database.MediaTable;
|
import org.thoughtcrime.securesms.database.MediaTable;
|
||||||
import org.thoughtcrime.securesms.database.MediaTable.MediaRecord;
|
import org.thoughtcrime.securesms.database.MediaTable.MediaRecord;
|
||||||
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader.GroupedThreadMedia;
|
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader.GroupedThreadMedia;
|
||||||
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
@@ -345,7 +346,10 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thumbnailView.setImageResource(glideRequests, slide, false, false);
|
thumbnailView.setImageResource(glideRequests, slide, false, false);
|
||||||
thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(thumbnailView, mediaRecord));
|
thumbnailView.setOnClickListener(view -> {
|
||||||
|
MediaPreviewCache.INSTANCE.setBitmap(thumbnailView.getBitmap());
|
||||||
|
itemClickListener.onMediaClicked(thumbnailView, mediaRecord);
|
||||||
|
});
|
||||||
thumbnailView.setOnLongClickListener(view -> onLongClick());
|
thumbnailView.setOnLongClickListener(view -> onLongClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,6 +598,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected @NonNull View getTransitionAnchor() {
|
protected @NonNull View getTransitionAnchor() {
|
||||||
|
MediaPreviewCache.INSTANCE.setBitmap(null);
|
||||||
return thumbnailView;
|
return thumbnailView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public final class ImageMediaPreviewFragment extends MediaPreviewFragment {
|
|||||||
zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia());
|
zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia());
|
||||||
|
|
||||||
lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> {
|
lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> {
|
||||||
zoomingImageView.setVisibility(state.isInSharedAnimation() ? View.INVISIBLE : View.VISIBLE);
|
zoomingImageView.setAlpha(state.isInSharedAnimation() ? 0f : 1f);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.thoughtcrime.securesms.mediapreview
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the bitmap for a thumbnail we are animating from via a shared
|
||||||
|
* element transition. This prevents us from having to load anything on the
|
||||||
|
* receiving end.
|
||||||
|
*/
|
||||||
|
object MediaPreviewCache {
|
||||||
|
var bitmap: Bitmap? = null
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.mediapreview
|
package org.thoughtcrime.securesms.mediapreview
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
@@ -11,18 +10,14 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.transition.addListener
|
import androidx.core.transition.addListener
|
||||||
import androidx.core.view.animation.PathInterpolatorCompat
|
import androidx.core.view.animation.PathInterpolatorCompat
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
|
||||||
import com.google.android.material.shape.ShapeAppearanceModel
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
import com.google.android.material.transition.platform.MaterialContainerTransform
|
import com.google.android.material.transition.platform.MaterialContainerTransform
|
||||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.util.ActionRequestListener
|
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
|
||||||
class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner {
|
class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner {
|
||||||
@@ -41,6 +36,8 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||||
val args = MediaIntentFactory.requireArguments(intent.extras!!)
|
val args = MediaIntentFactory.requireArguments(intent.extras!!)
|
||||||
|
|
||||||
|
if (MediaPreviewCache.bitmap != null) {
|
||||||
val originalCorners = ShapeAppearanceModel.Builder()
|
val originalCorners = ShapeAppearanceModel.Builder()
|
||||||
.setTopLeftCornerSize(args.sharedElementArgs.topLeft)
|
.setTopLeftCornerSize(args.sharedElementArgs.topLeft)
|
||||||
.setTopRightCornerSize(args.sharedElementArgs.topRight)
|
.setTopRightCornerSize(args.sharedElementArgs.topRight)
|
||||||
@@ -48,7 +45,6 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||||||
.setBottomLeftCornerSize(args.sharedElementArgs.bottomLeft)
|
.setBottomLeftCornerSize(args.sharedElementArgs.bottomLeft)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
postponeEnterTransition()
|
|
||||||
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
|
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
|
||||||
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
|
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
|
||||||
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
|
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
|
||||||
@@ -58,12 +54,10 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||||||
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
||||||
addListener(
|
addListener(
|
||||||
onStart = {
|
onStart = {
|
||||||
transitionImageView.visibility = View.VISIBLE
|
transitionImageView.alpha = 1f
|
||||||
viewModel.setIsInSharedAnimation(true)
|
viewModel.setIsInSharedAnimation(true)
|
||||||
},
|
},
|
||||||
onEnd = {
|
onEnd = {
|
||||||
transitionImageView.clearAnimation()
|
|
||||||
transitionImageView.visibility = View.INVISIBLE
|
|
||||||
viewModel.setIsInSharedAnimation(false)
|
viewModel.setIsInSharedAnimation(false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -77,25 +71,34 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||||||
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
||||||
addListener(
|
addListener(
|
||||||
onStart = {
|
onStart = {
|
||||||
transitionImageView.visibility = View.VISIBLE
|
transitionImageView.alpha = 1f
|
||||||
viewModel.setIsInSharedAnimation(true)
|
viewModel.setIsInSharedAnimation(true)
|
||||||
},
|
},
|
||||||
onEnd = {
|
onEnd = {
|
||||||
transitionImageView.clearAnimation()
|
|
||||||
transitionImageView.visibility = View.INVISIBLE
|
|
||||||
viewModel.setIsInSharedAnimation(false)
|
viewModel.setIsInSharedAnimation(false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState, ready)
|
super.onCreate(savedInstanceState, ready)
|
||||||
setTheme(R.style.TextSecure_MediaPreview)
|
setTheme(R.style.TextSecure_MediaPreview)
|
||||||
setContentView(R.layout.activity_mediapreview_v2)
|
setContentView(R.layout.activity_mediapreview_v2)
|
||||||
|
|
||||||
transitionImageView = findViewById(R.id.transition_image_view)
|
transitionImageView = findViewById(R.id.transition_image_view)
|
||||||
lifecycleDisposable += viewModel.state.subscribe { state ->
|
if (MediaPreviewCache.bitmap != null) {
|
||||||
if (state.position in state.mediaRecords.indices) {
|
transitionImageView.setImageBitmap(MediaPreviewCache.bitmap)
|
||||||
setTransitionImage(state.mediaRecords[state.position].attachment?.uri)
|
} else {
|
||||||
|
transitionImageView.visibility = View.INVISIBLE
|
||||||
|
viewModel.setIsInSharedAnimation(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleDisposable += viewModel.state.map {
|
||||||
|
it.isInSharedAnimation to it.loadState
|
||||||
|
}.distinctUntilChanged().subscribe { (isInSharedAnimation, loadState) ->
|
||||||
|
if (!isInSharedAnimation && loadState == MediaPreviewV2State.LoadState.MEDIA_READY) {
|
||||||
|
transitionImageView.clearAnimation()
|
||||||
|
transitionImageView.alpha = 0f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,19 +118,9 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTransitionImage(mediaUri: Uri?) {
|
override fun onPause() {
|
||||||
if (mediaUri == null) {
|
super.onPause()
|
||||||
GlideApp.with(this).clear(transitionImageView)
|
MediaPreviewCache.bitmap = null
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
GlideApp.with(this)
|
|
||||||
.load(DecryptableStreamUriLoader.DecryptableUri(mediaUri))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.dontTransform()
|
|
||||||
.downsample(DownsampleStrategy.FIT_CENTER)
|
|
||||||
.addListener(ActionRequestListener.onEither { startPostponedEnterTransition() })
|
|
||||||
.into(transitionImageView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -352,6 +352,10 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun scrollAlbumRailToCurrentAdapterPosition(smooth: Boolean = true) {
|
private fun scrollAlbumRailToCurrentAdapterPosition(smooth: Boolean = true) {
|
||||||
|
if (!isResumed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val currentItemPosition = albumRailAdapter.findSelectedItemPosition()
|
val currentItemPosition = albumRailAdapter.findSelectedItemPosition()
|
||||||
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
|
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
|
||||||
val offsetFromStart = (albumRail.width - individualItemWidth) / 2
|
val offsetFromStart = (albumRail.width - individualItemWidth) / 2
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
|||||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||||
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
|
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||||
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
|
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
|
||||||
import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog;
|
import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog;
|
||||||
@@ -536,7 +537,10 @@ public class AttachmentManager {
|
|||||||
private class ThumbnailClickListener implements View.OnClickListener {
|
private class ThumbnailClickListener implements View.OnClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (slide.isPresent()) previewImageDraft(slide.get());
|
if (slide.isPresent()) {
|
||||||
|
MediaPreviewCache.INSTANCE.setBitmap(((ThumbnailView) v).getBitmap());
|
||||||
|
previewImageDraft(slide.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragment_container_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/signal_dark_colorNeutral" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/transition_image_view"
|
android:id="@+id/transition_image_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -12,10 +18,4 @@
|
|||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
android:transitionName="thumb" />
|
android:transitionName="thumb" />
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/fragment_container_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@color/signal_dark_colorNeutral" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:longClickable="false"
|
android:longClickable="false"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="centerCrop"
|
||||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"
|
tools:visibility="visible"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||||
android:longClickable="false"
|
android:longClickable="false"
|
||||||
android:scaleType="fitCenter" />
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/thumbnail_caption_icon"
|
android:id="@+id/thumbnail_caption_icon"
|
||||||
|
|||||||
Reference in New Issue
Block a user