Align quote behaviour, move the media message outside of text bubble to simplify layouts (#935)

* refactor: remove text from quote model

* refactor: add docs for TODOs where quote text should be refactored

* refactor: remove the references to stored text in the quote and get the quote text from referenced DB lookup

* refactor: drop the quote data from DB

* fix: turns out we can't drop columns using this version of sqlite

* fix: fixing an attachment download bug, fixing up UI issues with quotes and body text

* feat: split off the message attachment UI from message bubble

* refactor: replace media thumbnails with new designs

* refactor: add debug drawing to troubleshoot swipe gesture

* fix: fix the swipe to reply gesture drawing
pull/944/head
Harris 3 years ago committed by GitHub
parent b1e954084c
commit d53752713e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,153 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.session.libsession.utilities.Stub;
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView;
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 java.util.List;
import network.loki.messenger.R;
public class AlbumThumbnailView extends FrameLayout {
private @Nullable SlideClickListener thumbnailClickListener;
private @Nullable SlidesClickedListener downloadClickListener;
private int currentSizeClass;
private ViewGroup albumCellContainer;
private Stub<TransferControlView> transferControls;
private final SlideClickListener defaultThumbnailClickListener = (v, slide) -> {
if (thumbnailClickListener != null) {
thumbnailClickListener.onClick(v, slide);
}
};
private final OnLongClickListener defaultLongClickListener = v -> this.performLongClick();
public AlbumThumbnailView(@NonNull Context context) {
super(context);
initialize();
}
public AlbumThumbnailView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.album_thumbnail_view, this);
albumCellContainer = findViewById(R.id.albumCellContainer);
transferControls = new Stub<>(findViewById(R.id.albumTransferControlsStub));
}
public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides, boolean showControls) {
if (slides.size() < 2) {
throw new IllegalStateException("Provided less than two slides.");
}
if (showControls) {
transferControls.get().setShowDownloadText(true);
transferControls.get().setSlides(slides);
transferControls.get().setDownloadClickListener(v -> {
if (downloadClickListener != null) {
downloadClickListener.onClick(v, slides);
}
});
} else {
if (transferControls.resolved()) {
transferControls.get().setVisibility(GONE);
}
}
int sizeClass = Math.min(slides.size(), 6);
if (sizeClass != currentSizeClass) {
inflateLayout(sizeClass);
currentSizeClass = sizeClass;
}
showSlides(glideRequests, slides);
}
public void setCellBackgroundColor(@ColorInt int color) {
ViewGroup cellRoot = findViewById(R.id.album_thumbnail_root);
if (cellRoot != null) {
for (int i = 0; i < cellRoot.getChildCount(); i++) {
cellRoot.getChildAt(i).setBackgroundColor(color);
}
}
}
public void setThumbnailClickListener(@Nullable SlideClickListener listener) {
thumbnailClickListener = listener;
}
public void setDownloadClickListener(@Nullable SlidesClickedListener listener) {
downloadClickListener = listener;
}
private void inflateLayout(int sizeClass) {
albumCellContainer.removeAllViews();
switch (sizeClass) {
case 2:
inflate(getContext(), R.layout.album_thumbnail_2, albumCellContainer);
break;
case 3:
inflate(getContext(), R.layout.album_thumbnail_3, albumCellContainer);
break;
case 4:
inflate(getContext(), R.layout.album_thumbnail_4, albumCellContainer);
break;
case 5:
inflate(getContext(), R.layout.album_thumbnail_5, albumCellContainer);
break;
default:
inflate(getContext(), R.layout.album_thumbnail_many, albumCellContainer);
break;
}
}
private void showSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides) {
setSlide(glideRequests, slides.get(0), R.id.album_cell_1);
setSlide(glideRequests, slides.get(1), R.id.album_cell_2);
if (slides.size() >= 3) {
setSlide(glideRequests, slides.get(2), R.id.album_cell_3);
}
if (slides.size() >= 4) {
setSlide(glideRequests, slides.get(3), R.id.album_cell_4);
}
if (slides.size() >= 5) {
setSlide(glideRequests, slides.get(4), R.id.album_cell_5);
}
if (slides.size() > 5) {
TextView text = findViewById(R.id.album_cell_overflow_text);
text.setText(getContext().getString(R.string.AlbumThumbnailView_plus, slides.size() - 5));
}
}
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id) {
}
}

@ -1,158 +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.UiThread;
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
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.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.utilities.ThemeUtil;
import java.util.List;
import network.loki.messenger.R;
public class ConversationItemThumbnail extends FrameLayout {
private ThumbnailView thumbnail;
private AlbumThumbnailView album;
private ImageView shade;
private ConversationItemFooter footer;
private CornerMask cornerMask;
private Outliner outliner;
private boolean borderless;
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);
this.outliner = new Outliner();
outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_image_outline_color));
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
typedArray.recycle();
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!borderless) {
cornerMask.mask(canvas);
if (album.getVisibility() != VISIBLE) {
outliner.draw(canvas);
}
}
}
@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);
outliner.setRadii(topLeft, topRight, bottomRight, bottomLeft);
}
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) {
thumbnail.setVisibility(VISIBLE);
album.setVisibility(GONE);
Slide slide = slides.get(0);
Attachment attachment = slide.asAttachment();
thumbnail.setImageResource(glideRequests, slide, showControls, isPreview, attachment.getWidth(), attachment.getHeight());
thumbnail.setLoadIndicatorVisibile(slide.isInProgress());
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);
}
}

