diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java index 0fd813cf4b7..62766d1cd78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java @@ -114,7 +114,7 @@ public void onBindItemViewHolder(ItemViewHolder viewHolder, int section, int off Slide slide = MediaUtil.getSlideForAttachment(context, mediaRecord.getAttachment()); if (slide != null) { - thumbnailView.setImageResource(glideRequests, slide, false, null); + thumbnailView.setImageResource(glideRequests, slide, false); } thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index d76e6f2b3d1..f646ace972c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -104,7 +104,7 @@ class AlbumThumbnailView : RelativeLayout { // iterate binding slides.take(MAX_ALBUM_DISPLAY_SIZE).forEachIndexed { position, slide -> val thumbnailView = getThumbnailView(position) - thumbnailView.setImageResource(glideRequests, slide, isPreview = false, mms = message) + thumbnailView.setImageResource(glideRequests, slide, isPreview = false) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt index 66164f100f6..9c414f34fd4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt @@ -31,10 +31,10 @@ class LinkPreviewDraftView : LinearLayout { // Hide the loader and show the content view binding.linkPreviewDraftContainer.isVisible = true binding.linkPreviewDraftLoader.isVisible = false - binding.thumbnailImageView.root.radius = toPx(4, resources) + binding.thumbnailImageView.root.setRoundedCorners(toPx(4, resources)) if (linkPreview.getThumbnail().isPresent) { // This internally fetches the thumbnail - binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, null) + binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false) } binding.linkPreviewDraftTitleTextView.text = linkPreview.title } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 96772238942..4e6066edb32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -41,7 +41,7 @@ class LinkPreviewView : LinearLayout { // Thumbnail if (linkPreview.getThumbnail().isPresent) { // This internally fetches the thumbnail - binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false, message) + binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false) binding.thumbnailImageView.root.loadIndicator.isVisible = false } // Title diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 2e0dae6b0d2..927ad5f60fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -108,8 +108,9 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? attachments.thumbnailSlide != null -> { val slide = attachments.thumbnailSlide!! // This internally fetches the thumbnail - binding.quoteViewAttachmentThumbnailImageView.root.radius = toPx(4, resources) - binding.quoteViewAttachmentThumbnailImageView.root.setImageResource(glide, slide, false, null) + binding.quoteViewAttachmentThumbnailImageView + .root.setRoundedCorners(toPx(4, resources)) + binding.quoteViewAttachmentThumbnailImageView.root.setImageResource(glide, slide, false) binding.quoteViewAttachmentThumbnailImageView.root.isVisible = true binding.quoteViewBodyTextView.text = if (MediaUtil.isVideo(slide.asAttachment())) resources.getString(R.string.Slide_video) else resources.getString(R.string.Slide_image) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index 4a9986d6eca..02c683aac61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -2,10 +2,13 @@ package org.thoughtcrime.securesms.conversation.v2.utilities import android.content.Context import android.graphics.Bitmap +import android.graphics.Outline import android.graphics.drawable.Drawable import android.net.Uri import android.util.AttributeSet +import android.util.TypedValue import android.view.View +import android.view.ViewOutlineProvider import android.widget.FrameLayout import androidx.core.view.isVisible import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -21,18 +24,17 @@ import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.SettableFuture import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget -import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri import org.thoughtcrime.securesms.mms.GlideRequest import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide -import kotlin.Boolean -import kotlin.Int -import kotlin.getValue -import kotlin.lazy -import kotlin.let -open class ThumbnailView: FrameLayout { +open class ThumbnailView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + companion object { private const val WIDTH = 0 private const val HEIGHT = 1 @@ -41,30 +43,29 @@ open class ThumbnailView: FrameLayout { private val binding: ThumbnailViewBinding by lazy { ThumbnailViewBinding.bind(this) } // region Lifecycle - constructor(context: Context) : super(context) { initialize(null) } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize(attrs) } val loadIndicator: View by lazy { binding.thumbnailLoadIndicator } private val dimensDelegate = ThumbnailDimensDelegate() private var slide: Slide? = null - var radius: Int = 0 - - private fun initialize(attrs: AttributeSet?) { - if (attrs != null) { - val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0) - - dimensDelegate.setBounds(typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0), - typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0), - typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0), - typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0)) - radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0) - - typedArray.recycle() - } + init { + attrs?.let { context.theme.obtainStyledAttributes(it, R.styleable.ThumbnailView, 0, 0) } + ?.apply { + dimensDelegate.setBounds( + getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0), + getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0), + getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0), + getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0) + ) + + setRoundedCorners( + getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0) + ) + + recycle() + } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { @@ -84,114 +85,118 @@ open class ThumbnailView: FrameLayout { private fun getDefaultWidth() = maxOf(layoutParams?.width ?: 0, 0) private fun getDefaultHeight() = maxOf(layoutParams?.height ?: 0, 0) + // endregion // region Interaction - fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, mms: MmsMessageRecord?): ListenableFuture { - return setImageResource(glide, slide, isPreview, 0, 0, mms) - } - - fun setImageResource(glide: GlideRequests, slide: Slide, - isPreview: Boolean, naturalWidth: Int, - naturalHeight: Int, mms: MmsMessageRecord?): ListenableFuture { + fun setRoundedCorners(radius: Int){ + // create an outline provider and clip the whole view to that shape + // that way we can round the image and the background ( and any other artifacts that the view may contain ) + val mOutlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + // all corners + outline.setRoundRect(0, 0, view.width, view.height, radius.toFloat()) + } + } - val currentSlide = this.slide + outlineProvider = mOutlineProvider + clipToOutline = true + } + fun setImageResource( + glide: GlideRequests, + slide: Slide, + isPreview: Boolean + ): ListenableFuture = setImageResource(glide, slide, isPreview, 0, 0) + + fun setImageResource( + glide: GlideRequests, slide: Slide, + isPreview: Boolean, naturalWidth: Int, + naturalHeight: Int + ): ListenableFuture { binding.playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() && - (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview)) + (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview)) - if (equals(currentSlide, slide)) { + if (equals(this.slide, slide)) { // don't re-load slide return SettableFuture(false) } - - if (currentSlide != null && currentSlide.fastPreflightId != null && currentSlide.fastPreflightId == slide.fastPreflightId) { - // not reloading slide for fast preflight - this.slide = slide - } - this.slide = slide binding.thumbnailLoadIndicator.isVisible = slide.isInProgress - binding.thumbnailDownloadIcon.isVisible = slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED + binding.thumbnailDownloadIcon.isVisible = + slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED dimensDelegate.setDimens(naturalWidth, naturalHeight) invalidate() - val result = SettableFuture() - - when { - slide.thumbnailUri != null -> { - buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) - } - slide.hasPlaceholder() -> { - buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, null, result)) - } - else -> { - glide.clear(binding.thumbnailImage) - result.set(false) + return SettableFuture().also { + when { + slide.thumbnailUri != null -> { + buildThumbnailGlideRequest(glide, slide).into( + GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, it) + ) + } + slide.hasPlaceholder() -> { + buildPlaceholderGlideRequest(glide, slide).into( + GlideBitmapListeningTarget(binding.thumbnailImage, null, it) + ) + } + else -> { + glide.clear(binding.thumbnailImage) + it.set(false) + } } } - return result } - fun buildThumbnailGlideRequest(glide: GlideRequests, slide: Slide): GlideRequest { - - val dimens = dimensDelegate.resourceSize() - - val request = glide.load(DecryptableUri(slide.thumbnailUri!!)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .let { request -> - if (dimens[WIDTH] == 0 || dimens[HEIGHT] == 0) { - request.override(getDefaultWidth(), getDefaultHeight()) - } else { - request.override(dimens[WIDTH], dimens[HEIGHT]) - } - } - .transition(DrawableTransitionOptions.withCrossFade()) - .centerCrop() - - return if (slide.isInProgress) request else request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)) - } - - fun buildPlaceholderGlideRequest(glide: GlideRequests, slide: Slide): GlideRequest { - - val dimens = dimensDelegate.resourceSize() - - return glide.asBitmap() - .load(slide.getPlaceholderRes(context.theme)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .let { request -> - if (dimens[WIDTH] == 0 || dimens[HEIGHT] == 0) { - request.override(getDefaultWidth(), getDefaultHeight()) - } else { - request.override(dimens[WIDTH], dimens[HEIGHT]) - } - } - .fitCenter() - } + private fun buildThumbnailGlideRequest( + glide: GlideRequests, + slide: Slide + ): GlideRequest = glide.load(DecryptableUri(slide.thumbnailUri!!)) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .overrideDimensions() + .transition(DrawableTransitionOptions.withCrossFade()) + .transform(CenterCrop()) + .missingThumbnailPicture(slide.isInProgress) + + private fun buildPlaceholderGlideRequest( + glide: GlideRequests, + slide: Slide + ): GlideRequest = glide.asBitmap() + .load(slide.getPlaceholderRes(context.theme)) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .overrideDimensions() + .fitCenter() open fun clear(glideRequests: GlideRequests) { glideRequests.clear(binding.thumbnailImage) slide = null } - fun setImageResource(glideRequests: GlideRequests, uri: Uri): ListenableFuture { - val future = SettableFuture() - - var request: GlideRequest = glideRequests.load(DecryptableUri(uri)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(DrawableTransitionOptions.withCrossFade()) - - request = if (radius > 0) { - request.transforms(CenterCrop(), RoundedCorners(radius)) - } else { - request.transforms(CenterCrop()) + fun setImageResource( + glideRequests: GlideRequests, + uri: Uri + ): ListenableFuture = glideRequests.load(DecryptableUri(uri)) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .transition(DrawableTransitionOptions.withCrossFade()) + .transform(CenterCrop()) + .intoDrawableTargetAsFuture() + + private fun GlideRequest.intoDrawableTargetAsFuture() = + SettableFuture().also { + binding.run { + GlideDrawableListeningTarget(thumbnailImage, thumbnailLoadIndicator, it) + }.let { into(it) } } - request.into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, future)) + private fun GlideRequest.overrideDimensions() = + dimensDelegate.resourceSize().takeIf { 0 !in it } + ?.let { override(it[WIDTH], it[HEIGHT]) } + ?: override(getDefaultWidth(), getDefaultHeight()) +} - return future - } -} \ No newline at end of file +private fun GlideRequest.missingThumbnailPicture( + inProgress: Boolean +) = takeIf { inProgress } ?: apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java index dd27c425026..6492069780c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.mediapreview; +import static org.thoughtcrime.securesms.util.GeneralUtilitiesKt.toPx; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; @@ -151,6 +153,8 @@ void bind(@NonNull Media media, boolean isActive, @NonNull GlideRequests glideRe { image.setImageResource(glideRequests, media.getUri()); image.setOnClickListener(v -> railItemListener.onRailItemClicked(distanceFromActive)); + // set the rounded corners + image.setRoundedCorners(toPx(5, image.getResources())); outline.setVisibility(isActive ? View.VISIBLE : View.GONE); diff --git a/app/src/main/res/layout/album_thumbnail_1.xml b/app/src/main/res/layout/album_thumbnail_1.xml index cee81ba3e39..2f3ffdaa792 100644 --- a/app/src/main/res/layout/album_thumbnail_1.xml +++ b/app/src/main/res/layout/album_thumbnail_1.xml @@ -9,11 +9,6 @@ + android:layout_height="match_parent"/> \ No newline at end of file diff --git a/app/src/main/res/layout/album_thumbnail_2.xml b/app/src/main/res/layout/album_thumbnail_2.xml index 52375d025ca..1712bdb741d 100644 --- a/app/src/main/res/layout/album_thumbnail_2.xml +++ b/app/src/main/res/layout/album_thumbnail_2.xml @@ -10,14 +10,12 @@ + android:layout_height="@dimen/album_2_total_height"/> + android:layout_gravity="end"/> \ No newline at end of file diff --git a/app/src/main/res/layout/album_thumbnail_3.xml b/app/src/main/res/layout/album_thumbnail_3.xml index b408ffd2bc3..d201565dabd 100644 --- a/app/src/main/res/layout/album_thumbnail_3.xml +++ b/app/src/main/res/layout/album_thumbnail_3.xml @@ -9,15 +9,13 @@ + android:layout_height="@dimen/album_3_total_height"/> + android:layout_gravity="end|top"/> + android:layout_gravity="center_horizontal|bottom"/> + android:layout_gravity="center"/>