Speed up thumbnail transition.
This commit is contained in:
parent
d9c31a6cd6
commit
d7c3112602
15 changed files with 190 additions and 127 deletions
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
@ -29,9 +30,11 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||
|
||||
private ViewGroup albumCellContainer;
|
||||
private Stub<TransferControlView> transferControls;
|
||||
private Bitmap bitmap;
|
||||
|
||||
private final SlideClickListener defaultThumbnailClickListener = (v, slide) -> {
|
||||
if (thumbnailClickListener != null) {
|
||||
bitmap = ((ThumbnailView) v).getBitmap();
|
||||
thumbnailClickListener.onClick(v, slide);
|
||||
}
|
||||
};
|
||||
|
@ -84,6 +87,10 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||
showSlides(glideRequests, slides);
|
||||
}
|
||||
|
||||
public @Nullable Bitmap getBitmap() {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public void setCellBackgroundColor(@ColorInt int color) {
|
||||
ViewGroup cellRoot = findViewById(R.id.album_thumbnail_root);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -58,10 +59,10 @@ public class BorderlessImageView extends FrameLayout {
|
|||
boolean showControls = slide.asAttachment().getUri() == null;
|
||||
|
||||
if (slide.hasSticker()) {
|
||||
image.setFit(new CenterInside());
|
||||
image.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
image.setImageResource(glideRequests, slide, showControls, false);
|
||||
} else {
|
||||
image.setFit(new CenterCrop());
|
||||
image.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
image.setImageResource(glideRequests, slide, showControls, false, slide.asAttachment().getWidth(), slide.asAttachment().getHeight());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
|
@ -130,6 +131,14 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
|||
state.applyState(thumbnail, album)
|
||||
}
|
||||
|
||||
fun getBitmap(): Bitmap? {
|
||||
return if (thumbnail.resolved()) {
|
||||
thumbnail.get().bitmap
|
||||
} else {
|
||||
album.get().bitmap
|
||||
}
|
||||
}
|
||||
|
||||
fun hideThumbnailView() {
|
||||
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 0f))
|
||||
state.thumbnailViewState.applyState(thumbnail)
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.signal.core.util.logging.Log;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
@ -97,6 +98,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
|||
}
|
||||
|
||||
imageView.setOnClickListener(v -> {
|
||||
MediaPreviewCache.INSTANCE.setBitmap(imageView.getBitmap());
|
||||
if (clickedListener != null) clickedListener.onItemClicked(imageView, mediaRecord);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.components;
|
|||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
@ -23,12 +25,10 @@ import androidx.appcompat.widget.AppCompatImageView;
|
|||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
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.RequestListener;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
@ -67,25 +67,26 @@ public class ThumbnailView extends FrameLayout {
|
|||
private static final int MIN_HEIGHT = 2;
|
||||
private static final int MAX_HEIGHT = 3;
|
||||
|
||||
private Bitmap imageBitmap;
|
||||
private final ImageView image;
|
||||
private final ImageView blurhash;
|
||||
private final View playOverlay;
|
||||
private final View captionIcon;
|
||||
private final AppCompatImageView errorImage;
|
||||
|
||||
private OnClickListener parentClickListener;
|
||||
private OnClickListener parentClickListener;
|
||||
|
||||
private final int[] dimens = new int[2];
|
||||
private final int[] bounds = new int[4];
|
||||
private final int[] measureDimens = new int[2];
|
||||
|
||||
private final CornerMask cornerMask;
|
||||
|
||||
private Optional<TransferControlView> transferControls = Optional.empty();
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlidesClickedListener downloadClickListener = null;
|
||||
private Slide slide = null;
|
||||
private BitmapTransformation fit = new CenterCrop();
|
||||
|
||||
private int radius;
|
||||
|
||||
public ThumbnailView(Context context) {
|
||||
this(context, null);
|
||||
|
@ -105,6 +106,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
this.playOverlay = findViewById(R.id.play_overlay);
|
||||
this.captionIcon = findViewById(R.id.thumbnail_caption_icon);
|
||||
this.errorImage = findViewById(R.id.thumbnail_error);
|
||||
this.cornerMask = new CornerMask(this);
|
||||
|
||||
super.setOnClickListener(new ThumbnailClickDispatcher());
|
||||
|
||||
|
@ -114,8 +116,9 @@ public class ThumbnailView extends FrameLayout {
|
|||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 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);
|
||||
if (transparentOverlayColor > 0) {
|
||||
|
@ -126,7 +129,8 @@ public class ThumbnailView extends FrameLayout {
|
|||
|
||||
typedArray.recycle();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +160,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
|
||||
if (playOverlayWidth * 2 > getWidth()) {
|
||||
playOverlayScale /= 2;
|
||||
captionIconScale = 0;
|
||||
captionIconScale = 0;
|
||||
}
|
||||
|
||||
playOverlay.setScaleX(playOverlayScale);
|
||||
|
@ -166,6 +170,13 @@ public class ThumbnailView extends FrameLayout {
|
|||
captionIcon.setScaleY(captionIconScale);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
cornerMask.mask(canvas);
|
||||
}
|
||||
|
||||
public void setMinimumThumbnailWidth(@Px int width) {
|
||||
bounds[MIN_WIDTH] = width;
|
||||
invalidate();
|
||||
|
@ -187,7 +198,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
}
|
||||
|
||||
if (dimensAreInvalid || dimensFilledCount == 0 || boundsFilledCount == 0) {
|
||||
targetDimens[WIDTH] = 0;
|
||||
targetDimens[WIDTH] = 0;
|
||||
targetDimens[HEIGHT] = 0;
|
||||
return;
|
||||
}
|
||||
|
@ -219,10 +230,10 @@ public class ThumbnailView extends FrameLayout {
|
|||
|
||||
if (maxWidthRatio > 1 || maxHeightRatio > 1) {
|
||||
if (maxWidthRatio >= maxHeightRatio) {
|
||||
measuredWidth /= maxWidthRatio;
|
||||
measuredWidth /= maxWidthRatio;
|
||||
measuredHeight /= maxWidthRatio;
|
||||
} else {
|
||||
measuredWidth /= maxHeightRatio;
|
||||
measuredWidth /= maxHeightRatio;
|
||||
measuredHeight /= maxHeightRatio;
|
||||
}
|
||||
|
||||
|
@ -231,10 +242,10 @@ public class ThumbnailView extends FrameLayout {
|
|||
|
||||
} else if (minWidthRatio < 1 || minHeightRatio < 1) {
|
||||
if (minWidthRatio <= minHeightRatio) {
|
||||
measuredWidth /= minWidthRatio;
|
||||
measuredWidth /= minWidthRatio;
|
||||
measuredHeight /= minWidthRatio;
|
||||
} else {
|
||||
measuredWidth /= minHeightRatio;
|
||||
measuredWidth /= minHeightRatio;
|
||||
measuredHeight /= minHeightRatio;
|
||||
}
|
||||
|
||||
|
@ -274,6 +285,10 @@ public class ThumbnailView extends FrameLayout {
|
|||
if (transferControls.isPresent()) transferControls.get().setClickable(clickable);
|
||||
}
|
||||
|
||||
public @Nullable Bitmap getBitmap() {
|
||||
return imageBitmap;
|
||||
}
|
||||
|
||||
private TransferControlView getTransferControls() {
|
||||
if (!transferControls.isPresent()) {
|
||||
transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub));
|
||||
|
@ -358,7 +373,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
if (this.slide != null && this.slide.getFastPreflightId() != null &&
|
||||
if (this.slide != null && this.slide.getFastPreflightId() != null &&
|
||||
(!slide.hasVideo() || Util.equals(this.slide.getUri(), slide.getUri())) &&
|
||||
Util.equals(this.slide.getFastPreflightId(), slide.getFastPreflightId()))
|
||||
{
|
||||
|
@ -400,7 +415,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
thumbnailFuture.addListener(new BlurhashClearListener(glideRequests, blurhash));
|
||||
}
|
||||
|
||||
buildThumbnailGlideRequest(glideRequests, slide).into(new GlideDrawableListeningTarget(image, result));
|
||||
buildThumbnailGlideRequest(glideRequests, slide).into(new BitmapCaptor(image, result));
|
||||
|
||||
resultHandled = true;
|
||||
} else {
|
||||
|
@ -440,15 +455,9 @@ public class ThumbnailView extends FrameLayout {
|
|||
request = request.override(width, height);
|
||||
}
|
||||
|
||||
if (radius > 0) {
|
||||
request = request.transforms(new CenterCrop(), new RoundedCorners(radius));
|
||||
} else {
|
||||
request = request.transforms(new CenterCrop());
|
||||
}
|
||||
|
||||
GlideDrawableListeningTarget target = new GlideDrawableListeningTarget(image, future);
|
||||
Request previousRequest = target.getRequest();
|
||||
boolean previousRequestRunning = previousRequest != null && previousRequest.isRunning();
|
||||
GlideDrawableListeningTarget target = new BitmapCaptor(image, future);
|
||||
Request previousRequest = target.getRequest();
|
||||
boolean previousRequestRunning = previousRequest != null && previousRequest.isRunning();
|
||||
request.into(target);
|
||||
if (listener != null) {
|
||||
listener.onLoadScheduled();
|
||||
|
@ -476,13 +485,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
request = request.override(width, height);
|
||||
}
|
||||
|
||||
if (radius > 0) {
|
||||
request = request.transforms(new CenterCrop(), new RoundedCorners(radius));
|
||||
} else {
|
||||
request = request.transforms(new CenterCrop());
|
||||
}
|
||||
|
||||
request.into(new GlideDrawableListeningTarget(image, future));
|
||||
request.into(new BitmapCaptor(image, future));
|
||||
blurhash.setImageDrawable(null);
|
||||
|
||||
return future;
|
||||
|
@ -518,23 +521,27 @@ public class ThumbnailView extends FrameLayout {
|
|||
getTransferControls().showProgressSpinner();
|
||||
}
|
||||
|
||||
public void setFit(@NonNull BitmapTransformation fit) {
|
||||
this.fit = fit;
|
||||
public void setScaleType(@NonNull ImageView.ScaleType scaleType) {
|
||||
image.setScaleType(scaleType);
|
||||
}
|
||||
|
||||
protected void setRadius(int radius) {
|
||||
this.radius = radius;
|
||||
cornerMask.setRadius(radius);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getUri()))
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.transition(withCrossFade()), fit);
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.transition(withCrossFade()));
|
||||
|
||||
boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23;
|
||||
|
||||
if (slide.isInProgress() || doNotShowMissingThumbnailImage) return request;
|
||||
else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
||||
if (slide.isInProgress() || doNotShowMissingThumbnailImage) {
|
||||
return request;
|
||||
} else {
|
||||
return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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];
|
||||
fillTargetDimensions(size, dimens, bounds);
|
||||
if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
|
||||
|
@ -558,13 +565,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
size[HEIGHT] = getDefaultHeight();
|
||||
}
|
||||
|
||||
request = request.override(size[WIDTH], size[HEIGHT]);
|
||||
|
||||
if (radius > 0) {
|
||||
return request.transforms(fitting, new RoundedCorners(radius));
|
||||
} else {
|
||||
return request.transforms(fitting);
|
||||
}
|
||||
return request.override(size[WIDTH], size[HEIGHT]);
|
||||
}
|
||||
|
||||
private int getDefaultWidth() {
|
||||
|
@ -585,6 +586,7 @@ public class ThumbnailView extends FrameLayout {
|
|||
|
||||
public interface ThumbnailRequestListener extends RequestListener<Drawable> {
|
||||
void onLoadCanceled();
|
||||
|
||||
void onLoadScheduled();
|
||||
}
|
||||
|
||||
|
@ -639,4 +641,22 @@ public class ThumbnailView extends FrameLayout {
|
|||
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.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
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) {
|
||||
eventListener.onPlayInlineContent(conversationMessage);
|
||||
} else if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||
if (eventListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
messageRecord.getThreadId(),
|
||||
messageRecord.getTimestamp(),
|
||||
|
@ -2388,6 +2393,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
mediaThumbnailStub.require().getCorners().getBottomRight(),
|
||||
mediaThumbnailStub.require().getCorners().getBottomLeft()
|
||||
));
|
||||
MediaPreviewCache.INSTANCE.setBitmap(mediaThumbnailStub.require().getBitmap());
|
||||
eventListener.goToMediaPreview(ConversationItem.this, mediaThumbnailStub.require(), args);
|
||||
} else if (slide.getUri() != null) {
|
||||
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.MediaRecord;
|
||||
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.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
|
@ -345,7 +346,10 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
|
|||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -594,6 +598,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
|
|||
|
||||
@Override
|
||||
protected @NonNull View getTransitionAnchor() {
|
||||
MediaPreviewCache.INSTANCE.setBitmap(null);
|
||||
return thumbnailView;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ public final class ImageMediaPreviewFragment extends MediaPreviewFragment {
|
|||
zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia());
|
||||
|
||||
lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> {
|
||||
zoomingImageView.setVisibility(state.isInSharedAnimation() ? View.INVISIBLE : View.VISIBLE);
|
||||
zoomingImageView.setAlpha(state.isInSharedAnimation() ? 0f : 1f);
|
||||
}));
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
|
@ -11,18 +10,14 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.transition.addListener
|
||||
import androidx.core.view.animation.PathInterpolatorCompat
|
||||
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.transition.platform.MaterialContainerTransform
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
|
||||
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
|
||||
|
||||
class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner {
|
||||
|
@ -41,51 +36,49 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
val args = MediaIntentFactory.requireArguments(intent.extras!!)
|
||||
val originalCorners = ShapeAppearanceModel.Builder()
|
||||
.setTopLeftCornerSize(args.sharedElementArgs.topLeft)
|
||||
.setTopRightCornerSize(args.sharedElementArgs.topRight)
|
||||
.setBottomRightCornerSize(args.sharedElementArgs.bottomRight)
|
||||
.setBottomLeftCornerSize(args.sharedElementArgs.bottomLeft)
|
||||
.build()
|
||||
|
||||
postponeEnterTransition()
|
||||
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
|
||||
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
|
||||
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
|
||||
startShapeAppearanceModel = originalCorners
|
||||
endShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build()
|
||||
duration = 250L
|
||||
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
||||
addListener(
|
||||
onStart = {
|
||||
transitionImageView.visibility = View.VISIBLE
|
||||
viewModel.setIsInSharedAnimation(true)
|
||||
},
|
||||
onEnd = {
|
||||
transitionImageView.clearAnimation()
|
||||
transitionImageView.visibility = View.INVISIBLE
|
||||
viewModel.setIsInSharedAnimation(false)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (MediaPreviewCache.bitmap != null) {
|
||||
val originalCorners = ShapeAppearanceModel.Builder()
|
||||
.setTopLeftCornerSize(args.sharedElementArgs.topLeft)
|
||||
.setTopRightCornerSize(args.sharedElementArgs.topRight)
|
||||
.setBottomRightCornerSize(args.sharedElementArgs.bottomRight)
|
||||
.setBottomLeftCornerSize(args.sharedElementArgs.bottomLeft)
|
||||
.build()
|
||||
|
||||
window.sharedElementExitTransition = MaterialContainerTransform().apply {
|
||||
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
|
||||
startShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build()
|
||||
endShapeAppearanceModel = originalCorners
|
||||
duration = 250L
|
||||
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
||||
addListener(
|
||||
onStart = {
|
||||
transitionImageView.visibility = View.VISIBLE
|
||||
viewModel.setIsInSharedAnimation(true)
|
||||
},
|
||||
onEnd = {
|
||||
transitionImageView.clearAnimation()
|
||||
transitionImageView.visibility = View.INVISIBLE
|
||||
viewModel.setIsInSharedAnimation(false)
|
||||
}
|
||||
)
|
||||
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
|
||||
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
|
||||
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
|
||||
startShapeAppearanceModel = originalCorners
|
||||
endShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build()
|
||||
duration = 250L
|
||||
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
||||
addListener(
|
||||
onStart = {
|
||||
transitionImageView.alpha = 1f
|
||||
viewModel.setIsInSharedAnimation(true)
|
||||
},
|
||||
onEnd = {
|
||||
viewModel.setIsInSharedAnimation(false)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
window.sharedElementExitTransition = MaterialContainerTransform().apply {
|
||||
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
|
||||
startShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build()
|
||||
endShapeAppearanceModel = originalCorners
|
||||
duration = 250L
|
||||
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
|
||||
addListener(
|
||||
onStart = {
|
||||
transitionImageView.alpha = 1f
|
||||
viewModel.setIsInSharedAnimation(true)
|
||||
},
|
||||
onEnd = {
|
||||
viewModel.setIsInSharedAnimation(false)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
|
@ -93,9 +86,19 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
|
|||
setContentView(R.layout.activity_mediapreview_v2)
|
||||
|
||||
transitionImageView = findViewById(R.id.transition_image_view)
|
||||
lifecycleDisposable += viewModel.state.subscribe { state ->
|
||||
if (state.position in state.mediaRecords.indices) {
|
||||
setTransitionImage(state.mediaRecords[state.position].attachment?.uri)
|
||||
if (MediaPreviewCache.bitmap != null) {
|
||||
transitionImageView.setImageBitmap(MediaPreviewCache.bitmap)
|
||||
} 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?) {
|
||||
if (mediaUri == null) {
|
||||
GlideApp.with(this).clear(transitionImageView)
|
||||
return
|
||||
}
|
||||
|
||||
GlideApp.with(this)
|
||||
.load(DecryptableStreamUriLoader.DecryptableUri(mediaUri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontTransform()
|
||||
.downsample(DownsampleStrategy.FIT_CENTER)
|
||||
.addListener(ActionRequestListener.onEither { startPostponedEnterTransition() })
|
||||
.into(transitionImageView)
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
MediaPreviewCache.bitmap = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -352,6 +352,10 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
|||
}
|
||||
|
||||
private fun scrollAlbumRailToCurrentAdapterPosition(smooth: Boolean = true) {
|
||||
if (!isResumed) {
|
||||
return
|
||||
}
|
||||
|
||||
val currentItemPosition = albumRailAdapter.findSelectedItemPosition()
|
||||
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
|
||||
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.maps.PlacePickerActivity;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache;
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
|
||||
import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog;
|
||||
|
@ -536,7 +537,10 @@ public class AttachmentManager {
|
|||
private class ThumbnailClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
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_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
|
||||
android:id="@+id/transition_image_view"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -12,10 +18,4 @@
|
|||
android:importantForAccessibility="no"
|
||||
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>
|
|
@ -8,7 +8,7 @@
|
|||
android:adjustViewBounds="true"
|
||||
android:clickable="false"
|
||||
android:longClickable="false"
|
||||
android:scaleType="fitCenter"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
android:clickable="false"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:longClickable="false"
|
||||
android:scaleType="fitCenter" />
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail_caption_icon"
|
||||
|
|
Loading…
Add table
Reference in a new issue