@ -31,7 +31,7 @@ class AlbumThumbnailView : FrameLayout {
private lateinit var binding: AlbumThumbnailViewBinding private lateinit var binding: AlbumThumbnailViewBinding
companion object { companion object {
const val MAX_ALBUM_DISPLAY_SIZE = 5 const val MAX_ALBUM_DISPLAY_SIZE = 3
} }
// region Lifecycle // region Lifecycle
@ -130,18 +130,13 @@ class AlbumThumbnailView : FrameLayout {
fun layoutRes(slideCount: Int) = when (slideCount) { fun layoutRes(slideCount: Int) = when (slideCount) {
1 -> R.layout.album_thumbnail_1 // single 1 -> R.layout.album_thumbnail_1 // single
2 -> R.layout.album_thumbnail_2// two sidebyside 2 -> R.layout.album_thumbnail_2// two sidebyside
3 -> R.layout.album_thumbnail_3// three stacked else -> R.layout.album_thumbnail_3 // three stacked with additional text
4 -> R.layout.album_thumbnail_4// four square
5 -> R.layout.album_thumbnail_5//
else -> R.layout.album_thumbnail_many// five or more
} }
fun getThumbnailView(position: Int): KThumbnailView = when (position) { fun getThumbnailView(position: Int): KThumbnailView = when (position) {
0 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_1) 0 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_1)
1 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_2) 1 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_2)
2 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_3) 2 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_3)
3 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_4)
4 -> binding.albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_5)
else -> throw Exception("Can't get thumbnail view for non-existent thumbnail at position: $position") else -> throw Exception("Can't get thumbnail view for non-existent thumbnail at position: $position")
} }

@ -67,18 +67,11 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient, fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient,
isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long, isOutgoingMessage: Boolean, isOpenGroupInvitation: Boolean, threadID: Long,
isOriginalMissing: Boolean, glide: GlideRequests) { isOriginalMissing: Boolean, glide: GlideRequests) {
// Reduce the max body text view line count to 2 if this is a group thread because
// we'll be showing the author text view and we don't want the overall quote view height
// to get too big.
binding.quoteViewBodyTextView.maxLines = if (thread.isGroupRecipient) 2 else 3
// Author // Author
if (thread.isGroupRecipient) {
val author = contactDb.getContactWithSessionID(authorPublicKey) val author = contactDb.getContactWithSessionID(authorPublicKey)
val authorDisplayName = author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}" val authorDisplayName = author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}"
binding.quoteViewAuthorTextView.text = authorDisplayName binding.quoteViewAuthorTextView.text = authorDisplayName
binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage)) binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage))
}
binding.quoteViewAuthorTextView.isVisible = thread.isGroupRecipient
// Body // Body
binding.quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else MentionUtilities.highlightMentions((body ?: "").toSpannable(), threadID, context) binding.quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else MentionUtilities.highlightMentions((body ?: "").toSpannable(), threadID, context)
binding.quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage)) binding.quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage))

