diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java index 7864e26ea3..ba733fb67b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java @@ -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 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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java index 59f46bc381..98756d09bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java @@ -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()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt index af42f32a4a..0676f053fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java index 9cf0d7a484..b4684f8ec7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java @@ -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); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java index 9ddcc0145e..ff1ef15fe9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -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 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 { 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 loaded) { + super(view, loaded); + } + + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + imageBitmap = ((BitmapDrawable) resource).getBitmap(); + super.onResourceReady(resource, transition); + } + + @Override public void onLoadCleared(@Nullable Drawable placeholder) { + imageBitmap = null; + super.onLoadCleared(placeholder); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index d76d2bc9b0..58e9e06b3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java index 6ed5db3fb2..f62e93c09f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java index 7da03274d6..2a3bc13106 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewCache.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewCache.kt new file mode 100644 index 0000000000..423d07005a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewCache.kt @@ -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 +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt index b7a0a54dba..1aacb16270 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index a47eade61e..fc9367b507 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index fdb0edcf11..573bd65de9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -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()); + } } } diff --git a/app/src/main/res/layout/activity_mediapreview_v2.xml b/app/src/main/res/layout/activity_mediapreview_v2.xml index 3105efa4b9..81326e5048 100644 --- a/app/src/main/res/layout/activity_mediapreview_v2.xml +++ b/app/src/main/res/layout/activity_mediapreview_v2.xml @@ -3,6 +3,12 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + - - \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml index 09568d08bc..39d2c99e77 100644 --- a/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml +++ b/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml @@ -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" diff --git a/app/src/main/res/layout/thumbnail_view.xml b/app/src/main/res/layout/thumbnail_view.xml index a5f00dae0f..0dde744ed1 100644 --- a/app/src/main/res/layout/thumbnail_view.xml +++ b/app/src/main/res/layout/thumbnail_view.xml @@ -23,7 +23,7 @@ android:clickable="false" android:contentDescription="@string/conversation_item__mms_image_description" android:longClickable="false" - android:scaleType="fitCenter" /> + android:scaleType="centerCrop" />