diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt index da9cbb4de..5cf4cbb61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java deleted file mode 100644 index 439b4d96b..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java +++ /dev/null @@ -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 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]); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt new file mode 100644 index 000000000..af42f32a4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt @@ -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 + private var album: Stub + private var shade: ImageView + var footer: Stub + 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(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, + 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" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt new file mode 100644 index 000000000..f24823465 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt @@ -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.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) { + 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, albumView: Stub) { + thumbnailViewState.applyState(thumbnailView) + albumViewState.applyState(albumView) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index aa84e8f65..d76d2bc9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java index 2369c7609..6c1ce3558 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java @@ -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(); diff --git a/app/src/main/res/layout/conversation_item_thumbnail.xml b/app/src/main/res/layout/conversation_item_thumbnail.xml index 577302bff..9d51bee8b 100644 --- a/app/src/main/res/layout/conversation_item_thumbnail.xml +++ b/app/src/main/res/layout/conversation_item_thumbnail.xml @@ -2,30 +2,19 @@ - + android:layout="@layout/conversation_item_thumbnail_thumbnail_view_stub" /> - + android:layout="@layout/conversation_item_thumbnail_album_thumbnail_view_stub" /> - + android:layout="@layout/conversation_item_thumbnail_footer_view_stub" /> diff --git a/app/src/main/res/layout/conversation_item_thumbnail_album_thumbnail_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_album_thumbnail_view_stub.xml new file mode 100644 index 000000000..ebf493abc --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thumbnail_album_thumbnail_view_stub.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_thumbnail_footer_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_footer_view_stub.xml new file mode 100644 index 000000000..6ba700929 --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thumbnail_footer_view_stub.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml new file mode 100644 index 000000000..09568d08b --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file