@ -13,11 +13,11 @@ import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat import androidx.core.graphics.BlendModeCompat
@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.SearchUtil
import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.util.getColorWithID import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.toPx
import java.util.Locale import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -76,7 +75,7 @@ class VisibleMessageContentView : LinearLayout {
val color = ThemeUtil.getThemedColor(context, colorID) val color = ThemeUtil.getThemedColor(context, colorID)
val filter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN) val filter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN)
background.colorFilter = filter background.colorFilter = filter
setBackground(background) binding.contentParent.background = background
val onlyBodyMessage = message is SmsMessageRecord val onlyBodyMessage = message is SmsMessageRecord
val mediaThumbnailMessage = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.thumbnailSlide != null val mediaThumbnailMessage = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.thumbnailSlide != null
@ -94,17 +93,13 @@ class VisibleMessageContentView : LinearLayout {
binding.deletedMessageView.root.isVisible = false binding.deletedMessageView.root.isVisible = false
} }
// clear the // clear the
binding.bodyTextView.text = null binding.bodyTextView.text = ""
binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null
binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty()
val linkPreviewLayout = binding.linkPreviewView.layoutParams
linkPreviewLayout.width = if (mediaThumbnailMessage) 0 else ViewGroup.LayoutParams.WRAP_CONTENT
binding.linkPreviewView.layoutParams = linkPreviewLayout
binding.untrustedView.root.isVisible = !contactIsTrusted && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty() binding.untrustedView.root.isVisible = !contactIsTrusted && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty()
binding.voiceMessageView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.audioSlide != null binding.voiceMessageView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.audioSlide != null
binding.documentView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.documentSlide != null binding.documentView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.documentSlide != null
@ -131,9 +126,7 @@ class VisibleMessageContentView : LinearLayout {
delegate?.scrollToMessageIfPossible(quote.id) delegate?.scrollToMessageIfPossible(quote.id)
} }
} }
val layoutParams = binding.quoteView.root.layoutParams as MarginLayoutParams
val hasMedia = message.slideDeck.asAttachments().isNotEmpty() val hasMedia = message.slideDeck.asAttachments().isNotEmpty()
binding.quoteView.root.minWidth = if (hasMedia) 0 else toPx(300,context.resources)
} }
if (message is MmsMessageRecord) { if (message is MmsMessageRecord) {
@ -198,6 +191,9 @@ class VisibleMessageContentView : LinearLayout {
isStart = isStartOfMessageCluster, isStart = isStartOfMessageCluster,
isEnd = isEndOfMessageCluster isEnd = isEndOfMessageCluster
) )
val layoutParams = binding.albumThumbnailView.layoutParams as ConstraintLayout.LayoutParams
layoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
binding.albumThumbnailView.layoutParams = layoutParams
onContentClick.add { event -> onContentClick.add { event ->
binding.albumThumbnailView.calculateHitObject(event, message, thread) binding.albumThumbnailView.calculateHitObject(event, message, thread)
} }
@ -215,11 +211,6 @@ class VisibleMessageContentView : LinearLayout {
binding.bodyTextView.isVisible = message.body.isNotEmpty() && !hideBody binding.bodyTextView.isVisible = message.body.isNotEmpty() && !hideBody
// set it to use constraints if not only a text message, otherwise wrap content to whatever width it wants
val params = binding.bodyTextView.layoutParams
params.width = if (onlyBodyMessage || binding.barrierViewsGone()) ViewGroup.LayoutParams.MATCH_PARENT else 0
binding.bodyTextView.layoutParams = params
if (message.body.isNotEmpty() && !hideBody) { if (message.body.isNotEmpty() && !hideBody) {
val color = getTextColor(context, message) val color = getTextColor(context, message)
binding.bodyTextView.setTextColor(color) binding.bodyTextView.setTextColor(color)
@ -232,6 +223,9 @@ class VisibleMessageContentView : LinearLayout {
} }
} }
} }
val layoutParams = binding.contentParent.layoutParams as ConstraintLayout.LayoutParams
layoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f
binding.contentParent.layoutParams = layoutParams
} }
private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean = private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean =

