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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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