Skip to content

Commit

Permalink
[SES-337] Add rounded corners to thumbnail in QuoteView (#1285)
Browse files Browse the repository at this point in the history
* Add rounded corners to thumbnail in QuoteView

* Simplify ThumbnailView

* Cleanup ThumbnailView

* Removed include custom attributes

The custom attributes are not passed to the view.
I added the radius programatically instead.

* Clipping whole thumbnail view instead of just the image requests

---------

Co-authored-by: AL-Session <[email protected]>
Co-authored-by: ThomasSession <[email protected]>
  • Loading branch information
3 people authored Jun 30, 2024
1 parent 5cd2cf5 commit 1d80bb0
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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<Boolean> {
return setImageResource(glide, slide, isPreview, 0, 0, mms)
}

fun setImageResource(glide: GlideRequests, slide: Slide,
isPreview: Boolean, naturalWidth: Int,
naturalHeight: Int, mms: MmsMessageRecord?): ListenableFuture<Boolean> {
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<Boolean> = setImageResource(glide, slide, isPreview, 0, 0)

fun setImageResource(
glide: GlideRequests, slide: Slide,
isPreview: Boolean, naturalWidth: Int,
naturalHeight: Int
): ListenableFuture<Boolean> {
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<Boolean>()

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<Boolean>().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<Drawable> {

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

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<Drawable> = 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<Bitmap> = 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<Boolean> {
val future = SettableFuture<Boolean>()

var request: GlideRequest<Drawable> = 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<Boolean> = glideRequests.load(DecryptableUri(uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.withCrossFade())
.transform(CenterCrop())
.intoDrawableTargetAsFuture()

private fun GlideRequest<Drawable>.intoDrawableTargetAsFuture() =
SettableFuture<Boolean>().also {
binding.run {
GlideDrawableListeningTarget(thumbnailImage, thumbnailLoadIndicator, it)
}.let { into(it) }
}

request.into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, future))
private fun <T> GlideRequest<T>.overrideDimensions() =
dimensDelegate.resourceSize().takeIf { 0 !in it }
?.let { override(it[WIDTH], it[HEIGHT]) }
?: override(getDefaultWidth(), getDefaultHeight())
}

return future
}
}
private fun <T> GlideRequest<T>.missingThumbnailPicture(
inProgress: Boolean
) = takeIf { inProgress } ?: apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture))
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);

Expand Down
7 changes: 1 addition & 6 deletions app/src/main/res/layout/album_thumbnail_1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@
<include layout="@layout/thumbnail_view"
android:id="@+id/album_cell_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:minWidth="@dimen/media_bubble_min_width"
app:maxWidth="@dimen/media_bubble_max_width"
app:minHeight="@dimen/media_bubble_min_height"
app:maxHeight="@dimen/media_bubble_max_height"
app:thumbnail_radius="1dp"/>
android:layout_height="match_parent"/>

</FrameLayout>
6 changes: 2 additions & 4 deletions app/src/main/res/layout/album_thumbnail_2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
<include layout="@layout/thumbnail_view"
android:id="@+id/album_cell_1"
android:layout_width="@dimen/album_2_cell_width"
android:layout_height="@dimen/album_2_total_height"
app:thumbnail_radius="0dp"/>
android:layout_height="@dimen/album_2_total_height"/>

<include layout="@layout/thumbnail_view"
android:id="@+id/album_cell_2"
android:layout_width="@dimen/album_2_cell_width"
android:layout_height="@dimen/album_2_total_height"
android:layout_gravity="end"
app:thumbnail_radius="0dp"/>
android:layout_gravity="end"/>

</FrameLayout>
Loading

0 comments on commit 1d80bb0

Please sign in to comment.