@ -17,7 +17,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageBinding import network.loki.messenger.databinding.ViewVisibleMessageBinding
@ -41,6 +43,7 @@ import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.toDp import org.thoughtcrime.securesms.util.toDp
import org.thoughtcrime.securesms.util.toPx import org.thoughtcrime.securesms.util.toPx
import java.util.Date import java.util.Date
import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@ -152,18 +155,15 @@ class VisibleMessageView : LinearLayout {
binding.moderatorIconImageView.isVisible = !message.isOutgoing && isModerator binding.moderatorIconImageView.isVisible = !message.isOutgoing && isModerator
} }
} }
binding.senderNameTextView.isVisible = isStartOfMessageCluster
val context =
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
binding.senderNameTextView.text = contact?.displayName(context) ?: senderSessionID
} else {
binding.senderNameTextView.visibility = View.GONE
} }
binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected))
val contactContext =
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
// Date break // Date break
binding.dateBreakTextView.showDateBreak(message, previous) val showDateBreak = isStartOfMessageCluster || snIsSelected
// Timestamp binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
// binding.messageTimestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) binding.dateBreakTextView.isVisible = showDateBreak
// Set inter-message spacing
// Message status indicator // Message status indicator
val (iconID, iconColor) = getMessageStatusImage(message) val (iconID, iconColor) = getMessageStatusImage(message)
if (iconID != null) { if (iconID != null) {
@ -242,7 +242,7 @@ class VisibleMessageView : LinearLayout {
container.layoutParams = containerParams container.layoutParams = containerParams
if (message.expiresIn > 0 && !message.isPending) { if (message.expiresIn > 0 && !message.isPending) {
binding.expirationTimerView.setColorFilter(ResourcesCompat.getColor(resources, R.color.text, context.theme)) binding.expirationTimerView.setColorFilter(ResourcesCompat.getColor(resources, R.color.text, context.theme))
binding.expirationTimerView.isVisible = true binding.expirationTimerView.isInvisible = false
binding.expirationTimerView.setPercentComplete(0.0f) binding.expirationTimerView.setPercentComplete(0.0f)
if (message.expireStarted > 0) { if (message.expireStarted > 0) {
binding.expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) binding.expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn)
@ -265,7 +265,7 @@ class VisibleMessageView : LinearLayout {
binding.expirationTimerView.setPercentComplete(0.0f) binding.expirationTimerView.setPercentComplete(0.0f)
} }
} else { } else {
binding.expirationTimerView.isVisible = false binding.expirationTimerView.isInvisible = true
} }
container.requestLayout() container.requestLayout()
} }
@ -279,15 +279,19 @@ class VisibleMessageView : LinearLayout {
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
if (translationX < 0 && !binding.expirationTimerView.isVisible) {
val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing) val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing)
val threshold = swipeToReplyThreshold
val iconSize = toPx(24, context.resources) val iconSize = toPx(24, context.resources)
val bottomVOffset = paddingBottom + binding.messageStatusImageView.height + (binding.messageContentView.height - iconSize) / 2 val left = binding.expirationTimerViewContainer.left + binding.messageContentView.right + spacing
swipeToReplyIconRect.left = binding.messageContentView.right - binding.messageContentView.paddingEnd + spacing val top = height - (binding.expirationTimerViewContainer.height / 2) - binding.profilePictureView.root.marginBottom - (iconSize / 2)
swipeToReplyIconRect.top = height - bottomVOffset - iconSize val right = left + iconSize
swipeToReplyIconRect.right = binding.messageContentView.right - binding.messageContentView.paddingEnd + iconSize + spacing val bottom = top + iconSize
swipeToReplyIconRect.bottom = height - bottomVOffset swipeToReplyIconRect.left = left
swipeToReplyIconRect.top = top
swipeToReplyIconRect.right = right
swipeToReplyIconRect.bottom = bottom
if (translationX < 0 && !binding.expirationTimerView.isVisible) {
val threshold = swipeToReplyThreshold
swipeToReplyIcon.bounds = swipeToReplyIconRect swipeToReplyIcon.bounds = swipeToReplyIconRect
swipeToReplyIcon.alpha = (255.0f * (min(abs(translationX), threshold) / threshold)).roundToInt() swipeToReplyIcon.alpha = (255.0f * (min(abs(translationX), threshold) / threshold)).roundToInt()
} else { } else {

@ -62,7 +62,6 @@ import org.thoughtcrime.securesms.database.SmsDatabase.InsertListener
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord
import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get
@ -479,7 +478,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
) )
val quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID)) val quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID))
val quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR)) val quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR))
val quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY)) val quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY)) // TODO: this should be the referenced quote
val quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE_MISSING)) == 1 val quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE_MISSING)) == 1
val quoteAttachments = associatedAttachments val quoteAttachments = associatedAttachments
.filter { obj: DatabaseAttachment -> obj.isQuote } .filter { obj: DatabaseAttachment -> obj.isQuote }
@ -502,7 +501,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
quote = QuoteModel( quote = QuoteModel(
quoteId, quoteId,
fromSerialized(quoteAuthor), fromSerialized(quoteAuthor),
quoteText, quoteText, // TODO: refactor this to use referenced quote
quoteMissing, quoteMissing,
quoteAttachments quoteAttachments
) )
@ -669,7 +668,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
var quoteAttachments: List<Attachment?>? = LinkedList() var quoteAttachments: List<Attachment?>? = LinkedList()
if (retrieved.quote != null) { if (retrieved.quote != null) {
contentValues.put(QUOTE_ID, retrieved.quote.id) contentValues.put(QUOTE_ID, retrieved.quote.id)
contentValues.put(QUOTE_BODY, retrieved.quote.text)
contentValues.put(QUOTE_AUTHOR, retrieved.quote.author.serialize()) contentValues.put(QUOTE_AUTHOR, retrieved.quote.author.serialize())
contentValues.put(QUOTE_MISSING, if (retrieved.quote.missing) 1 else 0) contentValues.put(QUOTE_MISSING, if (retrieved.quote.missing) 1 else 0)
quoteAttachments = retrieved.quote.attachments quoteAttachments = retrieved.quote.attachments
@ -816,7 +814,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
if (message.outgoingQuote != null) { if (message.outgoingQuote != null) {
contentValues.put(QUOTE_ID, message.outgoingQuote!!.id) contentValues.put(QUOTE_ID, message.outgoingQuote!!.id)
contentValues.put(QUOTE_AUTHOR, message.outgoingQuote!!.author.serialize()) contentValues.put(QUOTE_AUTHOR, message.outgoingQuote!!.author.serialize())
contentValues.put(QUOTE_BODY, message.outgoingQuote!!.text)
contentValues.put(QUOTE_MISSING, if (message.outgoingQuote!!.missing) 1 else 0) contentValues.put(QUOTE_MISSING, if (message.outgoingQuote!!.missing) 1 else 0)
quoteAttachments.addAll(message.outgoingQuote!!.attachments!!) quoteAttachments.addAll(message.outgoingQuote!!.attachments!!)
} }
@ -949,31 +946,12 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
} }
val query = queryBuilder.toString() val query = queryBuilder.toString()
val db = databaseHelper.writableDatabase val db = databaseHelper.writableDatabase
val values = ContentValues(3) val values = ContentValues(2)
values.put(QUOTE_MISSING, 1) values.put(QUOTE_MISSING, 1)
values.put(QUOTE_BODY, "")
values.put(QUOTE_AUTHOR, "") values.put(QUOTE_AUTHOR, "")
db!!.update(TABLE_NAME, values, query, null) db!!.update(TABLE_NAME, values, query, null)
} }
fun deleteQuotedFromMessages(toDeleteRecord: MessageRecord?) {
if (toDeleteRecord == null) {
return
}
val query = "$THREAD_ID = ?"
rawQuery(query, arrayOf(toDeleteRecord.threadId.toString())).use { threadMmsCursor ->
val reader = readerFor(threadMmsCursor)
var messageRecord: MmsMessageRecord? = reader.next as MmsMessageRecord?
while (messageRecord != null) {
if (messageRecord.quote != null && toDeleteRecord.dateSent == messageRecord.quote?.id) {
setQuoteMissing(messageRecord.id)
}
messageRecord = reader.next as MmsMessageRecord?
}
reader.close()
}
}
/** /**
* Delete all the messages in single queries where possible * Delete all the messages in single queries where possible
* @param messageIds a String array representation of regularly Long types representing message IDs * @param messageIds a String array representation of regularly Long types representing message IDs
@ -997,13 +975,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) }) queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) })
val groupReceiptDatabase = get(context).groupReceiptDatabase() val groupReceiptDatabase = get(context).groupReceiptDatabase()
groupReceiptDatabase.deleteRowsForMessages(messageIds) groupReceiptDatabase.deleteRowsForMessages(messageIds)
val toDeleteList: MutableList<MessageRecord> = ArrayList()
getMessages(idsAsString).use { messageCursor ->
while (messageCursor.moveToNext()) {
toDeleteList.add(readerFor(messageCursor).current)
}
}
deleteQuotedFromMessages(toDeleteList)
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
database.delete(TABLE_NAME, idsAsString, null) database.delete(TABLE_NAME, idsAsString, null)
notifyConversationListListeners() notifyConversationListListeners()
@ -1017,13 +988,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) }) queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
val groupReceiptDatabase = get(context).groupReceiptDatabase() val groupReceiptDatabase = get(context).groupReceiptDatabase()
groupReceiptDatabase.deleteRowsForMessage(messageId) groupReceiptDatabase.deleteRowsForMessage(messageId)
var toDelete: MessageRecord?
getMessage(messageId).use { messageCursor ->
toDelete = readerFor(messageCursor).next
}
deleteQuotedFromMessages(toDelete)
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
database!!.delete(TABLE_NAME, ID_WHERE, arrayOf<String>(messageId.toString())) database!!.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString()))
val threadDeleted = get(context).threadDatabase().update(threadId, false) val threadDeleted = get(context).threadDatabase().update(threadId, false)
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
notifyStickerListeners() notifyStickerListeners()
@ -1287,7 +1253,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
if (message.outgoingQuote != null) Quote( if (message.outgoingQuote != null) Quote(
message.outgoingQuote!!.id, message.outgoingQuote!!.id,
message.outgoingQuote!!.author, message.outgoingQuote!!.author,
message.outgoingQuote!!.text, message.outgoingQuote!!.text, // TODO: use the referenced message's content
message.outgoingQuote!!.missing, message.outgoingQuote!!.missing,
SlideDeck(context, message.outgoingQuote!!.attachments!!) SlideDeck(context, message.outgoingQuote!!.attachments!!)
) else null, ) else null,
@ -1466,8 +1432,9 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
private fun getQuote(cursor: Cursor): Quote? { private fun getQuote(cursor: Cursor): Quote? {
val quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID)) val quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID))
val quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR)) val quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR))
val quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY)) val retrievedQuote = get(context).mmsSmsDatabase().getMessageFor(quoteId, quoteAuthor)
val quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE_MISSING)) == 1 val quoteText = retrievedQuote?.body
val quoteMissing = retrievedQuote == null
val attachments = get(context).attachmentDatabase().getAttachment(cursor) val attachments = get(context).attachmentDatabase().getAttachment(cursor)
val quoteAttachments: List<Attachment?>? = val quoteAttachments: List<Attachment?>? =
Stream.of(attachments).filter { obj: DatabaseAttachment? -> obj!!.isQuote } Stream.of(attachments).filter { obj: DatabaseAttachment? -> obj!!.isQuote }
@ -1601,7 +1568,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS
) )
private const val RAW_ID_WHERE: String = "$TABLE_NAME._id = ?" private const val RAW_ID_WHERE: String = "$TABLE_NAME._id = ?"
private const val RAW_ID_IN: String = "$TABLE_NAME._id IN (?)"
const val createMessageRequestResponseCommand: String = "ALTER TABLE $TABLE_NAME ADD COLUMN $MESSAGE_REQUEST_RESPONSE INTEGER DEFAULT 0;" const val createMessageRequestResponseCommand: String = "ALTER TABLE $TABLE_NAME ADD COLUMN $MESSAGE_REQUEST_RESPONSE INTEGER DEFAULT 0;"
} }
} }

@ -563,12 +563,6 @@ public class SmsDatabase extends MessagingDatabase {
Log.i("MessageDatabase", "Deleting: " + messageId); Log.i("MessageDatabase", "Deleting: " + messageId);
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdForMessage(messageId); long threadId = getThreadIdForMessage(messageId);
try {
SmsMessageRecord toDelete = getMessage(messageId);
DatabaseComponent.get(context).mmsDatabase().deleteQuotedFromMessages(toDelete);
} catch (NoSuchMessageException e) {
Log.e(TAG, "Couldn't find message record for messageId "+messageId, e);
}
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);

@ -258,7 +258,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
Cursor pushCursor = null; Cursor pushCursor = null;
try { try {
telcoCursor = DatabaseComponent.get(context).mmsSmsDatabase().getUnread(); telcoCursor = DatabaseComponent.get(context).mmsSmsDatabase().getUnread(); // TODO: add a notification specific lighter query here
if ((telcoCursor == null || telcoCursor.isAfterLast()) || !TextSecurePreferences.hasSeenWelcomeScreen(context)) if ((telcoCursor == null || telcoCursor.isAfterLast()) || !TextSecurePreferences.hasSeenWelcomeScreen(context))
{ {

@ -17,7 +17,7 @@
android:id="@+id/album_cell_2" android:id="@+id/album_cell_2"
android:layout_width="@dimen/album_2_cell_width" android:layout_width="@dimen/album_2_cell_width"
android:layout_height="@dimen/album_2_total_height" android:layout_height="@dimen/album_2_total_height"
android:layout_gravity="right|end" android:layout_gravity="end"
app:thumbnail_radius="0dp"/> app:thumbnail_radius="0dp"/>
</FrameLayout> </FrameLayout>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/album_thumbnail_root" android:id="@+id/album_thumbnail_root"
android:layout_width="@dimen/album_total_width" android:layout_width="@dimen/album_total_width"
android:layout_height="@dimen/album_3_total_height"> android:layout_height="@dimen/album_3_total_height">
@ -16,14 +16,34 @@
android:id="@+id/album_cell_2" android:id="@+id/album_cell_2"
android:layout_width="@dimen/album_3_cell_size_small" android:layout_width="@dimen/album_3_cell_size_small"
android:layout_height="@dimen/album_3_cell_size_small" android:layout_height="@dimen/album_3_cell_size_small"
android:layout_gravity="right|end|top" android:layout_gravity="end|top"
app:thumbnail_radius="0dp"/> app:thumbnail_radius="0dp"/>
<FrameLayout
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="end|bottom">
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView <org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_3" android:id="@+id/album_cell_3"
android:layout_width="@dimen/album_3_cell_size_small" android:layout_height="match_parent"
android:layout_height="@dimen/album_3_cell_size_small" android:layout_width="match_parent"
android:layout_gravity="right|end|bottom" android:layout_gravity="center_horizontal|bottom"
app:thumbnail_radius="0dp"/> app:thumbnail_radius="0dp"/>
<TextView
tools:visibility="visible"
android:visibility="gone"
android:id="@+id/album_cell_overflow_text"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:gravity="center"
android:textSize="@dimen/text_size"
android:textColor="@color/core_white"
android:background="@color/transparent_black_70"
tools:text="+2" />
</FrameLayout>
</FrameLayout> </FrameLayout>

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/album_thumbnail_root"
android:layout_width="@dimen/album_total_width"
android:layout_height="@dimen/album_4_total_height">
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_1"
android:layout_width="@dimen/album_4_cell_size"
android:layout_height="@dimen/album_4_cell_size"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_2"
android:layout_width="@dimen/album_4_cell_size"
android:layout_height="@dimen/album_4_cell_size"
android:layout_gravity="right|end|top"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_3"
android:layout_width="@dimen/album_4_cell_size"
android:layout_height="@dimen/album_4_cell_size"
android:layout_gravity="left|start|bottom"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_4"
android:layout_width="@dimen/album_4_cell_size"
android:layout_height="@dimen/album_4_cell_size"
android:layout_gravity="right|end|bottom"
app:thumbnail_radius="0dp"/>
</FrameLayout>

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/album_thumbnail_root"
android:layout_width="@dimen/album_total_width"
android:layout_height="@dimen/album_5_total_height">
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_1"
android:layout_width="@dimen/album_5_cell_size_big"
android:layout_height="@dimen/album_5_cell_size_big"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_2"
android:layout_width="@dimen/album_5_cell_size_big"
android:layout_height="@dimen/album_5_cell_size_big"
android:layout_gravity="right|end|top"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_3"
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="left|start|bottom"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_4"
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="center_horizontal|bottom"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_5"
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="right|end|bottom"
app:thumbnail_radius="0dp"/>
</FrameLayout>

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/album_thumbnail_root"
android:layout_width="@dimen/album_total_width"
android:layout_height="@dimen/album_5_total_height">
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_1"
android:layout_width="@dimen/album_5_cell_size_big"
android:layout_height="@dimen/album_5_cell_size_big"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_2"
android:layout_width="@dimen/album_5_cell_size_big"
android:layout_height="@dimen/album_5_cell_size_big"
android:layout_gravity="right|end|top"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_3"
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="left|start|bottom"
app:thumbnail_radius="0dp"/>
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_4"
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="center_horizontal|bottom"
app:thumbnail_radius="0dp"/>
<FrameLayout
android:layout_width="@dimen/album_5_cell_size_small"
android:layout_height="@dimen/album_5_cell_size_small"
android:layout_gravity="right|end|bottom">
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
android:id="@+id/album_cell_5"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_gravity="center_horizontal|bottom"
app:thumbnail_radius="0dp"/>
<TextView
android:id="@+id/album_cell_overflow_text"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:gravity="center"
android:textSize="@dimen/text_size"
android:textColor="@color/core_white"
android:background="@color/transparent_black_40"
tools:text="+2" />
</FrameLayout>
</FrameLayout>

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
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"/>
<org.thoughtcrime.securesms.components.AlbumThumbnailView
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"/>
<ImageView
android:id="@+id/conversation_thumbnail_shade"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:visibility="gone"
android:src="@drawable/image_shade" />
<org.thoughtcrime.securesms.components.ConversationItemFooter
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_text_color="@android:color/white"
app:footer_icon_color="@android:color/white"/>
</merge>

@ -6,7 +6,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/input_bar_background" android:background="@color/input_bar_background"
android:minWidth="300dp"
android:minHeight="52dp" android:minHeight="52dp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:paddingHorizontal="12dp" android:paddingHorizontal="12dp"
@ -24,6 +23,7 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout <RelativeLayout
tools:visibility="visible"
android:id="@+id/quoteViewAttachmentPreviewContainer" android:id="@+id/quoteViewAttachmentPreviewContainer"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
@ -71,30 +71,27 @@
android:textColor="@color/text" android:textColor="@color/text"
android:textSize="@dimen/small_font_size" android:textSize="@dimen/small_font_size"
android:textStyle="bold" android:textStyle="bold"
android:visibility="gone" android:maxWidth="240dp"
app:layout_constraintBottom_toTopOf="@+id/quoteViewBodyTextView" app:layout_constraintBottom_toTopOf="@+id/quoteViewBodyTextView"
app:layout_constraintEnd_toEndOf="@+id/quoteViewBodyTextView"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/quoteStartBarrier" app:layout_constraintStart_toEndOf="@+id/quoteStartBarrier"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
tools:text="Spiderman" /> tools:text="@tools:sample/full_names" />
<TextView <TextView
android:id="@+id/quoteViewBodyTextView" android:id="@+id/quoteViewBodyTextView"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_spacing" android:layout_marginStart="@dimen/medium_spacing"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="3" android:maxLines="1"
android:textColor="@color/text" android:textColor="@color/text"
android:textSize="@dimen/small_font_size" android:textSize="@dimen/small_font_size"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/quoteStartBarrier" app:layout_constraintStart_toEndOf="@+id/quoteStartBarrier"
app:layout_constraintTop_toBottomOf="@+id/quoteViewAuthorTextView" app:layout_constraintTop_toBottomOf="@+id/quoteViewAuthorTextView"
app:layout_constraintVertical_chainStyle="packed"
android:maxWidth="240dp" android:maxWidth="240dp"
tools:maxLines="1" tools:maxLines="1"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />

@ -31,7 +31,7 @@
android:layout_height="1dp"/> android:layout_height="1dp"/>
<include <include
tools:visibility="gone" tools:visibility="visible"
android:id="@+id/profilePictureView" android:id="@+id/profilePictureView"
layout="@layout/view_profile_picture" layout="@layout/view_profile_picture"
android:layout_marginBottom="@dimen/small_spacing" android:layout_marginBottom="@dimen/small_spacing"
@ -44,6 +44,7 @@
android:layout_gravity="center" /> android:layout_gravity="center" />
<ImageView <ImageView
tools:visibility="visible"
android:visibility="gone" android:visibility="gone"
android:id="@+id/moderatorIconImageView" android:id="@+id/moderatorIconImageView"
android:layout_width="16dp" android:layout_width="16dp"
@ -87,7 +88,7 @@
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView <org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
tools:visibility="visible" tools:visibility="visible"
android:visibility="gone" android:visibility="invisible"
android:id="@+id/expirationTimerView" android:id="@+id/expirationTimerView"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"

@ -7,12 +7,19 @@
android:id="@+id/mainContainerConstraint" android:id="@+id/mainContainerConstraint"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/contentParent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintEnd_toEndOf="parent"
>
<!-- Content that will only show on its own --> <!-- Content that will only show on its own -->
<include layout="@layout/view_deleted_message" <include layout="@layout/view_deleted_message"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" android:visibility="gone"
android:id="@+id/deletedMessageView" android:id="@+id/deletedMessageView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -24,7 +31,6 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" android:visibility="gone"
android:id="@+id/untrustedView" android:id="@+id/untrustedView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -34,7 +40,6 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" android:visibility="gone"
android:id="@+id/voiceMessageView" android:id="@+id/voiceMessageView"
android:layout_width="160dp" android:layout_width="160dp"
@ -45,7 +50,6 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" android:visibility="gone"
android:id="@+id/openGroupInvitationView" android:id="@+id/openGroupInvitationView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -55,7 +59,6 @@
tools:visibility="gone" tools:visibility="gone"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" android:visibility="gone"
android:id="@+id/documentView" android:id="@+id/documentView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -71,6 +74,7 @@
android:id="@+id/quoteView" android:id="@+id/quoteView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.conversation.v2.messages.LinkPreviewView <org.thoughtcrime.securesms.conversation.v2.messages.LinkPreviewView
app:layout_constraintTop_toBottomOf="@+id/quoteView" app:layout_constraintTop_toBottomOf="@+id/quoteView"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -78,33 +82,45 @@
android:id="@+id/linkPreviewView" android:id="@+id/linkPreviewView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
app:barrierAllowsGoneWidgets="true" app:barrierAllowsGoneWidgets="true"
android:id="@+id/bodyBarrier" android:id="@+id/bodyBarrier"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="end" app:barrierDirection="end"
app:constraint_referenced_ids="albumThumbnailView,linkPreviewView,quoteView,voiceMessageView"/> app:constraint_referenced_ids="linkPreviewView,quoteView,voiceMessageView"/>
<org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView <androidx.constraintlayout.widget.Barrier
app:layout_constraintTop_toBottomOf="@+id/linkPreviewView"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:id="@+id/albumThumbnailView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"
android:id="@+id/bodyTopBarrier"
app:constraint_referenced_ids="linkPreviewView,quoteView"
app:barrierDirection="bottom"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
tools:visibility="visible"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/albumThumbnailView" app:layout_constraintTop_toBottomOf="@+id/bodyTopBarrier"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="@+id/bodyBarrier" android:maxWidth="300dp"
android:paddingHorizontal="12dp" android:paddingHorizontal="12dp"
android:paddingVertical="@dimen/small_spacing" android:paddingVertical="@dimen/small_spacing"
android:id="@+id/bodyTextView" android:id="@+id/bodyTextView"
android:layout_width="0dp" android:layout_width="wrap_content"
android:maxWidth="300dp"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
android:visibility="visible"
android:id="@+id/albumThumbnailView"
android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@+id/contentParent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -60,6 +60,15 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
messageDataProvider.setAttachmentState(AttachmentState.FAILED, AttachmentId(attachmentID,0), databaseMessageID) messageDataProvider.setAttachmentState(AttachmentState.FAILED, AttachmentId(attachmentID,0), databaseMessageID)
} }
this.handlePermanentFailure(exception) this.handlePermanentFailure(exception)
} else if (exception == Error.DuplicateData) {
attachment?.let { id ->
Log.d("AttachmentDownloadJob", "Setting attachment state = done from duplicate data")
messageDataProvider.setAttachmentState(AttachmentState.DONE, id, databaseMessageID)
} ?: run {
Log.d("AttachmentDownloadJob", "Setting attachment state = done from duplicate data")
messageDataProvider.setAttachmentState(AttachmentState.DONE, AttachmentId(attachmentID,0), databaseMessageID)
}
this.handleSuccess()
} else { } else {
if (failureCount + 1 >= maxFailureCount) { if (failureCount + 1 >= maxFailureCount) {
attachment?.let { id -> attachment?.let { id ->

@ -30,7 +30,7 @@ class Quote() {
fun from(signalQuote: SignalQuote?): Quote? { fun from(signalQuote: SignalQuote?): Quote? {
if (signalQuote == null) { return null } if (signalQuote == null) { return null }
val attachmentID = (signalQuote.attachments?.firstOrNull() as? DatabaseAttachment)?.attachmentId?.rowId val attachmentID = (signalQuote.attachments?.firstOrNull() as? DatabaseAttachment)?.attachmentId?.rowId
return Quote(signalQuote.id, signalQuote.author.serialize(), signalQuote.text, attachmentID) return Quote(signalQuote.id, signalQuote.author.serialize(), "", attachmentID) // TODO: re-add this
} }
} }

@ -254,9 +254,9 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author) val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author)
if (messageInfo != null) { if (messageInfo != null) {
val attachments = if (messageInfo.second) messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList() val attachments = if (messageInfo.second) messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList()
quoteModel = QuoteModel(quote.id, author, messageDataProvider.getMessageBodyFor(quote.id, quote.author), false, attachments) quoteModel = QuoteModel(quote.id, author,null,false, attachments)
} else { } else {
quoteModel = QuoteModel(quote.id, author, quote.text, true, PointerAttachment.forPointers(proto.dataMessage.quote.attachmentsList)) quoteModel = QuoteModel(quote.id, author,null, true, PointerAttachment.forPointers(proto.dataMessage.quote.attachmentsList))
} }
} }
// Parse link preview if needed // Parse link preview if needed

@ -5,6 +5,6 @@ import org.session.libsession.utilities.Address
class QuoteModel(val id: Long, class QuoteModel(val id: Long,
val author: Address, val author: Address,
val text: String, val text: String?,
val missing: Boolean, val missing: Boolean,
val attachments: List<Attachment>?) val attachments: List<Attachment>?)

Loading…
Cancel
Save