Add stubbing to ConversationThumbnailView and caching to a typeface.
This commit is contained in:
parent
ffbebe0670
commit
dda5037429
10 changed files with 417 additions and 248 deletions
|
@ -6,6 +6,7 @@ import android.graphics.Canvas
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.annotation.MainThread
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import com.airbnb.lottie.SimpleColorFilter
|
import com.airbnb.lottie.SimpleColorFilter
|
||||||
import org.signal.core.util.concurrent.SignalExecutors
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
|
@ -28,8 +29,13 @@ object AvatarRenderer {
|
||||||
|
|
||||||
val DIMENSIONS = AvatarHelper.AVATAR_DIMENSIONS
|
val DIMENSIONS = AvatarHelper.AVATAR_DIMENSIONS
|
||||||
|
|
||||||
|
private var typeface: Typeface? = null
|
||||||
|
|
||||||
|
@MainThread
|
||||||
fun getTypeface(context: Context): Typeface {
|
fun getTypeface(context: Context): Typeface {
|
||||||
return Typeface.createFromAsset(context.assets, "fonts/Inter-Medium.otf")
|
val interMedium = typeface ?: Typeface.createFromAsset(context.assets, "fonts/Inter-Medium.otf")
|
||||||
|
typeface = interMedium
|
||||||
|
return interMedium
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderAvatar(context: Context, avatar: Avatar, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
|
fun renderAvatar(context: Context, avatar: Avatar, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
|
||||||
|
|
|
@ -1,221 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.components;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.Px;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
|
||||||
import org.thoughtcrime.securesms.util.Projection;
|
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ConversationItemThumbnail extends FrameLayout {
|
|
||||||
|
|
||||||
private ThumbnailView thumbnail;
|
|
||||||
private AlbumThumbnailView album;
|
|
||||||
private ImageView shade;
|
|
||||||
private ConversationItemFooter footer;
|
|
||||||
private CornerMask cornerMask;
|
|
||||||
private Outliner pulseOutliner;
|
|
||||||
private boolean borderless;
|
|
||||||
private int[] normalBounds;
|
|
||||||
private int[] gifBounds;
|
|
||||||
private int minimumThumbnailWidth;
|
|
||||||
private int maximumThumbnailHeight;
|
|
||||||
|
|
||||||
public ConversationItemThumbnail(Context context) {
|
|
||||||
super(context);
|
|
||||||
init(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationItemThumbnail(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
init(attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationItemThumbnail(final Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
init(attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(@Nullable AttributeSet attrs) {
|
|
||||||
inflate(getContext(), R.layout.conversation_item_thumbnail, this);
|
|
||||||
|
|
||||||
this.thumbnail = findViewById(R.id.conversation_thumbnail_image);
|
|
||||||
this.album = findViewById(R.id.conversation_thumbnail_album);
|
|
||||||
this.shade = findViewById(R.id.conversation_thumbnail_shade);
|
|
||||||
this.footer = findViewById(R.id.conversation_thumbnail_footer);
|
|
||||||
this.cornerMask = new CornerMask(this);
|
|
||||||
|
|
||||||
int gifWidth = ViewUtil.dpToPx(260);
|
|
||||||
if (attrs != null) {
|
|
||||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
|
|
||||||
normalBounds = new int[]{
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0),
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0),
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0),
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
gifWidth = typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_gifWidth, gifWidth);
|
|
||||||
typedArray.recycle();
|
|
||||||
} else {
|
|
||||||
normalBounds = new int[]{0, 0, 0, 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
gifBounds = new int[]{
|
|
||||||
gifWidth,
|
|
||||||
gifWidth,
|
|
||||||
1,
|
|
||||||
Integer.MAX_VALUE
|
|
||||||
};
|
|
||||||
|
|
||||||
minimumThumbnailWidth = -1;
|
|
||||||
maximumThumbnailHeight = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
|
||||||
@Override
|
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
|
||||||
super.dispatchDraw(canvas);
|
|
||||||
|
|
||||||
if (!borderless) {
|
|
||||||
cornerMask.mask(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pulseOutliner != null) {
|
|
||||||
pulseOutliner.draw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideThumbnailView() {
|
|
||||||
thumbnail.setAlpha(0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showThumbnailView() {
|
|
||||||
thumbnail.setAlpha(1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull Projection.Corners getCorners() {
|
|
||||||
return new Projection.Corners(cornerMask.getRadii());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPulseOutliner(@NonNull Outliner outliner) {
|
|
||||||
this.pulseOutliner = outliner;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFocusable(boolean focusable) {
|
|
||||||
thumbnail.setFocusable(focusable);
|
|
||||||
album.setFocusable(focusable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setClickable(boolean clickable) {
|
|
||||||
thumbnail.setClickable(clickable);
|
|
||||||
album.setClickable(clickable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
|
|
||||||
thumbnail.setOnLongClickListener(l);
|
|
||||||
album.setOnLongClickListener(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showShade(boolean show) {
|
|
||||||
shade.setVisibility(show ? VISIBLE : GONE);
|
|
||||||
forceLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCorners(int topLeft, int topRight, int bottomRight, int bottomLeft) {
|
|
||||||
cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMinimumThumbnailWidth(@Px int width) {
|
|
||||||
minimumThumbnailWidth = width;
|
|
||||||
thumbnail.setMinimumThumbnailWidth(width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaximumThumbnailHeight(@Px int height) {
|
|
||||||
maximumThumbnailHeight = height;
|
|
||||||
thumbnail.setMaximumThumbnailHeight(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBorderless(boolean borderless) {
|
|
||||||
this.borderless = borderless;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationItemFooter getFooter() {
|
|
||||||
return footer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides,
|
|
||||||
boolean showControls, boolean isPreview)
|
|
||||||
{
|
|
||||||
if (slides.size() == 1) {
|
|
||||||
Slide slide = slides.get(0);
|
|
||||||
if (slide.isVideoGif()) {
|
|
||||||
setThumbnailBounds(gifBounds);
|
|
||||||
} else {
|
|
||||||
setThumbnailBounds(normalBounds);
|
|
||||||
|
|
||||||
if (minimumThumbnailWidth != -1) {
|
|
||||||
thumbnail.setMinimumThumbnailWidth(minimumThumbnailWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maximumThumbnailHeight != -1) {
|
|
||||||
thumbnail.setMaximumThumbnailHeight(maximumThumbnailHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thumbnail.setVisibility(VISIBLE);
|
|
||||||
album.setVisibility(GONE);
|
|
||||||
|
|
||||||
Attachment attachment = slides.get(0).asAttachment();
|
|
||||||
thumbnail.setImageResource(glideRequests, slides.get(0), showControls, isPreview, attachment.getWidth(), attachment.getHeight());
|
|
||||||
setTouchDelegate(thumbnail.getTouchDelegate());
|
|
||||||
} else {
|
|
||||||
thumbnail.setVisibility(GONE);
|
|
||||||
album.setVisibility(VISIBLE);
|
|
||||||
|
|
||||||
album.setSlides(glideRequests, slides, showControls);
|
|
||||||
setTouchDelegate(album.getTouchDelegate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConversationColor(@ColorInt int color) {
|
|
||||||
if (album.getVisibility() == VISIBLE) {
|
|
||||||
album.setCellBackgroundColor(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setThumbnailClickListener(SlideClickListener listener) {
|
|
||||||
thumbnail.setThumbnailClickListener(listener);
|
|
||||||
album.setThumbnailClickListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDownloadClickListener(SlidesClickedListener listener) {
|
|
||||||
thumbnail.setDownloadClickListener(listener);
|
|
||||||
album.setDownloadClickListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setThumbnailBounds(@NonNull int[] bounds) {
|
|
||||||
thumbnail.setBounds(bounds[0], bounds[1], bounds[2], bounds[3]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.Px
|
||||||
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import org.signal.core.util.dp
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
import org.thoughtcrime.securesms.mms.Slide
|
||||||
|
import org.thoughtcrime.securesms.mms.SlideClickListener
|
||||||
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener
|
||||||
|
import org.thoughtcrime.securesms.util.Projection.Corners
|
||||||
|
import org.thoughtcrime.securesms.util.views.Stub
|
||||||
|
|
||||||
|
class ConversationItemThumbnail @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : FrameLayout(context, attrs) {
|
||||||
|
|
||||||
|
private var state: ConversationItemThumbnailState
|
||||||
|
private var thumbnail: Stub<ThumbnailView>
|
||||||
|
private var album: Stub<AlbumThumbnailView>
|
||||||
|
private var shade: ImageView
|
||||||
|
var footer: Stub<ConversationItemFooter>
|
||||||
|
private set
|
||||||
|
private var cornerMask: CornerMask
|
||||||
|
private var borderless = false
|
||||||
|
private var normalBounds: IntArray
|
||||||
|
private var gifBounds: IntArray
|
||||||
|
private var minimumThumbnailWidth = 0
|
||||||
|
private var maximumThumbnailHeight = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.conversation_item_thumbnail, this)
|
||||||
|
|
||||||
|
thumbnail = Stub(findViewById(R.id.thumbnail_view_stub))
|
||||||
|
album = Stub(findViewById(R.id.album_view_stub))
|
||||||
|
shade = findViewById(R.id.conversation_thumbnail_shade)
|
||||||
|
footer = Stub(findViewById(R.id.footer_view_stub))
|
||||||
|
cornerMask = CornerMask(this)
|
||||||
|
|
||||||
|
var gifWidth = 260.dp
|
||||||
|
|
||||||
|
if (attrs != null) {
|
||||||
|
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0)
|
||||||
|
normalBounds = intArrayOf(
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
gifWidth = typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_gifWidth, gifWidth)
|
||||||
|
|
||||||
|
typedArray.recycle()
|
||||||
|
} else {
|
||||||
|
normalBounds = intArrayOf(0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
gifBounds = intArrayOf(
|
||||||
|
gifWidth,
|
||||||
|
gifWidth,
|
||||||
|
1,
|
||||||
|
Int.MAX_VALUE
|
||||||
|
)
|
||||||
|
|
||||||
|
minimumThumbnailWidth = -1
|
||||||
|
maximumThumbnailHeight = -1
|
||||||
|
|
||||||
|
state = ConversationItemThumbnailState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchDraw(canvas: Canvas) {
|
||||||
|
super.dispatchDraw(canvas)
|
||||||
|
if (!borderless) {
|
||||||
|
cornerMask.mask(canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(): Parcelable? {
|
||||||
|
val root = super.onSaveInstanceState()
|
||||||
|
return bundleOf(
|
||||||
|
STATE_ROOT to root,
|
||||||
|
STATE_STATE to state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(state: Parcelable) {
|
||||||
|
if (state is Bundle && state.containsKey(STATE_STATE)) {
|
||||||
|
val root = state.getParcelable<Parcelable>(STATE_ROOT)
|
||||||
|
this.state = state.getParcelable(STATE_STATE)!!
|
||||||
|
super.onRestoreInstanceState(root)
|
||||||
|
} else {
|
||||||
|
super.onRestoreInstanceState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFocusable(focusable: Boolean) {
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(focusable = focusable),
|
||||||
|
albumViewState = state.albumViewState.copy(focusable = focusable)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setClickable(clickable: Boolean) {
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(clickable = clickable),
|
||||||
|
albumViewState = state.albumViewState.copy(clickable = clickable)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnLongClickListener(l: OnLongClickListener?) {
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(longClickListener = l),
|
||||||
|
albumViewState = state.albumViewState.copy(longClickListener = l)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideThumbnailView() {
|
||||||
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 0f))
|
||||||
|
state.thumbnailViewState.applyState(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showThumbnailView() {
|
||||||
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 1f))
|
||||||
|
state.thumbnailViewState.applyState(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
val corners: Corners
|
||||||
|
get() = Corners(cornerMask.radii)
|
||||||
|
|
||||||
|
fun showShade(show: Boolean) {
|
||||||
|
shade.visibility = if (show) VISIBLE else GONE
|
||||||
|
forceLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCorners(topLeft: Int, topRight: Int, bottomRight: Int, bottomLeft: Int) {
|
||||||
|
cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMinimumThumbnailWidth(@Px width: Int) {
|
||||||
|
minimumThumbnailWidth = width
|
||||||
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(minimumThumbnailWidth = width))
|
||||||
|
state.thumbnailViewState.applyState(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMaximumThumbnailHeight(@Px height: Int) {
|
||||||
|
maximumThumbnailHeight = height
|
||||||
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(maximumThumbnailHeight = height))
|
||||||
|
state.thumbnailViewState.applyState(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBorderless(borderless: Boolean) {
|
||||||
|
this.borderless = borderless
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
fun setImageResource(
|
||||||
|
glideRequests: GlideRequests,
|
||||||
|
slides: List<Slide>,
|
||||||
|
showControls: Boolean,
|
||||||
|
isPreview: Boolean
|
||||||
|
) {
|
||||||
|
if (slides.size == 1) {
|
||||||
|
val slide = slides[0]
|
||||||
|
|
||||||
|
if (slide.isVideoGif) {
|
||||||
|
setThumbnailBounds(gifBounds)
|
||||||
|
} else {
|
||||||
|
setThumbnailBounds(normalBounds)
|
||||||
|
|
||||||
|
if (minimumThumbnailWidth != -1) {
|
||||||
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(minimumThumbnailWidth = minimumThumbnailWidth))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maximumThumbnailHeight != -1) {
|
||||||
|
state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(maximumThumbnailHeight = maximumThumbnailHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(visibility = VISIBLE),
|
||||||
|
albumViewState = state.albumViewState.copy(visibility = GONE)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
|
||||||
|
val attachment = slides[0].asAttachment()
|
||||||
|
|
||||||
|
thumbnail.get().setImageResource(glideRequests, slides[0], showControls, isPreview, attachment.width, attachment.height)
|
||||||
|
touchDelegate = thumbnail.get().touchDelegate
|
||||||
|
} else {
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(visibility = GONE),
|
||||||
|
albumViewState = state.albumViewState.copy(visibility = VISIBLE)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
album.get().setSlides(glideRequests, slides, showControls)
|
||||||
|
touchDelegate = album.get().touchDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setConversationColor(@ColorInt color: Int) {
|
||||||
|
state = state.copy(albumViewState = state.albumViewState.copy(cellBackgroundColor = color))
|
||||||
|
state.albumViewState.applyState(album)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setThumbnailClickListener(listener: SlideClickListener?) {
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(clickListener = listener),
|
||||||
|
albumViewState = state.albumViewState.copy(clickListener = listener)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDownloadClickListener(listener: SlidesClickedListener?) {
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(downloadClickListener = listener),
|
||||||
|
albumViewState = state.albumViewState.copy(downloadClickListener = listener)
|
||||||
|
)
|
||||||
|
|
||||||
|
state.applyState(thumbnail, album)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setThumbnailBounds(bounds: IntArray) {
|
||||||
|
val (minWidth, maxWidth, minHeight, maxHeight) = bounds
|
||||||
|
state = state.copy(
|
||||||
|
thumbnailViewState = state.thumbnailViewState.copy(
|
||||||
|
minWidth = minWidth,
|
||||||
|
maxWidth = maxWidth,
|
||||||
|
minHeight = minHeight,
|
||||||
|
maxHeight = maxHeight
|
||||||
|
)
|
||||||
|
)
|
||||||
|
state.thumbnailViewState.applyState(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STATE_ROOT = "state.root"
|
||||||
|
private const val STATE_STATE = "state.state"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.OnLongClickListener
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.thoughtcrime.securesms.mms.SlideClickListener
|
||||||
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener
|
||||||
|
import org.thoughtcrime.securesms.util.views.Stub
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parcelizable state object for [ConversationItemThumbnail]
|
||||||
|
* This allows us to manage inputs for [ThumbnailView] and [AlbumThumbnailView] without
|
||||||
|
* actually having them inflated. When the views are finally inflated, we 'apply'
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class ConversationItemThumbnailState(
|
||||||
|
val thumbnailViewState: ThumbnailViewState = ThumbnailViewState(),
|
||||||
|
val albumViewState: AlbumViewState = AlbumViewState()
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ThumbnailViewState(
|
||||||
|
private val alpha: Float = 0f,
|
||||||
|
private val focusable: Boolean = true,
|
||||||
|
private val clickable: Boolean = true,
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val clickListener: SlideClickListener? = null,
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val downloadClickListener: SlidesClickedListener? = null,
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val longClickListener: OnLongClickListener? = null,
|
||||||
|
private val minimumThumbnailWidth: Int = -1,
|
||||||
|
private val maximumThumbnailHeight: Int = -1,
|
||||||
|
private val visibility: Int = View.GONE,
|
||||||
|
private val minWidth: Int = -1,
|
||||||
|
private val maxWidth: Int = -1,
|
||||||
|
private val minHeight: Int = -1,
|
||||||
|
private val maxHeight: Int = -1
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun applyState(thumbnailView: Stub<ThumbnailView>) {
|
||||||
|
thumbnailView.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailView.get().alpha = alpha
|
||||||
|
thumbnailView.get().isFocusable = focusable
|
||||||
|
thumbnailView.get().isClickable = clickable
|
||||||
|
thumbnailView.get().setThumbnailClickListener(clickListener)
|
||||||
|
thumbnailView.get().setDownloadClickListener(downloadClickListener)
|
||||||
|
thumbnailView.get().setOnLongClickListener(longClickListener)
|
||||||
|
thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight)
|
||||||
|
thumbnailView.get().setMinimumThumbnailWidth(minimumThumbnailWidth)
|
||||||
|
thumbnailView.get().setMaximumThumbnailHeight(maximumThumbnailHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AlbumViewState(
|
||||||
|
private val focusable: Boolean = true,
|
||||||
|
private val clickable: Boolean = true,
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val clickListener: SlideClickListener? = null,
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val downloadClickListener: SlidesClickedListener? = null,
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val longClickListener: OnLongClickListener? = null,
|
||||||
|
private val visibility: Int = View.GONE,
|
||||||
|
private val cellBackgroundColor: Int = Color.TRANSPARENT
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun applyState(albumView: Stub<AlbumThumbnailView>) {
|
||||||
|
albumView.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
albumView.get().isFocusable = focusable
|
||||||
|
albumView.get().isClickable = clickable
|
||||||
|
albumView.get().setThumbnailClickListener(clickListener)
|
||||||
|
albumView.get().setDownloadClickListener(downloadClickListener)
|
||||||
|
albumView.get().setOnLongClickListener(longClickListener)
|
||||||
|
albumView.get().setCellBackgroundColor(cellBackgroundColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyState(thumbnailView: Stub<ThumbnailView>, albumView: Stub<AlbumThumbnailView>) {
|
||||||
|
thumbnailViewState.applyState(thumbnailView)
|
||||||
|
albumViewState.applyState(albumView)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1656,7 +1656,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||||
footer.setVisibility(GONE);
|
footer.setVisibility(GONE);
|
||||||
ViewUtil.setVisibilityIfNonNull(stickerFooter, GONE);
|
ViewUtil.setVisibilityIfNonNull(stickerFooter, GONE);
|
||||||
if (sharedContactStub.resolved()) sharedContactStub.get().getFooter().setVisibility(GONE);
|
if (sharedContactStub.resolved()) sharedContactStub.get().getFooter().setVisibility(GONE);
|
||||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().getFooter().setVisibility(GONE);
|
if (mediaThumbnailStub.resolved() && mediaThumbnailStub.require().getFooter().resolved()) {
|
||||||
|
mediaThumbnailStub.require().getFooter().setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
if (isFooterVisible(current, next, isGroupThread)) {
|
if (isFooterVisible(current, next, isGroupThread)) {
|
||||||
ConversationItemFooter activeFooter = getActiveFooter(current);
|
ConversationItemFooter activeFooter = getActiveFooter(current);
|
||||||
|
@ -1741,7 +1743,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||||
} else if (hasSharedContact(messageRecord) && messageRecord.isDisplayBodyEmpty(getContext())) {
|
} else if (hasSharedContact(messageRecord) && messageRecord.isDisplayBodyEmpty(getContext())) {
|
||||||
return sharedContactStub.get().getFooter();
|
return sharedContactStub.get().getFooter();
|
||||||
} else if (hasOnlyThumbnail(messageRecord) && messageRecord.isDisplayBodyEmpty(getContext())) {
|
} else if (hasOnlyThumbnail(messageRecord) && messageRecord.isDisplayBodyEmpty(getContext())) {
|
||||||
return mediaThumbnailStub.require().getFooter();
|
return mediaThumbnailStub.require().getFooter().get();
|
||||||
} else {
|
} else {
|
||||||
return footer;
|
return footer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class ConversationViewModel extends ViewModel {
|
||||||
this.recipientId = BehaviorSubject.create();
|
this.recipientId = BehaviorSubject.create();
|
||||||
this.threadId = BehaviorSubject.create();
|
this.threadId = BehaviorSubject.create();
|
||||||
this.groupAuthorNameColorHelper = new GroupAuthorNameColorHelper();
|
this.groupAuthorNameColorHelper = new GroupAuthorNameColorHelper();
|
||||||
this.conversationStateStore = new RxStore<>(ConversationState.create(), Schedulers.io());
|
this.conversationStateStore = new RxStore<>(ConversationState.create(), Schedulers.computation());
|
||||||
this.disposables = new CompositeDisposable();
|
this.disposables = new CompositeDisposable();
|
||||||
this.conversationStateTick = BehaviorSubject.createDefault(Unit.INSTANCE);
|
this.conversationStateTick = BehaviorSubject.createDefault(Unit.INSTANCE);
|
||||||
this.markReadRequestPublisher = PublishProcessor.create();
|
this.markReadRequestPublisher = PublishProcessor.create();
|
||||||
|
|
|
@ -2,30 +2,19 @@
|
||||||
<merge
|
<merge
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
tools:viewBindingIgnore="true"
|
tools:viewBindingIgnore="true"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
<ViewStub
|
||||||
android:id="@+id/conversation_thumbnail_image"
|
android:id="@+id/thumbnail_view_stub"
|
||||||
android:layout_width="@dimen/media_bubble_default_dimens"
|
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||||
android:layout_height="@dimen/media_bubble_default_dimens"
|
android:layout_height="@dimen/media_bubble_default_dimens"
|
||||||
android:adjustViewBounds="true"
|
android:layout="@layout/conversation_item_thumbnail_thumbnail_view_stub" />
|
||||||
android:clickable="false"
|
|
||||||
android:longClickable="false"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"
|
|
||||||
app:thumbnail_radius="1dp"/>
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AlbumThumbnailView
|
<ViewStub
|
||||||
android:id="@+id/conversation_thumbnail_album"
|
android:id="@+id/album_view_stub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clickable="false"
|
android:layout="@layout/conversation_item_thumbnail_album_thumbnail_view_stub" />
|
||||||
android:longClickable="false"
|
|
||||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/conversation_thumbnail_shade"
|
android:id="@+id/conversation_thumbnail_shade"
|
||||||
|
@ -35,17 +24,14 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:src="@drawable/image_shade" />
|
android:src="@drawable/image_shade" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.ConversationItemFooter
|
<ViewStub
|
||||||
android:id="@+id/conversation_thumbnail_footer"
|
android:id="@+id/footer_view_stub"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||||
app:footer_mode="outgoing"
|
android:layout="@layout/conversation_item_thumbnail_footer_view_stub" />
|
||||||
app:footer_text_color="@color/signal_text_toolbar_subtitle"
|
|
||||||
app:footer_reveal_dot_color="@color/signal_text_toolbar_subtitle"
|
|
||||||
app:footer_icon_color="@color/signal_text_toolbar_subtitle"/>
|
|
||||||
|
|
||||||
</merge>
|
</merge>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.components.AlbumThumbnailView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/conversation_thumbnail_album"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="false"
|
||||||
|
android:longClickable="false"
|
||||||
|
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:showIn="@layout/conversation_item_thumbnail" />
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.components.ConversationItemFooter xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/conversation_thumbnail_footer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginStart="@dimen/message_bubble_horizontal_padding"
|
||||||
|
android:layout_marginEnd="@dimen/message_bubble_horizontal_padding"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_bottom_padding"
|
||||||
|
app:footer_mode="outgoing"
|
||||||
|
app:footer_text_color="@color/signal_text_toolbar_subtitle"
|
||||||
|
app:footer_reveal_dot_color="@color/signal_text_toolbar_subtitle"
|
||||||
|
app:footer_icon_color="@color/signal_text_toolbar_subtitle"
|
||||||
|
tools:showIn="@layout/conversation_item_thumbnail" />
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.thoughtcrime.securesms.components.ThumbnailView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/conversation_thumbnail_image"
|
||||||
|
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||||
|
android:layout_height="@dimen/media_bubble_default_dimens"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:clickable="false"
|
||||||
|
android:longClickable="false"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
app:thumbnail_radius="1dp"
|
||||||
|
tools:showIn="@layout/conversation_item_thumbnail" />
|
Loading…
Add table
Reference in a new issue