Add stubbing to ConversationThumbnailView and caching to a typeface.

main
Alex Hart 2023-02-10 13:22:46 -04:00 zatwierdzone przez Greyson Parrelli
rodzic ffbebe0670
commit dda5037429
10 zmienionych plików z 417 dodań i 248 usunięć

Wyświetl plik

@ -6,6 +6,7 @@ import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.net.Uri
import androidx.annotation.MainThread
import androidx.appcompat.content.res.AppCompatResources
import com.airbnb.lottie.SimpleColorFilter
import org.signal.core.util.concurrent.SignalExecutors
@ -28,8 +29,13 @@ object AvatarRenderer {
val DIMENSIONS = AvatarHelper.AVATAR_DIMENSIONS
private var typeface: Typeface? = null
@MainThread
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) {

Wyświetl plik

@ -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]);
}
}

Wyświetl plik

@ -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"
}
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -1656,7 +1656,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
footer.setVisibility(GONE);
ViewUtil.setVisibilityIfNonNull(stickerFooter, 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)) {
ConversationItemFooter activeFooter = getActiveFooter(current);
@ -1741,7 +1743,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
} else if (hasSharedContact(messageRecord) && messageRecord.isDisplayBodyEmpty(getContext())) {
return sharedContactStub.get().getFooter();
} else if (hasOnlyThumbnail(messageRecord) && messageRecord.isDisplayBodyEmpty(getContext())) {
return mediaThumbnailStub.require().getFooter();
return mediaThumbnailStub.require().getFooter().get();
} else {
return footer;
}

Wyświetl plik

@ -127,7 +127,7 @@ public class ConversationViewModel extends ViewModel {
this.recipientId = BehaviorSubject.create();
this.threadId = BehaviorSubject.create();
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.conversationStateTick = BehaviorSubject.createDefault(Unit.INSTANCE);
this.markReadRequestPublisher = PublishProcessor.create();

Wyświetl plik

@ -2,30 +2,19 @@
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<org.thoughtcrime.securesms.components.ThumbnailView
android:id="@+id/conversation_thumbnail_image"
<ViewStub
android:id="@+id/thumbnail_view_stub"
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"/>
android:layout="@layout/conversation_item_thumbnail_thumbnail_view_stub" />
<org.thoughtcrime.securesms.components.AlbumThumbnailView
android:id="@+id/conversation_thumbnail_album"
<ViewStub
android:id="@+id/album_view_stub"
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"/>
android:layout="@layout/conversation_item_thumbnail_album_thumbnail_view_stub" />
<ImageView
android:id="@+id/conversation_thumbnail_shade"
@ -35,17 +24,14 @@
android:visibility="gone"
android:src="@drawable/image_shade" />
<org.thoughtcrime.securesms.components.ConversationItemFooter
android:id="@+id/conversation_thumbnail_footer"
<ViewStub
android:id="@+id/footer_view_stub"
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"/>
android:layout="@layout/conversation_item_thumbnail_footer_view_stub" />
</merge>

Wyświetl plik

@ -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" />

Wyświetl plik

@ -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" />

Wyświetl plik

@ -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" />