mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
[SES-337] Add rounded corners to thumbnail in QuoteView (#1285)
* 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 <160798022+AL-Session@users.noreply.github.com> Co-authored-by: ThomasSession <thomas.r@getsession.org>
This commit is contained in:
parent
5cd2cf5cb5
commit
1d80bb0ba9
@ -114,7 +114,7 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
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));
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
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)
|
||||
)
|
||||
|
||||
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))
|
||||
setRoundedCorners(
|
||||
getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0)
|
||||
)
|
||||
|
||||
radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0)
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
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<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> {
|
||||
|
||||
val currentSlide = this.slide
|
||||
|
||||
binding.playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() &&
|
||||
(slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview))
|
||||
|
||||
if (equals(currentSlide, slide)) {
|
||||
// don't re-load slide
|
||||
return SettableFuture(false)
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
outlineProvider = mOutlineProvider
|
||||
clipToOutline = true
|
||||
}
|
||||
|
||||
if (currentSlide != null && currentSlide.fastPreflightId != null && currentSlide.fastPreflightId == slide.fastPreflightId) {
|
||||
// not reloading slide for fast preflight
|
||||
this.slide = slide
|
||||
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))
|
||||
|
||||
if (equals(this.slide, slide)) {
|
||||
// don't re-load slide
|
||||
return SettableFuture(false)
|
||||
}
|
||||
|
||||
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> {
|
||||
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)
|
||||
|
||||
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 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>()
|
||||
fun setImageResource(
|
||||
glideRequests: GlideRequests,
|
||||
uri: Uri
|
||||
): ListenableFuture<Boolean> = glideRequests.load(DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.transform(CenterCrop())
|
||||
.intoDrawableTargetAsFuture()
|
||||
|
||||
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())
|
||||
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))
|
||||
|
@ -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 @@ public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.Medi
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -9,15 +9,13 @@
|
||||
<include layout="@layout/thumbnail_view"
|
||||
android:id="@+id/album_cell_1"
|
||||
android:layout_width="@dimen/album_3_cell_width_big"
|
||||
android:layout_height="@dimen/album_3_total_height"
|
||||
app:thumbnail_radius="0dp"/>
|
||||
android:layout_height="@dimen/album_3_total_height"/>
|
||||
|
||||
<include layout="@layout/thumbnail_view"
|
||||
android:id="@+id/album_cell_2"
|
||||
android:layout_width="@dimen/album_3_cell_size_small"
|
||||
android:layout_height="@dimen/album_3_cell_size_small"
|
||||
android:layout_gravity="end|top"
|
||||
app:thumbnail_radius="0dp"/>
|
||||
android:layout_gravity="end|top"/>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
@ -29,8 +27,7 @@
|
||||
android:id="@+id/album_cell_3"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
app:thumbnail_radius="0dp"/>
|
||||
android:layout_gravity="center_horizontal|bottom"/>
|
||||
|
||||
<TextView
|
||||
tools:visibility="visible"
|
||||
|
@ -12,9 +12,7 @@
|
||||
android:id="@+id/rail_item_image"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/mediarail_media_outline"
|
||||
app:thumbnail_radius="5dp"/>
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/rail_item_outline"
|
||||
|
Loading…
Reference in New Issue
Block a user