Speed up thumbnail transition.

This commit is contained in:
Alex Hart
2023-02-13 10:12:42 -04:00
committed by Greyson Parrelli
parent d9c31a6cd6
commit d7c3112602
15 changed files with 190 additions and 127 deletions

View File

@@ -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);

View File

@@ -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());
} }

View File

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

View File

@@ -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);
}); });
} }

View File

@@ -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);
}
}
} }

View File

@@ -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());

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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
}

View File

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

View File

@@ -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

View File

@@ -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());
}
} }
} }

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"