mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-23 19:49:30 +00:00
Improve the look of message bubbles.
This commit is contained in:
@@ -9,18 +9,21 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public interface BindableConversationItem extends Unbindable {
|
||||
void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
boolean pulseHighlight);
|
||||
void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
boolean pulseHighlight);
|
||||
|
||||
MessageRecord getMessageRecord();
|
||||
|
||||
|
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.security.MessageDigest;
|
||||
@@ -189,12 +190,24 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) {
|
||||
long start = System.currentTimeMillis();
|
||||
viewHolder.getView().bind(messageRecord, glideRequests, locale, batchSelected, recipient, messageRecord == recordToPulseHighlight);
|
||||
protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord, int position) {
|
||||
long start = System.currentTimeMillis();
|
||||
MessageRecord previousRecord = position < getItemCount() - 1 ? getRecordForPositionOrThrow(position + 1) : null;
|
||||
MessageRecord nextRecord = position > 0 ? getRecordForPositionOrThrow(position - 1) : null;
|
||||
|
||||
viewHolder.getView().bind(messageRecord,
|
||||
Optional.fromNullable(previousRecord),
|
||||
Optional.fromNullable(nextRecord),
|
||||
glideRequests,
|
||||
locale,
|
||||
batchSelected,
|
||||
recipient,
|
||||
messageRecord == recordToPulseHighlight);
|
||||
|
||||
if (messageRecord == recordToPulseHighlight) {
|
||||
recordToPulseHighlight = null;
|
||||
}
|
||||
|
||||
Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start));
|
||||
}
|
||||
|
||||
@@ -240,11 +253,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
|
||||
@Override
|
||||
public int getItemViewType(@NonNull MessageRecord messageRecord) {
|
||||
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() ||
|
||||
messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() ||
|
||||
messageRecord.isIdentityUpdate() || messageRecord.isIdentityVerified() ||
|
||||
messageRecord.isIdentityDefault())
|
||||
{
|
||||
if (messageRecord.isUpdate()) {
|
||||
return MESSAGE_TYPE_UPDATE;
|
||||
} else if (hasAudio(messageRecord)) {
|
||||
if (messageRecord.isOutgoing()) return MESSAGE_TYPE_AUDIO_OUTGOING;
|
||||
@@ -365,7 +374,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
if (!isActiveCursor()) return -1;
|
||||
|
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
@@ -25,10 +24,9 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.DimenRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
@@ -40,7 +38,6 @@ import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@@ -50,12 +47,11 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.components.AlertView;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
||||
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.ExpirationTimerView;
|
||||
import org.thoughtcrime.securesms.components.QuoteView;
|
||||
import org.thoughtcrime.securesms.components.SharedContactView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -75,18 +71,15 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@@ -114,28 +107,24 @@ public class ConversationItem extends LinearLayout
|
||||
private Recipient recipient;
|
||||
private GlideRequests glideRequests;
|
||||
|
||||
protected View bodyBubble;
|
||||
private QuoteView quoteView;
|
||||
private TextView bodyText;
|
||||
private TextView dateText;
|
||||
private TextView simInfoText;
|
||||
private TextView indicatorText;
|
||||
private TextView groupSender;
|
||||
private TextView groupSenderProfileName;
|
||||
private View groupSenderHolder;
|
||||
private ImageView insecureImage;
|
||||
private AvatarImageView contactPhoto;
|
||||
private DeliveryStatusView deliveryStatusIndicator;
|
||||
private AlertView alertView;
|
||||
protected View bodyBubble;
|
||||
private QuoteView quoteView;
|
||||
private TextView bodyText;
|
||||
private ConversationItemFooter footer;
|
||||
private TextView groupSender;
|
||||
private TextView groupSenderProfileName;
|
||||
private View groupSenderHolder;
|
||||
private AvatarImageView contactPhoto;
|
||||
private AlertView alertView;
|
||||
private ViewGroup container;
|
||||
|
||||
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
|
||||
private @NonNull Recipient conversationRecipient;
|
||||
private @NonNull Stub<ThumbnailView> mediaThumbnailStub;
|
||||
private @NonNull Stub<AudioView> audioViewStub;
|
||||
private @NonNull Stub<DocumentView> documentViewStub;
|
||||
private @NonNull Stub<SharedContactView> sharedContactStub;
|
||||
private @NonNull ExpirationTimerView expirationTimer;
|
||||
private @Nullable EventListener eventListener;
|
||||
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
|
||||
private @NonNull Recipient conversationRecipient;
|
||||
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
||||
private @NonNull Stub<AudioView> audioViewStub;
|
||||
private @NonNull Stub<DocumentView> documentViewStub;
|
||||
private @NonNull Stub<SharedContactView> sharedContactStub;
|
||||
private @Nullable EventListener eventListener;
|
||||
|
||||
private int defaultBubbleColor;
|
||||
|
||||
@@ -167,13 +156,9 @@ public class ConversationItem extends LinearLayout
|
||||
initializeAttributes();
|
||||
|
||||
this.bodyText = findViewById(R.id.conversation_item_body);
|
||||
this.dateText = findViewById(R.id.conversation_item_date);
|
||||
this.simInfoText = findViewById(R.id.sim_info);
|
||||
this.indicatorText = findViewById(R.id.indicator_text);
|
||||
this.footer = findViewById(R.id.conversation_item_footer);
|
||||
this.groupSender = findViewById(R.id.group_message_sender);
|
||||
this.groupSenderProfileName = findViewById(R.id.group_message_sender_profile);
|
||||
this.insecureImage = findViewById(R.id.insecure_indicator);
|
||||
this.deliveryStatusIndicator = findViewById(R.id.delivery_status);
|
||||
this.alertView = findViewById(R.id.indicators_parent);
|
||||
this.contactPhoto = findViewById(R.id.contact_photo);
|
||||
this.bodyBubble = findViewById(R.id.body_bubble);
|
||||
@@ -181,9 +166,9 @@ public class ConversationItem extends LinearLayout
|
||||
this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub));
|
||||
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
|
||||
this.sharedContactStub = new Stub<>(findViewById(R.id.shared_contact_view_stub));
|
||||
this.expirationTimer = findViewById(R.id.expiration_indicator);
|
||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
this.container = findViewById(R.id.container);
|
||||
|
||||
setOnClickListener(new ClickListener(null));
|
||||
|
||||
@@ -194,12 +179,14 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseHighlight)
|
||||
public void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseHighlight)
|
||||
{
|
||||
this.messageRecord = messageRecord;
|
||||
this.locale = locale;
|
||||
@@ -212,17 +199,18 @@ public class ConversationItem extends LinearLayout
|
||||
this.recipient.addListener(this);
|
||||
this.conversationRecipient.addListener(this);
|
||||
|
||||
setMediaAttributes(messageRecord, conversationRecipient);
|
||||
setInteractionState(messageRecord, pulseHighlight);
|
||||
setBodyText(messageRecord);
|
||||
setBubbleState(messageRecord, recipient);
|
||||
setStatusIcons(messageRecord);
|
||||
setContactPhoto(recipient);
|
||||
setGroupMessageStatus(messageRecord, recipient);
|
||||
setMinimumWidth();
|
||||
setSimInfo(messageRecord);
|
||||
setExpiration(messageRecord);
|
||||
setQuote(messageRecord);
|
||||
presentMessageBackground(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
presentMedia(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
||||
presentInteractionState(messageRecord, pulseHighlight);
|
||||
presentBodyText(messageRecord);
|
||||
presentBubbleState(messageRecord);
|
||||
presentStatusIcons(messageRecord);
|
||||
presentContactPhoto(recipient);
|
||||
presentGroupMessageStatus(messageRecord, recipient);
|
||||
presentAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
presentQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
presentMessageSpacing(messageRecord, nextMessageRecord, groupThread);
|
||||
presentFooter(messageRecord, nextMessageRecord, locale, groupThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,53 +218,52 @@ public class ConversationItem extends LinearLayout
|
||||
this.eventListener = eventListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
if (groupSenderHolder != null && groupSenderHolder.getVisibility() == View.VISIBLE) {
|
||||
View content = (View) groupSenderHolder.getParent();
|
||||
|
||||
groupSenderHolder.layout(content.getPaddingLeft(), content.getPaddingTop(),
|
||||
content.getWidth() - content.getPaddingRight(),
|
||||
content.getPaddingTop() + groupSenderHolder.getMeasuredHeight());
|
||||
|
||||
|
||||
if (ViewCompat.getLayoutDirection(groupSenderProfileName) == ViewCompat.LAYOUT_DIRECTION_RTL) {
|
||||
groupSenderProfileName.layout(groupSenderHolder.getPaddingLeft(),
|
||||
groupSenderHolder.getPaddingTop(),
|
||||
groupSenderHolder.getPaddingLeft() + groupSenderProfileName.getWidth(),
|
||||
groupSenderHolder.getPaddingTop() + groupSenderProfileName.getHeight());
|
||||
} else {
|
||||
groupSenderProfileName.layout(groupSenderHolder.getWidth() - groupSenderHolder.getPaddingRight() - groupSenderProfileName.getWidth(),
|
||||
groupSenderHolder.getPaddingTop(),
|
||||
groupSenderHolder.getWidth() - groupSenderProfileName.getPaddingRight(),
|
||||
groupSenderHolder.getPaddingTop() + groupSenderProfileName.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (hasQuote(messageRecord)) {
|
||||
int quoteWidth = quoteView.getMeasuredWidth();
|
||||
if (isInEditMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int availableWidth;
|
||||
if (hasAudio(messageRecord)) {
|
||||
availableWidth = audioViewStub.get().getMeasuredWidth();
|
||||
} else if (hasThumbnail(messageRecord)) {
|
||||
availableWidth = mediaThumbnailStub.get().getMeasuredWidth();
|
||||
} else {
|
||||
availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight();
|
||||
}
|
||||
boolean needsMeasure = false;
|
||||
|
||||
if (hasQuote(messageRecord)) {
|
||||
int quoteWidth = quoteView.getMeasuredWidth();
|
||||
int availableWidth = getAvailableMessageBubbleWidth(quoteView);
|
||||
|
||||
if (quoteWidth != availableWidth) {
|
||||
quoteView.getLayoutParams().width = availableWidth;
|
||||
measure(widthMeasureSpec, heightMeasureSpec);
|
||||
needsMeasure = true;
|
||||
}
|
||||
}
|
||||
|
||||
ConversationItemFooter activeFooter = getActiveFooter(messageRecord);
|
||||
int availableWidth = getAvailableMessageBubbleWidth(footer);
|
||||
|
||||
if (activeFooter.getVisibility() != GONE && activeFooter.getMeasuredWidth() != availableWidth) {
|
||||
activeFooter.getLayoutParams().width = availableWidth;
|
||||
needsMeasure = true;
|
||||
}
|
||||
|
||||
if (needsMeasure) {
|
||||
measure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private int getAvailableMessageBubbleWidth(@NonNull View forView) {
|
||||
int availableWidth;
|
||||
if (hasAudio(messageRecord)) {
|
||||
availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get());
|
||||
} else if (hasThumbnail(messageRecord)) {
|
||||
availableWidth = mediaThumbnailStub.get().getMeasuredWidth();
|
||||
} else {
|
||||
availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight();
|
||||
}
|
||||
|
||||
availableWidth -= ViewUtil.getLeftMargin(forView) + ViewUtil.getRightMargin(forView);
|
||||
|
||||
return availableWidth;
|
||||
}
|
||||
|
||||
private void initializeAttributes() {
|
||||
@@ -292,8 +279,6 @@ public class ConversationItem extends LinearLayout
|
||||
if (recipient != null) {
|
||||
recipient.removeListener(this);
|
||||
}
|
||||
|
||||
this.expirationTimer.stopAnimation();
|
||||
}
|
||||
|
||||
public MessageRecord getMessageRecord() {
|
||||
@@ -302,29 +287,23 @@ public class ConversationItem extends LinearLayout
|
||||
|
||||
/// MessageRecord Attribute Parsers
|
||||
|
||||
private void setBubbleState(MessageRecord messageRecord, Recipient recipient) {
|
||||
private void presentBubbleState(MessageRecord messageRecord) {
|
||||
if (messageRecord.isOutgoing()) {
|
||||
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setBackgroundColorHint(defaultBubbleColor);
|
||||
} else {
|
||||
int color = recipient.getColor().toConversationColor(context);
|
||||
bodyBubble.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setBackgroundColorHint(color);
|
||||
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||
bodyBubble.getBackground().setColorFilter(messageRecord.getRecipient().getColor().toConversationColor(context), PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
|
||||
if (audioViewStub.resolved()) {
|
||||
setAudioViewTint(messageRecord, conversationRecipient);
|
||||
}
|
||||
|
||||
if (documentViewStub.resolved()) {
|
||||
setDocumentViewTint(messageRecord, conversationRecipient);
|
||||
setAudioViewTint(messageRecord, this.conversationRecipient);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAudioViewTint(MessageRecord messageRecord, Recipient recipient) {
|
||||
if (messageRecord.isOutgoing()) {
|
||||
if (DynamicTheme.LIGHT.equals(TextSecurePreferences.getTheme(context))) {
|
||||
audioViewStub.get().setTint(recipient.getColor().toConversationColor(context), defaultBubbleColor);
|
||||
audioViewStub.get().setTint(getContext().getResources().getColor(R.color.core_light_60), defaultBubbleColor);
|
||||
} else {
|
||||
audioViewStub.get().setTint(Color.WHITE, defaultBubbleColor);
|
||||
}
|
||||
@@ -333,19 +312,7 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void setDocumentViewTint(MessageRecord messageRecord, Recipient recipient) {
|
||||
if (messageRecord.isOutgoing()) {
|
||||
if (DynamicTheme.LIGHT.equals(TextSecurePreferences.getTheme(context))) {
|
||||
documentViewStub.get().setTint(recipient.getColor().toConversationColor(context), defaultBubbleColor);
|
||||
} else {
|
||||
documentViewStub.get().setTint(Color.WHITE, defaultBubbleColor);
|
||||
}
|
||||
} else {
|
||||
documentViewStub.get().setTint(Color.WHITE, recipient.getColor().toConversationColor(context));
|
||||
}
|
||||
}
|
||||
|
||||
private void setInteractionState(MessageRecord messageRecord, boolean pulseHighlight) {
|
||||
private void presentInteractionState(MessageRecord messageRecord, boolean pulseHighlight) {
|
||||
if (batchSelected.contains(messageRecord)) {
|
||||
setBackgroundResource(R.drawable.conversation_item_background);
|
||||
setSelected(true);
|
||||
@@ -387,6 +354,10 @@ public class ConversationItem extends LinearLayout
|
||||
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
|
||||
}
|
||||
|
||||
private boolean hasOnlyThumbnail(MessageRecord messageRecord) {
|
||||
return hasThumbnail(messageRecord) && !hasAudio(messageRecord) && !hasDocument(messageRecord) && !hasSharedContact(messageRecord);
|
||||
}
|
||||
|
||||
private boolean hasDocument(MessageRecord messageRecord) {
|
||||
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null;
|
||||
}
|
||||
@@ -399,7 +370,7 @@ public class ConversationItem extends LinearLayout
|
||||
return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getSharedContacts().isEmpty();
|
||||
}
|
||||
|
||||
private void setBodyText(MessageRecord messageRecord) {
|
||||
private void presentBodyText(MessageRecord messageRecord) {
|
||||
bodyText.setClickable(false);
|
||||
bodyText.setFocusable(false);
|
||||
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
||||
@@ -412,54 +383,67 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaAttributes(MessageRecord messageRecord, Recipient conversationRecipient) {
|
||||
boolean showControls = !messageRecord.isFailed() && !Util.isOwnNumber(context, conversationRecipient.getAddress());
|
||||
private void presentMedia(@NonNull MessageRecord currentMessage,
|
||||
@NonNull Optional<MessageRecord> previousMessage,
|
||||
@NonNull Optional<MessageRecord> nextMessage,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean isGroupThread)
|
||||
{
|
||||
boolean showControls = !currentMessage.isFailed() && !Util.isOwnNumber(context, conversationRecipient.getAddress());
|
||||
|
||||
if (hasSharedContact(messageRecord)) {
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.setPaddingTop(bodyBubble, readDimen(R.dimen.message_bubble_top_padding));
|
||||
ViewUtil.setPaddingBottom(bodyBubble, 0);
|
||||
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().showShade(false);
|
||||
|
||||
footer.setVisibility(VISIBLE);
|
||||
|
||||
if (hasSharedContact(currentMessage)) {
|
||||
sharedContactStub.get().setVisibility(VISIBLE);
|
||||
if (audioViewStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale);
|
||||
sharedContactStub.get().setContact(((MediaMmsMessageRecord) currentMessage).getSharedContacts().get(0), glideRequests, locale);
|
||||
sharedContactStub.get().setEventListener(sharedContactEventListener);
|
||||
sharedContactStub.get().setOnClickListener(sharedContactClickListener);
|
||||
sharedContactStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
} else if (hasAudio(messageRecord)) {
|
||||
setSharedContactCorners(currentMessage, previousMessage, nextMessage, isGroupThread);
|
||||
|
||||
footer.setVisibility(GONE);
|
||||
} else if (hasAudio(currentMessage)) {
|
||||
audioViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
|
||||
audioViewStub.get().setAudio(((MediaMmsMessageRecord) currentMessage).getSlideDeck().getAudioSlide(), showControls);
|
||||
audioViewStub.get().setDownloadClickListener(downloadClickListener);
|
||||
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
} else if (hasDocument(messageRecord)) {
|
||||
} else if (hasDocument(currentMessage)) {
|
||||
documentViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
documentViewStub.get().setDocument(((MediaMmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide(), showControls);
|
||||
documentViewStub.get().setDocument(((MediaMmsMessageRecord)currentMessage).getSlideDeck().getDocumentSlide(), showControls);
|
||||
documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener());
|
||||
documentViewStub.get().setDownloadClickListener(downloadClickListener);
|
||||
documentViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
|
||||
} else if (hasThumbnail(messageRecord)) {
|
||||
} else if (hasThumbnail(currentMessage)) {
|
||||
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||
|
||||
setThumbnailCorners(currentMessage, previousMessage, nextMessage, isGroupThread);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
Slide thumbnailSlide = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlide();
|
||||
Slide thumbnailSlide = ((MmsMessageRecord) currentMessage).getSlideDeck().getThumbnailSlide();
|
||||
Attachment attachment = thumbnailSlide.asAttachment();
|
||||
mediaThumbnailStub.get().setImageResource(glideRequests,
|
||||
thumbnailSlide,
|
||||
@@ -472,17 +456,97 @@ public class ConversationItem extends LinearLayout
|
||||
mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
mediaThumbnailStub.get().setOnClickListener(passthroughClickListener);
|
||||
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if (!hasQuote(currentMessage)) {
|
||||
ViewUtil.setPaddingTop(bodyBubble, 0);
|
||||
} else {
|
||||
ViewUtil.setPaddingTop(bodyBubble, readDimen(R.dimen.message_bubble_top_padding));
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(currentMessage.getDisplayBody())) {
|
||||
mediaThumbnailStub.get().showShade(true);
|
||||
mediaThumbnailStub.get().setBackgroundResource(getCornerBackgroundRes(currentMessage, previousMessage, nextMessage, isGroupThread));
|
||||
}
|
||||
} else {
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
}
|
||||
|
||||
private void setContactPhoto(@NonNull Recipient recipient) {
|
||||
private void setThumbnailCorners(@NonNull MessageRecord current,
|
||||
@NonNull Optional<MessageRecord> previous,
|
||||
@NonNull Optional<MessageRecord> next,
|
||||
boolean isGroupThread)
|
||||
{
|
||||
int defaultRadius = readDimen(R.dimen.message_corner_radius);
|
||||
int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius);
|
||||
|
||||
int topLeft = defaultRadius;
|
||||
int topRight = defaultRadius;
|
||||
int bottomLeft = defaultRadius;
|
||||
int bottomRight = defaultRadius;
|
||||
|
||||
if (isSingularMessage(current, previous, next, isGroupThread)) {
|
||||
topLeft = defaultRadius;
|
||||
topRight = defaultRadius;
|
||||
bottomLeft = defaultRadius;
|
||||
bottomRight = defaultRadius;
|
||||
} else if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
||||
if (current.isOutgoing()) {
|
||||
bottomRight = collapseRadius;
|
||||
} else {
|
||||
bottomLeft = collapseRadius;
|
||||
}
|
||||
} else if (isEndOfMessageCluster(current, next, isGroupThread)) {
|
||||
if (current.isOutgoing()) {
|
||||
topRight = collapseRadius;
|
||||
} else {
|
||||
topLeft = collapseRadius;
|
||||
}
|
||||
} else {
|
||||
if (current.isOutgoing()) {
|
||||
topRight = collapseRadius;
|
||||
bottomRight = collapseRadius;
|
||||
} else {
|
||||
topLeft = collapseRadius;
|
||||
bottomLeft = collapseRadius;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(current.getDisplayBody())) {
|
||||
bottomLeft = 0;
|
||||
bottomRight = 0;
|
||||
}
|
||||
|
||||
if (isStartOfMessageCluster(current, previous, isGroupThread) && !current.isOutgoing() && isGroupThread) {
|
||||
topLeft = 0;
|
||||
topRight = 0;
|
||||
}
|
||||
|
||||
if (hasQuote(messageRecord)) {
|
||||
topLeft = 0;
|
||||
topRight = 0;
|
||||
}
|
||||
|
||||
mediaThumbnailStub.get().setCornerRadii(topLeft, topRight, bottomRight, bottomLeft);
|
||||
}
|
||||
|
||||
private void setSharedContactCorners(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
if (isSingularMessage(current, previous, next, isGroupThread) || isEndOfMessageCluster(current, next, isGroupThread)) {
|
||||
sharedContactStub.get().setSingularStyle();
|
||||
} else {
|
||||
if (current.isOutgoing()) {
|
||||
sharedContactStub.get().setClusteredOutgoingStyle();
|
||||
} else {
|
||||
sharedContactStub.get().setClusteredIncomingStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void presentContactPhoto(@NonNull Recipient recipient) {
|
||||
if (contactPhoto == null) return;
|
||||
|
||||
if (messageRecord.isOutgoing() || !groupThread) {
|
||||
@@ -507,12 +571,8 @@ public class ConversationItem extends LinearLayout
|
||||
return messageBody;
|
||||
}
|
||||
|
||||
private void setStatusIcons(MessageRecord messageRecord) {
|
||||
indicatorText.setVisibility(View.GONE);
|
||||
|
||||
insecureImage.setVisibility(messageRecord.isSecure() ? View.GONE : View.VISIBLE);
|
||||
private void presentStatusIcons(MessageRecord messageRecord) {
|
||||
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
|
||||
dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getTimestamp()));
|
||||
|
||||
if (messageRecord.isFailed()) {
|
||||
setFailedStatusIcons();
|
||||
@@ -520,73 +580,12 @@ public class ConversationItem extends LinearLayout
|
||||
setFallbackStatusIcons();
|
||||
} else {
|
||||
alertView.setNone();
|
||||
|
||||
if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone();
|
||||
else if (messageRecord.isPending()) deliveryStatusIndicator.setPending();
|
||||
else if (messageRecord.isRemoteRead()) deliveryStatusIndicator.setRead();
|
||||
else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered();
|
||||
else deliveryStatusIndicator.setSent();
|
||||
}
|
||||
}
|
||||
|
||||
private void setSimInfo(MessageRecord messageRecord) {
|
||||
SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context);
|
||||
|
||||
if (messageRecord.getSubscriptionId() == -1 || !Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE) || subscriptionManager.getActiveSubscriptionInfoList().size() < 2) {
|
||||
simInfoText.setVisibility(View.GONE);
|
||||
} else {
|
||||
Optional<SubscriptionInfoCompat> subscriptionInfo = subscriptionManager.getActiveSubscriptionInfo(messageRecord.getSubscriptionId());
|
||||
|
||||
if (subscriptionInfo.isPresent() && messageRecord.isOutgoing()) {
|
||||
simInfoText.setText(getContext().getString(R.string.ConversationItem_from_s, subscriptionInfo.get().getDisplayName()));
|
||||
simInfoText.setVisibility(View.VISIBLE);
|
||||
} else if (subscriptionInfo.isPresent()) {
|
||||
simInfoText.setText(getContext().getString(R.string.ConversationItem_to_s, subscriptionInfo.get().getDisplayName()));
|
||||
simInfoText.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
simInfoText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void setExpiration(final MessageRecord messageRecord) {
|
||||
if (messageRecord.getExpiresIn() > 0) {
|
||||
this.expirationTimer.setVisibility(View.VISIBLE);
|
||||
this.expirationTimer.setPercentage(0);
|
||||
|
||||
if (messageRecord.getExpireStarted() > 0) {
|
||||
this.expirationTimer.setExpirationTime(messageRecord.getExpireStarted(),
|
||||
messageRecord.getExpiresIn());
|
||||
this.expirationTimer.startAnimation();
|
||||
|
||||
if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) {
|
||||
ApplicationContext.getInstance(context).getExpiringMessageManager().checkSchedule();
|
||||
}
|
||||
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
long id = messageRecord.getId();
|
||||
boolean mms = messageRecord.isMms();
|
||||
|
||||
if (mms) DatabaseFactory.getMmsDatabase(context).markExpireStarted(id);
|
||||
else DatabaseFactory.getSmsDatabase(context).markExpireStarted(id);
|
||||
|
||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
} else {
|
||||
this.expirationTimer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuote(@NonNull MessageRecord messageRecord) {
|
||||
if (messageRecord.isMms() && !messageRecord.isMmsNotification() && ((MediaMmsMessageRecord)messageRecord).getQuote() != null) {
|
||||
Quote quote = ((MediaMmsMessageRecord)messageRecord).getQuote();
|
||||
private void presentQuote(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) {
|
||||
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
|
||||
assert quote != null;
|
||||
quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quote.getText(), quote.getAttachment());
|
||||
quoteView.setVisibility(View.VISIBLE);
|
||||
@@ -594,42 +593,98 @@ public class ConversationItem extends LinearLayout
|
||||
|
||||
quoteView.setOnClickListener(view -> {
|
||||
if (eventListener != null && batchSelected.isEmpty()) {
|
||||
eventListener.onQuoteClicked((MmsMessageRecord) messageRecord);
|
||||
eventListener.onQuoteClicked((MmsMessageRecord) current);
|
||||
} else {
|
||||
passthroughClickListener.onClick(view);
|
||||
}
|
||||
});
|
||||
quoteView.setOnLongClickListener(passthroughClickListener);
|
||||
ViewUtil.setPaddingTop(bodyBubble, 0);
|
||||
|
||||
if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
||||
if (current.isOutgoing()) {
|
||||
quoteView.setTopCornerSizes(true, true);
|
||||
} else if (isGroupThread) {
|
||||
quoteView.setTopCornerSizes(false, false);
|
||||
} else {
|
||||
quoteView.setTopCornerSizes(true, true);
|
||||
}
|
||||
} else if (!isSingularMessage(current, previous, next, isGroupThread)) {
|
||||
if (current.isOutgoing()) {
|
||||
quoteView.setTopCornerSizes(true, false);
|
||||
} else {
|
||||
quoteView.setTopCornerSizes(false, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quoteView.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void presentMessageSpacing(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
ViewUtil.setPaddingBottom(this, getMessageSpacing(context, current, next));
|
||||
|
||||
if (isGroupThread) {
|
||||
if (current.isOutgoing()) {
|
||||
ViewUtil.setLeftMargin(container, readDimen(R.dimen.conversation_group_left_gutter));
|
||||
} else {
|
||||
ViewUtil.setLeftMargin(bodyBubble, readDimen(R.dimen.conversation_group_left_gutter));
|
||||
}
|
||||
} else {
|
||||
if (current.isOutgoing()) {
|
||||
ViewUtil.setLeftMargin(container, readDimen(R.dimen.conversation_individual_left_gutter));
|
||||
} else {
|
||||
ViewUtil.setLeftMargin(bodyBubble, readDimen(R.dimen.conversation_individual_left_gutter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void presentMessageBackground(@NonNull MessageRecord current,
|
||||
@NonNull Optional<MessageRecord> previous,
|
||||
@NonNull Optional<MessageRecord> next,
|
||||
boolean isGroupThread)
|
||||
{
|
||||
bodyBubble.setBackgroundResource(getCornerBackgroundRes(current, previous, next, isGroupThread));
|
||||
}
|
||||
|
||||
private void presentFooter(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> next, @NonNull Locale locale, boolean isGroupThread) {
|
||||
ViewUtil.updateLayoutParams(footer, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.setPaddingBottom(bodyBubble, 0);
|
||||
|
||||
footer.setVisibility(GONE);
|
||||
if (sharedContactStub.resolved()) sharedContactStub.get().getFooter().setVisibility(GONE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().getFooter().setVisibility(GONE);
|
||||
|
||||
boolean differentMinutes = next.isPresent() && !DateUtils.isSameBriefRelativeTimestamp(context, locale, next.get().getTimestamp(), current.getTimestamp());
|
||||
if (current.isOutgoing() || current.getExpiresIn() > 0 || !current.isSecure() || differentMinutes || isEndOfMessageCluster(current, next, isGroupThread)) {
|
||||
ConversationItemFooter activeFooter = getActiveFooter(current);
|
||||
activeFooter.setVisibility(VISIBLE);
|
||||
activeFooter.setMessageRecord(current, locale);
|
||||
} else if (!TextUtils.isEmpty(messageRecord.getDisplayBody())) {
|
||||
ViewUtil.setPaddingBottom(bodyBubble, readDimen(R.dimen.message_bubble_collapsed_footer_padding));
|
||||
}
|
||||
}
|
||||
|
||||
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
|
||||
if (hasSharedContact(messageRecord)) {
|
||||
return sharedContactStub.get().getFooter();
|
||||
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody())) {
|
||||
return mediaThumbnailStub.get().getFooter();
|
||||
} else {
|
||||
return footer;
|
||||
}
|
||||
}
|
||||
|
||||
private int readDimen(@DimenRes int dimenId) {
|
||||
return context.getResources().getDimensionPixelOffset(dimenId);
|
||||
}
|
||||
|
||||
private void setFailedStatusIcons() {
|
||||
alertView.setFailed();
|
||||
deliveryStatusIndicator.setNone();
|
||||
dateText.setText(R.string.ConversationItem_error_not_delivered);
|
||||
|
||||
if (messageRecord.isOutgoing()) {
|
||||
indicatorText.setText(R.string.ConversationItem_click_for_details);
|
||||
indicatorText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setFallbackStatusIcons() {
|
||||
alertView.setPendingApproval();
|
||||
deliveryStatusIndicator.setNone();
|
||||
indicatorText.setVisibility(View.VISIBLE);
|
||||
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
||||
}
|
||||
|
||||
private void setMinimumWidth() {
|
||||
if (indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) {
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
bodyBubble.setMinimumWidth(indicatorText.getText().length() * (int) (6.5 * density) + (int) (22.0 * density));
|
||||
} else {
|
||||
bodyBubble.setMinimumWidth(0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldInterceptClicks(MessageRecord messageRecord) {
|
||||
@@ -640,24 +695,97 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
|
||||
if (groupThread && !messageRecord.isOutgoing()) {
|
||||
this.groupSender.setText(recipient.toShortString());
|
||||
private void presentGroupMessageStatus(@NonNull MessageRecord current, @NonNull Recipient recipient) {
|
||||
this.groupSender.setText(recipient.toShortString());
|
||||
|
||||
if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) {
|
||||
this.groupSenderProfileName.setText("~" + recipient.getProfileName());
|
||||
this.groupSenderProfileName.setVisibility(View.VISIBLE);
|
||||
if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName()) && !current.isOutgoing()) {
|
||||
this.groupSenderProfileName.setText("~" + recipient.getProfileName());
|
||||
this.groupSenderProfileName.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
this.groupSenderProfileName.setText(null);
|
||||
this.groupSenderProfileName.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAuthor(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
if (isGroupThread && !current.isOutgoing()) {
|
||||
if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress())) {
|
||||
groupSenderHolder.setVisibility(VISIBLE);
|
||||
ViewUtil.setPaddingTop(bodyBubble, readDimen(R.dimen.message_bubble_top_padding));
|
||||
} else {
|
||||
this.groupSenderProfileName.setText(null);
|
||||
this.groupSenderProfileName.setVisibility(View.GONE);
|
||||
groupSenderHolder.setVisibility(GONE);
|
||||
}
|
||||
|
||||
this.groupSenderHolder.setVisibility(View.VISIBLE);
|
||||
if (!next.isPresent() || next.get().isUpdate() || !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress())) {
|
||||
contactPhoto.setVisibility(VISIBLE);
|
||||
} else {
|
||||
contactPhoto.setVisibility(GONE);
|
||||
}
|
||||
} else {
|
||||
this.groupSenderHolder.setVisibility(View.GONE);
|
||||
groupSenderHolder.setVisibility(GONE);
|
||||
|
||||
if (contactPhoto != null) {
|
||||
contactPhoto.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getCornerBackgroundRes(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
if (isSingularMessage(current, previous, next, isGroupThread)) {
|
||||
return current.isOutgoing() ? R.drawable.message_bubble_background_sent_alone
|
||||
: R.drawable.message_bubble_background_received_alone;
|
||||
} else if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
||||
return current.isOutgoing() ? R.drawable.message_bubble_background_sent_start
|
||||
: R.drawable.message_bubble_background_received_start;
|
||||
} else if (isEndOfMessageCluster(current, next, isGroupThread)) {
|
||||
return current.isOutgoing() ? R.drawable.message_bubble_background_sent_end
|
||||
: R.drawable.message_bubble_background_received_end;
|
||||
} else {
|
||||
return current.isOutgoing() ? R.drawable.message_bubble_background_sent_middle
|
||||
: R.drawable.message_bubble_background_received_middle;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStartOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, boolean isGroupThread) {
|
||||
if (isGroupThread) {
|
||||
return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) ||
|
||||
!current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress());
|
||||
} else {
|
||||
return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) ||
|
||||
current.isOutgoing() != previous.get().isOutgoing();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEndOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
if (isGroupThread) {
|
||||
return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) ||
|
||||
!current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress());
|
||||
} else {
|
||||
return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) ||
|
||||
current.isOutgoing() != next.get().isOutgoing();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSingularMessage(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||
return isStartOfMessageCluster(current, previous, isGroupThread) && isEndOfMessageCluster(current, next, isGroupThread);
|
||||
}
|
||||
|
||||
private int getMessageSpacing(@NonNull Context context, @NonNull MessageRecord current, @NonNull Optional<MessageRecord> next) {
|
||||
if (next.isPresent()) {
|
||||
boolean recipientsMatch = current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress());
|
||||
boolean outgoingMatch = current.isOutgoing() == next.get().isOutgoing();
|
||||
|
||||
if (!recipientsMatch || !outgoingMatch) {
|
||||
return readDimen(context, R.dimen.conversation_vertical_message_spacing_default);
|
||||
}
|
||||
}
|
||||
return readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse);
|
||||
}
|
||||
|
||||
private int readDimen(@NonNull Context context, @DimenRes int dimenId) {
|
||||
return context.getResources().getDimensionPixelOffset(dimenId);
|
||||
}
|
||||
|
||||
/// Event handlers
|
||||
|
||||
private void handleApproveIdentity() {
|
||||
@@ -673,11 +801,10 @@ public class ConversationItem extends LinearLayout
|
||||
@Override
|
||||
public void onModified(final Recipient modified) {
|
||||
Util.runOnMain(() -> {
|
||||
setBubbleState(messageRecord, recipient);
|
||||
setContactPhoto(recipient);
|
||||
setGroupMessageStatus(messageRecord, recipient);
|
||||
presentBubbleState(messageRecord);
|
||||
presentContactPhoto(recipient);
|
||||
presentGroupMessageStatus(messageRecord, recipient);
|
||||
setAudioViewTint(messageRecord, conversationRecipient);
|
||||
setDocumentViewTint(messageRecord, conversationRecipient);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -133,7 +133,6 @@ public class ConversationListFragment extends Fragment
|
||||
list.setHasFixedSize(true);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setItemAnimator(new DeleteItemAnimator());
|
||||
list.addItemDecoration(new InsetDividerItemDecoration(getActivity()));
|
||||
|
||||
new ItemTouchHelper(new ArchiveListenerCallback()).attachToRecyclerView(list);
|
||||
|
||||
@@ -574,63 +573,6 @@ public class ConversationListFragment extends Fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsetDividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private Drawable divider;
|
||||
private final Rect bounds = new Rect();
|
||||
|
||||
InsetDividerItemDecoration(Context context) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(new int[]{R.attr.conversation_list_item_divider});
|
||||
this.divider = typedArray.getDrawable(0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
|
||||
if (parent.getLayoutManager() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.save();
|
||||
|
||||
final int left;
|
||||
final int right;
|
||||
|
||||
if (parent.getClipToPadding()) {
|
||||
left = parent.getPaddingLeft();
|
||||
right = parent.getWidth() - parent.getPaddingRight();
|
||||
canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom());
|
||||
} else {
|
||||
left = 0;
|
||||
right = parent.getWidth();
|
||||
}
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
|
||||
for (int i = 0; i < childCount-1; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds);
|
||||
final int bottom = bounds.bottom + Math.round(child.getTranslationY());
|
||||
final int top = bottom - divider.getIntrinsicHeight();
|
||||
divider.setBounds(left, top, right, bottom);
|
||||
divider.draw(canvas);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
if (divider == null) {
|
||||
outRect.set(0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
outRect.set(0, 0, 0, divider.getIntrinsicHeight());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -67,8 +67,8 @@ public class ConversationListItem extends RelativeLayout
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = ConversationListItem.class.getSimpleName();
|
||||
|
||||
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif", Typeface.BOLD);
|
||||
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif-light", Typeface.NORMAL);
|
||||
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
||||
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
|
||||
|
||||
private Set<Long> selectedThreads;
|
||||
private Recipient recipient;
|
||||
|
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -40,6 +41,7 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
private Set<MessageRecord> batchSelected;
|
||||
|
||||
private ImageView icon;
|
||||
private TextView title;
|
||||
private TextView body;
|
||||
private TextView date;
|
||||
private Recipient sender;
|
||||
@@ -58,20 +60,23 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
this.icon = findViewById(R.id.conversation_update_icon);
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.date = findViewById(R.id.conversation_update_date);
|
||||
this.icon = findViewById(R.id.conversation_update_icon);
|
||||
this.title = findViewById(R.id.conversation_update_title);
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.date = findViewById(R.id.conversation_update_date);
|
||||
|
||||
this.setOnClickListener(new InternalClickListener(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseUpdate)
|
||||
public void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
boolean pulseUpdate)
|
||||
{
|
||||
this.batchSelected = batchSelected;
|
||||
|
||||
@@ -116,27 +121,37 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getDateReceived()));
|
||||
|
||||
title.setVisibility(GONE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setTimerRecord(final MessageRecord messageRecord) {
|
||||
if (messageRecord.getExpiresIn() > 0) {
|
||||
icon.setImageResource(R.drawable.ic_timer_white_24dp);
|
||||
icon.setImageResource(R.drawable.ic_timer);
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
} else {
|
||||
icon.setImageResource(R.drawable.ic_timer_off_white_24dp);
|
||||
icon.setImageResource(R.drawable.ic_timer_disabled);
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
}
|
||||
|
||||
title.setText(ExpirationUtil.getExpirationDisplayValue(getContext(), (int)(messageRecord.getExpiresIn() / 1000)));
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
|
||||
title.setVisibility(VISIBLE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void setIdentityRecord(final MessageRecord messageRecord) {
|
||||
icon.setImageResource(R.drawable.ic_security_white_24dp);
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
|
||||
title.setVisibility(GONE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void setIdentityVerifyUpdate(final MessageRecord messageRecord) {
|
||||
@@ -145,7 +160,10 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
|
||||
title.setVisibility(GONE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void setGroupRecord(MessageRecord messageRecord) {
|
||||
@@ -155,21 +173,29 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
GroupUtil.getDescription(getContext(), messageRecord.getBody()).addListener(this);
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
|
||||
date.setVisibility(View.GONE);
|
||||
title.setVisibility(GONE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void setJoinedRecord(MessageRecord messageRecord) {
|
||||
icon.setImageResource(R.drawable.ic_favorite_grey600_24dp);
|
||||
icon.clearColorFilter();
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
|
||||
title.setVisibility(GONE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void setEndSessionRecord(MessageRecord messageRecord) {
|
||||
icon.setImageResource(R.drawable.ic_refresh_white_24dp);
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
|
||||
title.setVisibility(GONE);
|
||||
body.setVisibility(VISIBLE);
|
||||
date.setVisibility(GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.sql.Date;
|
||||
@@ -252,7 +253,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
toFromRes = R.string.message_details_header__from;
|
||||
}
|
||||
toFrom.setText(toFromRes);
|
||||
conversationItem.bind(messageRecord, glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, false);
|
||||
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, false);
|
||||
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
|
||||
}
|
||||
|
||||
|
@@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
@@ -347,8 +348,10 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
vibrateCallPreference.setSummary(vibrateCallSummary.first);
|
||||
vibrateCallPreference.setValueIndex(vibrateCallSummary.second);
|
||||
|
||||
colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(getActivity()));
|
||||
colorPreference.setColor(recipient.getColor().toActionBarColor(getActivity()));
|
||||
|
||||
if (recipient.isGroupRecipient()) {
|
||||
if (colorPreference != null) colorPreference.setVisible(false);
|
||||
if (blockPreference != null) blockPreference.setVisible(false);
|
||||
if (identityPreference != null) identityPreference.setVisible(false);
|
||||
if (privacyCategory != null) privacyCategory.setVisible(false);
|
||||
@@ -540,7 +543,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientDatabase(context).setColor(recipient, selectedColor);
|
||||
|
||||
if (recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
if (recipient.resolve().isGroupRecipient()) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new MultiDeviceGroupUpdateJob(context));
|
||||
} else if (recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new MultiDeviceContactUpdateJob(context, recipient.getAddress()));
|
||||
|
@@ -6,32 +6,45 @@ import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.thoughtcrime.securesms.util.ThemeUtil.isDarkTheme;
|
||||
|
||||
public enum MaterialColor {
|
||||
RED (R.color.red_400, R.color.red_700, R.color.red_700, R.color.red_900, "red"),
|
||||
PINK (R.color.pink_400, R.color.pink_700, R.color.pink_700, R.color.pink_900, "pink"),
|
||||
PURPLE (R.color.purple_400, R.color.purple_700, R.color.purple_700, R.color.purple_900, "purple"),
|
||||
DEEP_PURPLE(R.color.deep_purple_400, R.color.deep_purple_700, R.color.deep_purple_700, R.color.deep_purple_900, "deep_purple"),
|
||||
INDIGO (R.color.indigo_400, R.color.indigo_700, R.color.indigo_700, R.color.indigo_900, "indigo"),
|
||||
BLUE (R.color.blue_500, R.color.blue_800, R.color.blue_700, R.color.blue_900, "blue"),
|
||||
LIGHT_BLUE (R.color.light_blue_500, R.color.light_blue_800, R.color.light_blue_700, R.color.light_blue_900, "light_blue"),
|
||||
CYAN (R.color.cyan_500, R.color.cyan_700, R.color.cyan_700, R.color.cyan_900, "cyan"),
|
||||
TEAL (R.color.teal_500, R.color.teal_700, R.color.teal_700, R.color.teal_900, "teal"),
|
||||
GREEN (R.color.green_500, R.color.green_700, R.color.green_700, R.color.green_900, "green"),
|
||||
LIGHT_GREEN(R.color.light_green_600, R.color.light_green_700, R.color.light_green_700, R.color.light_green_900, "light_green"),
|
||||
LIME (R.color.lime_500, R.color.lime_700, R.color.lime_700, R.color.lime_900, "lime"),
|
||||
YELLOW (R.color.yellow_500, R.color.yellow_700, R.color.yellow_700, R.color.yellow_900, "yellow"),
|
||||
AMBER (R.color.amber_600, R.color.amber_700, R.color.amber_700, R.color.amber_900, "amber"),
|
||||
ORANGE (R.color.orange_500, R.color.orange_700, R.color.orange_700, R.color.orange_900, "orange"),
|
||||
DEEP_ORANGE(R.color.deep_orange_500, R.color.deep_orange_700, R.color.deep_orange_700, R.color.deep_orange_900, "deep_orange"),
|
||||
BROWN (R.color.brown_500, R.color.brown_700, R.color.brown_700, R.color.brown_900, "brown"),
|
||||
GREY (R.color.grey_500, R.color.grey_700, R.color.grey_700, R.color.grey_900, "grey"),
|
||||
BLUE_GREY (R.color.blue_grey_500, R.color.blue_grey_700, R.color.blue_grey_700, R.color.blue_grey_900, "blue_grey"),
|
||||
RED (R.color.conversation_red, R.color.conversation_red_shade, "red"),
|
||||
PINK (R.color.conversation_pink, R.color.conversation_pink_shade, "pink"),
|
||||
PURPLE (R.color.conversation_purple, R.color.conversation_purple_shade, "purple"),
|
||||
INDIGO (R.color.conversation_indigo, R.color.conversation_indigo_shade, "indigo"),
|
||||
BLUE (R.color.conversation_blue, R.color.conversation_blue_shade, "blue"),
|
||||
CYAN (R.color.conversation_cyan, R.color.conversation_cyan_shade, "cyan"),
|
||||
TEAL (R.color.conversation_teal, R.color.conversation_teal_shade, "teal"),
|
||||
GREEN (R.color.conversation_green, R.color.conversation_green_shade, "green"),
|
||||
ORANGE (R.color.conversation_orange, R.color.conversation_orange_shade, "orange"),
|
||||
GREY (R.color.conversation_grey, R.color.conversation_grey_shade, "grey");
|
||||
|
||||
GROUP (GREY.conversationColorLight, R.color.textsecure_primary, R.color.textsecure_primary_dark,
|
||||
GREY.conversationColorDark, R.color.gray95, R.color.black,
|
||||
"group_color");
|
||||
private static final Map<String, MaterialColor> COLOR_MATCHES = new HashMap<String, MaterialColor>() {{
|
||||
put("red", RED);
|
||||
put("brown", RED);
|
||||
put("pink", PINK);
|
||||
put("purple", PURPLE);
|
||||
put("deep_purple", PURPLE);
|
||||
put("indigo", INDIGO);
|
||||
put("blue", BLUE);
|
||||
put("light_blue", BLUE);
|
||||
put("cyan", CYAN);
|
||||
put("blue_grey", CYAN);
|
||||
put("teal", TEAL);
|
||||
put("green", GREEN);
|
||||
put("light_green", GREEN);
|
||||
put("lime", GREEN);
|
||||
put("orange", ORANGE);
|
||||
put("amber", ORANGE);
|
||||
put("deep_orange", ORANGE);
|
||||
put("yellow", ORANGE);
|
||||
put("grey", GREY);
|
||||
put("group_color", BLUE);
|
||||
}};
|
||||
|
||||
private final int conversationColorLight;
|
||||
private final int actionBarColorLight;
|
||||
@@ -55,11 +68,10 @@ public enum MaterialColor {
|
||||
this.serialized = serialized;
|
||||
}
|
||||
|
||||
MaterialColor(int lightColor, int darkColor,
|
||||
int lightStatusBarColor, int darkStatusBarColor, String serialized)
|
||||
MaterialColor(int conversationColor, int statusBarColor, String serialized)
|
||||
{
|
||||
this(lightColor, lightColor, lightStatusBarColor,
|
||||
darkColor, darkColor, darkStatusBarColor, serialized);
|
||||
this(conversationColor, conversationColor, statusBarColor,
|
||||
conversationColor, conversationColor, statusBarColor, serialized);
|
||||
}
|
||||
|
||||
public int toConversationColor(@NonNull Context context) {
|
||||
@@ -77,10 +89,6 @@ public enum MaterialColor {
|
||||
: statusBarColorLight);
|
||||
}
|
||||
|
||||
public int toQuoteTitleColor(@NonNull Context context) {
|
||||
return context.getResources().getColor(conversationColorDark);
|
||||
}
|
||||
|
||||
public int toQuoteBarColorResource(@NonNull Context context, boolean outgoing) {
|
||||
if (outgoing) {
|
||||
return conversationColorDark;
|
||||
@@ -90,36 +98,15 @@ public enum MaterialColor {
|
||||
|
||||
public int toQuoteBackgroundColor(@NonNull Context context, boolean outgoing) {
|
||||
if (outgoing) {
|
||||
if (isDarkTheme(context)) {
|
||||
return context.getResources().getColor(R.color.transparent_white_60);
|
||||
} else {
|
||||
int color = toConversationColor(context);
|
||||
return Color.argb(0x44, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
int color = toConversationColor(context);
|
||||
int alpha = isDarkTheme(context) ? (int) (0.2 * 255) : (int) (0.4 * 255);
|
||||
|
||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
return context.getResources().getColor(isDarkTheme(context) ? R.color.transparent_white_70
|
||||
return context.getResources().getColor(isDarkTheme(context) ? R.color.transparent_black_70
|
||||
: R.color.transparent_white_aa);
|
||||
}
|
||||
|
||||
public int toQuoteOutlineColor(@NonNull Context context, boolean outgoing) {
|
||||
if (!outgoing) {
|
||||
return context.getResources().getColor(R.color.transparent_white_70);
|
||||
}
|
||||
return context.getResources().getColor(isDarkTheme(context) ? R.color.transparent_white_40
|
||||
: R.color.grey_400_transparent);
|
||||
}
|
||||
|
||||
public int toQuoteIconForegroundColor(@NonNull Context context, boolean outgoing) {
|
||||
if (outgoing) {
|
||||
return context.getResources().getColor(R.color.white);
|
||||
}
|
||||
return toConversationColor(context);
|
||||
}
|
||||
|
||||
public int toQuoteIconBackgroundColor(@NonNull Context context, boolean outgoing) {
|
||||
return context.getResources().getColor(toQuoteBarColorResource(context, outgoing));
|
||||
}
|
||||
|
||||
public boolean represents(Context context, int colorValue) {
|
||||
return context.getResources().getColor(conversationColorDark) == colorValue ||
|
||||
context.getResources().getColor(conversationColorLight) == colorValue ||
|
||||
@@ -134,8 +121,8 @@ public enum MaterialColor {
|
||||
}
|
||||
|
||||
public static MaterialColor fromSerialized(String serialized) throws UnknownColorException {
|
||||
for (MaterialColor color : MaterialColor.values()) {
|
||||
if (color.serialized.equals(serialized)) return color;
|
||||
if (COLOR_MATCHES.containsKey(serialized)) {
|
||||
return COLOR_MATCHES.get(serialized);
|
||||
}
|
||||
|
||||
throw new UnknownColorException("Unknown color: " + serialized);
|
||||
|
@@ -11,28 +11,15 @@ import java.util.List;
|
||||
public class MaterialColors {
|
||||
|
||||
public static final MaterialColorList CONVERSATION_PALETTE = new MaterialColorList(new ArrayList<>(Arrays.asList(
|
||||
MaterialColor.ORANGE,
|
||||
MaterialColor.RED,
|
||||
MaterialColor.PINK,
|
||||
MaterialColor.PURPLE,
|
||||
|
||||
MaterialColor.DEEP_PURPLE,
|
||||
MaterialColor.INDIGO,
|
||||
MaterialColor.BLUE,
|
||||
|
||||
MaterialColor.LIGHT_BLUE,
|
||||
MaterialColor.CYAN,
|
||||
MaterialColor.TEAL,
|
||||
MaterialColor.GREEN,
|
||||
MaterialColor.LIGHT_GREEN,
|
||||
// Lime
|
||||
// Yellow
|
||||
// Amber
|
||||
MaterialColor.ORANGE,
|
||||
MaterialColor.DEEP_ORANGE,
|
||||
// MaterialColor.BROWN,
|
||||
MaterialColor.AMBER,
|
||||
// Grey
|
||||
MaterialColor.BLUE_GREY
|
||||
MaterialColor.TEAL,
|
||||
MaterialColor.CYAN
|
||||
)));
|
||||
|
||||
public static class MaterialColorList {
|
||||
|
@@ -37,8 +37,8 @@ public class AlertView extends LinearLayout {
|
||||
private void initialize(AttributeSet attrs) {
|
||||
inflate(getContext(), R.layout.alert_view, this);
|
||||
|
||||
approvalIndicator = (ImageView) findViewById(R.id.pending_approval_indicator);
|
||||
failedIndicator = (ImageView) findViewById(R.id.sms_failed_indicator);
|
||||
approvalIndicator = findViewById(R.id.pending_approval_indicator);
|
||||
failedIndicator = findViewById(R.id.sms_failed_indicator);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.AlertView, 0, 0);
|
||||
@@ -46,7 +46,10 @@ public class AlertView extends LinearLayout {
|
||||
typedArray.recycle();
|
||||
|
||||
if (useSmallIcon) {
|
||||
failedIndicator.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_error_red_18dp));
|
||||
int size = getResources().getDimensionPixelOffset(R.dimen.alertview_small_icon_size);
|
||||
failedIndicator.getLayoutParams().width = size;
|
||||
failedIndicator.getLayoutParams().height = size;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,172 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
|
||||
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ConversationItemFooter extends LinearLayout {
|
||||
|
||||
private TextView dateView;
|
||||
private TextView simView;
|
||||
private ExpirationTimerView timerView;
|
||||
private ImageView insecureIndicatorView;
|
||||
private DeliveryStatusView deliveryStatusView;
|
||||
|
||||
public ConversationItemFooter(Context context) {
|
||||
super(context);
|
||||
init(null);
|
||||
}
|
||||
|
||||
public ConversationItemFooter(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
public ConversationItemFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
private void init(@Nullable AttributeSet attrs) {
|
||||
inflate(getContext(), R.layout.conversation_item_footer, this);
|
||||
|
||||
dateView = findViewById(R.id.footer_date);
|
||||
simView = findViewById(R.id.footer_sim_info);
|
||||
timerView = findViewById(R.id.footer_expiration_timer);
|
||||
insecureIndicatorView = findViewById(R.id.footer_insecure_indicator);
|
||||
deliveryStatusView = findViewById(R.id.footer_delivery_status);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemFooter, 0, 0);
|
||||
setColor(typedArray.getInt(R.styleable.ConversationItemFooter_footer_color, getResources().getColor(R.color.core_white)));
|
||||
typedArray.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
timerView.stopAnimation();
|
||||
}
|
||||
|
||||
public void setMessageRecord(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
||||
presentDate(messageRecord, locale);
|
||||
presentSimInfo(messageRecord);
|
||||
presentTimer(messageRecord);
|
||||
presentInsecureIndicator(messageRecord);
|
||||
presentDeliveryStatus(messageRecord);
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
dateView.setTextColor(color);
|
||||
simView.setTextColor(color);
|
||||
timerView.setColorFilter(color);
|
||||
insecureIndicatorView.setColorFilter(color);
|
||||
deliveryStatusView.setTint(color);
|
||||
}
|
||||
|
||||
private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale) {
|
||||
dateView.forceLayout();
|
||||
|
||||
if (messageRecord.isFailed()) {
|
||||
dateView.setText(R.string.ConversationItem_error_not_delivered);
|
||||
} else if (messageRecord.isPendingInsecureSmsFallback()) {
|
||||
dateView.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
||||
} else {
|
||||
dateView.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getTimestamp()));
|
||||
}
|
||||
}
|
||||
|
||||
private void presentSimInfo(@NonNull MessageRecord messageRecord) {
|
||||
SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(getContext());
|
||||
|
||||
if (messageRecord.getSubscriptionId() == -1 || !Permissions.hasAll(getContext(), Manifest.permission.READ_PHONE_STATE) || subscriptionManager.getActiveSubscriptionInfoList().size() < 2) {
|
||||
simView.setVisibility(View.GONE);
|
||||
} else {
|
||||
Optional<SubscriptionInfoCompat> subscriptionInfo = subscriptionManager.getActiveSubscriptionInfo(messageRecord.getSubscriptionId());
|
||||
|
||||
if (subscriptionInfo.isPresent() && messageRecord.isOutgoing()) {
|
||||
simView.setText(getContext().getString(R.string.ConversationItem_from_s, subscriptionInfo.get().getDisplayName()));
|
||||
simView.setVisibility(View.VISIBLE);
|
||||
} else if (subscriptionInfo.isPresent()) {
|
||||
simView.setText(getContext().getString(R.string.ConversationItem_to_s, subscriptionInfo.get().getDisplayName()));
|
||||
simView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
simView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void presentTimer(@NonNull final MessageRecord messageRecord) {
|
||||
if (messageRecord.getExpiresIn() > 0 && !messageRecord.isPending()) {
|
||||
this.timerView.setVisibility(View.VISIBLE);
|
||||
this.timerView.setPercentComplete(0);
|
||||
|
||||
if (messageRecord.getExpireStarted() > 0) {
|
||||
this.timerView.setExpirationTime(messageRecord.getExpireStarted(),
|
||||
messageRecord.getExpiresIn());
|
||||
this.timerView.startAnimation();
|
||||
|
||||
if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) {
|
||||
ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule();
|
||||
}
|
||||
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(getContext()).getExpiringMessageManager();
|
||||
long id = messageRecord.getId();
|
||||
boolean mms = messageRecord.isMms();
|
||||
|
||||
if (mms) DatabaseFactory.getMmsDatabase(getContext()).markExpireStarted(id);
|
||||
else DatabaseFactory.getSmsDatabase(getContext()).markExpireStarted(id);
|
||||
|
||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
} else {
|
||||
this.timerView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentInsecureIndicator(@NonNull MessageRecord messageRecord) {
|
||||
insecureIndicatorView.setVisibility(messageRecord.isSecure() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void presentDeliveryStatus(@NonNull MessageRecord messageRecord) {
|
||||
if (!messageRecord.isFailed() && !messageRecord.isPendingInsecureSmsFallback()) {
|
||||
if (!messageRecord.isOutgoing()) deliveryStatusView.setNone();
|
||||
else if (messageRecord.isPending()) deliveryStatusView.setPending();
|
||||
else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead();
|
||||
else if (messageRecord.isDelivered()) deliveryStatusView.setDelivered();
|
||||
else deliveryStatusView.setSent();
|
||||
} else {
|
||||
deliveryStatusView.setNone();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
|
||||
public class ConversationItemThumbnail extends FrameLayout {
|
||||
|
||||
private static final String TAG = ConversationItemThumbnail.class.getSimpleName();
|
||||
|
||||
private ThumbnailView thumbnail;
|
||||
private ImageView shade;
|
||||
private CornerMaskingView cornerMask;
|
||||
private ConversationItemFooter footer;
|
||||
|
||||
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.shade = findViewById(R.id.conversation_thumbnail_shade);
|
||||
this.cornerMask = findViewById(R.id.conversation_thumbnail_corner_mask);
|
||||
this.footer = findViewById(R.id.conversation_thumbnail_footer);
|
||||
|
||||
setCornerRadius(getResources().getDimensionPixelSize(R.dimen.message_corner_radius));
|
||||
setTouchDelegate(thumbnail.getTouchDelegate());
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
|
||||
thumbnail.setBounds(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));
|
||||
typedArray.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (getMeasuredWidth() != thumbnail.getMeasuredWidth()) {
|
||||
getLayoutParams().width = shade.getLayoutParams().width = thumbnail.getMeasuredWidth();
|
||||
measure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocusable(boolean focusable) {
|
||||
thumbnail.setFocusable(focusable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClickable(boolean clickable) {
|
||||
thumbnail.setClickable(clickable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
|
||||
thumbnail.setOnLongClickListener(l);
|
||||
}
|
||||
|
||||
public void showShade(boolean show) {
|
||||
shade.setVisibility(show ? VISIBLE : GONE);
|
||||
forceLayout();
|
||||
}
|
||||
|
||||
public ConversationItemFooter getFooter() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
public void setCornerRadius(int radius) {
|
||||
setCornerRadii(radius, radius, radius, radius);
|
||||
}
|
||||
|
||||
public void setCornerRadii(int topLeft, int topRight, int bottomRight, int bottomLeft) {
|
||||
cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft);
|
||||
}
|
||||
|
||||
public void setImageBackground(@DrawableRes int resId) {
|
||||
thumbnail.setImageBackground(resId);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview)
|
||||
{
|
||||
thumbnail.setImageResource(glideRequests, slide, showControls, isPreview);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview, int naturalWidth,
|
||||
int naturalHeight)
|
||||
{
|
||||
thumbnail.setImageResource(glideRequests, slide, showControls, isPreview, naturalWidth, naturalHeight);
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
thumbnail.setImageResource(glideRequests, uri);
|
||||
}
|
||||
|
||||
public void setThumbnailClickListener(SlideClickListener listener) {
|
||||
thumbnail.setThumbnailClickListener(listener);
|
||||
}
|
||||
|
||||
public void setDownloadClickListener(SlideClickListener listener) {
|
||||
thumbnail.setDownloadClickListener(listener);
|
||||
}
|
||||
|
||||
public void clear(GlideRequests glideRequests) {
|
||||
thumbnail.clear(glideRequests);
|
||||
}
|
||||
|
||||
public void showProgressSpinner() {
|
||||
thumbnail.showProgressSpinner();
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class CornerMaskingView extends FrameLayout {
|
||||
|
||||
private final float[] radii = new float[8];
|
||||
private final Paint paint = new Paint();
|
||||
private final Path corners = new Path();
|
||||
private final RectF bounds = new RectF();
|
||||
|
||||
public CornerMaskingView(@NonNull Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CornerMaskingView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CornerMaskingView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.right = canvas.getWidth();
|
||||
bounds.bottom = canvas.getHeight();
|
||||
|
||||
corners.reset();
|
||||
corners.addRoundRect(bounds, radii, Path.Direction.CW);
|
||||
|
||||
canvas.drawPath(corners, paint);
|
||||
}
|
||||
|
||||
public void setRadius(int radius) {
|
||||
setRadii(radius, radius, radius, radius);
|
||||
}
|
||||
|
||||
public void setRadii(int topLeft, int topRight, int bottomRight, int bottomLeft) {
|
||||
radii[0] = radii[1] = topLeft;
|
||||
radii[2] = radii[3] = topRight;
|
||||
radii[4] = radii[5] = bottomRight;
|
||||
radii[6] = radii[7] = bottomLeft;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setTopLeftRadius(int radius) {
|
||||
radii[0] = radii[1] = radius;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setTopRightRadius(int radius) {
|
||||
radii[2] = radii[3] = radius;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setBottomRightRadius(int radius) {
|
||||
radii[4] = radii[5] = radius;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setBottomLeftRadius(int radius) {
|
||||
radii[6] = radii[7] = radius;
|
||||
invalidate();
|
||||
}
|
||||
}
|
@@ -19,7 +19,7 @@ public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
private static final String TAG = DeliveryStatusView.class.getSimpleName();
|
||||
|
||||
private final ViewGroup pendingIndicatorStub;
|
||||
private final ImageView pendingIndicator;
|
||||
private final ImageView sentIndicator;
|
||||
private final ImageView deliveredIndicator;
|
||||
private final ImageView readIndicator;
|
||||
@@ -37,31 +37,16 @@ public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
inflate(context, R.layout.delivery_status_view, this);
|
||||
|
||||
this.deliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator);
|
||||
this.sentIndicator = (ImageView) findViewById(R.id.sent_indicator);
|
||||
this.pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub);
|
||||
this.readIndicator = (ImageView) findViewById(R.id.read_indicator);
|
||||
|
||||
int iconColor = Color.GRAY;
|
||||
this.deliveredIndicator = findViewById(R.id.delivered_indicator);
|
||||
this.sentIndicator = findViewById(R.id.sent_indicator);
|
||||
this.pendingIndicator = findViewById(R.id.pending_indicator);
|
||||
this.readIndicator = findViewById(R.id.read_indicator);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DeliveryStatusView, 0, 0);
|
||||
iconColor = typedArray.getColor(R.styleable.DeliveryStatusView_iconColor, iconColor);
|
||||
setTint(typedArray.getColor(R.styleable.DeliveryStatusView_iconColor, getResources().getColor(R.color.core_white)));
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
deliveredIndicator.setColorFilter(iconColor, android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||
sentIndicator.setColorFilter(iconColor, android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
inflate(context, R.layout.conversation_item_pending_v11, pendingIndicatorStub);
|
||||
DotsTextView pendingIndicator = (DotsTextView) findViewById(R.id.pending_indicator);
|
||||
pendingIndicator.setDotsColor(iconColor);
|
||||
} else {
|
||||
inflate(context, R.layout.conversation_item_pending, pendingIndicatorStub);
|
||||
TextView pendingIndicator = (TextView) findViewById(R.id.pending_indicator);
|
||||
pendingIndicator.setTextColor(iconColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNone() {
|
||||
@@ -70,7 +55,7 @@ public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
public void setPending() {
|
||||
this.setVisibility(View.VISIBLE);
|
||||
pendingIndicatorStub.setVisibility(View.VISIBLE);
|
||||
pendingIndicator.setVisibility(View.VISIBLE);
|
||||
sentIndicator.setVisibility(View.GONE);
|
||||
deliveredIndicator.setVisibility(View.GONE);
|
||||
readIndicator.setVisibility(View.GONE);
|
||||
@@ -78,7 +63,7 @@ public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
public void setSent() {
|
||||
this.setVisibility(View.VISIBLE);
|
||||
pendingIndicatorStub.setVisibility(View.GONE);
|
||||
pendingIndicator.setVisibility(View.GONE);
|
||||
sentIndicator.setVisibility(View.VISIBLE);
|
||||
deliveredIndicator.setVisibility(View.GONE);
|
||||
readIndicator.setVisibility(View.GONE);
|
||||
@@ -86,7 +71,7 @@ public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
public void setDelivered() {
|
||||
this.setVisibility(View.VISIBLE);
|
||||
pendingIndicatorStub.setVisibility(View.GONE);
|
||||
pendingIndicator.setVisibility(View.GONE);
|
||||
sentIndicator.setVisibility(View.GONE);
|
||||
deliveredIndicator.setVisibility(View.VISIBLE);
|
||||
readIndicator.setVisibility(View.GONE);
|
||||
@@ -94,9 +79,16 @@ public class DeliveryStatusView extends FrameLayout {
|
||||
|
||||
public void setRead() {
|
||||
this.setVisibility(View.VISIBLE);
|
||||
pendingIndicatorStub.setVisibility(View.GONE);
|
||||
pendingIndicator.setVisibility(View.GONE);
|
||||
sentIndicator.setVisibility(View.GONE);
|
||||
deliveredIndicator.setVisibility(View.GONE);
|
||||
readIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void setTint(int color) {
|
||||
pendingIndicator.setColorFilter(color);
|
||||
deliveredIndicator.setColorFilter(color);
|
||||
sentIndicator.setColorFilter(color);
|
||||
readIndicator.setColorFilter(color);
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
@@ -34,8 +35,8 @@ public class DocumentView extends FrameLayout {
|
||||
private final @NonNull AnimatingToggle controlToggle;
|
||||
private final @NonNull ImageView downloadButton;
|
||||
private final @NonNull ProgressWheel downloadProgress;
|
||||
private final @NonNull View documentBackground;
|
||||
private final @NonNull View container;
|
||||
private final @NonNull ViewGroup iconContainer;
|
||||
private final @NonNull TextView fileName;
|
||||
private final @NonNull TextView fileSize;
|
||||
private final @NonNull TextView document;
|
||||
@@ -56,24 +57,23 @@ public class DocumentView extends FrameLayout {
|
||||
super(context, attrs, defStyleAttr);
|
||||
inflate(context, R.layout.document_view, this);
|
||||
|
||||
this.container = findViewById(R.id.document_container);
|
||||
this.controlToggle = (AnimatingToggle) findViewById(R.id.control_toggle);
|
||||
this.downloadButton = (ImageView) findViewById(R.id.download);
|
||||
this.downloadProgress = (ProgressWheel) findViewById(R.id.download_progress);
|
||||
this.fileName = (TextView) findViewById(R.id.file_name);
|
||||
this.fileSize = (TextView) findViewById(R.id.file_size);
|
||||
this.documentBackground = findViewById(R.id.document_background);
|
||||
this.document = (TextView) findViewById(R.id.document);
|
||||
|
||||
this.document.getBackground().mutate();
|
||||
this.documentBackground.getBackground().mutate();
|
||||
this.container = findViewById(R.id.document_container);
|
||||
this.iconContainer = findViewById(R.id.icon_container);
|
||||
this.controlToggle = findViewById(R.id.control_toggle);
|
||||
this.downloadButton = findViewById(R.id.download);
|
||||
this.downloadProgress = findViewById(R.id.download_progress);
|
||||
this.fileName = findViewById(R.id.file_name);
|
||||
this.fileSize = findViewById(R.id.file_size);
|
||||
this.document = findViewById(R.id.document);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DocumentView, 0, 0);
|
||||
setTint(typedArray.getColor(R.styleable.DocumentView_documentForegroundTintColor, Color.WHITE),
|
||||
typedArray.getColor(R.styleable.DocumentView_documentBackgroundTintColor, Color.WHITE));
|
||||
container.setBackgroundColor(typedArray.getColor(R.styleable.DocumentView_documentWidgetBackground, Color.TRANSPARENT));
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.DocumentView, 0, 0);
|
||||
int titleColor = typedArray.getInt(R.styleable.DocumentView_doc_titleColor, Color.BLACK);
|
||||
int captionColor = typedArray.getInt(R.styleable.DocumentView_doc_captionColor, Color.BLACK);
|
||||
typedArray.recycle();
|
||||
|
||||
fileName.setTextColor(titleColor);
|
||||
fileSize.setTextColor(captionColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class DocumentView extends FrameLayout {
|
||||
controlToggle.displayQuick(downloadProgress);
|
||||
downloadProgress.spin();
|
||||
} else {
|
||||
controlToggle.displayQuick(documentBackground);
|
||||
controlToggle.displayQuick(iconContainer);
|
||||
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
|
||||
}
|
||||
|
||||
@@ -108,18 +108,6 @@ public class DocumentView extends FrameLayout {
|
||||
this.setOnClickListener(new OpenClickedListener(documentSlide));
|
||||
}
|
||||
|
||||
public void setTint(int foregroundTint, int backgroundTint) {
|
||||
DrawableCompat.setTint(this.document.getBackground(), backgroundTint);
|
||||
DrawableCompat.setTint(this.documentBackground.getBackground(), foregroundTint);
|
||||
this.document.setTextColor(foregroundTint);
|
||||
|
||||
this.fileName.setTextColor(foregroundTint);
|
||||
this.fileSize.setTextColor(foregroundTint);
|
||||
|
||||
this.downloadButton.setColorFilter(foregroundTint);
|
||||
this.downloadProgress.setBarColor(foregroundTint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocusable(boolean focusable) {
|
||||
super.setFocusable(focusable);
|
||||
|
@@ -5,12 +5,13 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ExpirationTimerView extends HourglassView {
|
||||
public class ExpirationTimerView extends android.support.v7.widget.AppCompatImageView {
|
||||
|
||||
private long startedAt;
|
||||
private long expiresIn;
|
||||
@@ -18,6 +19,20 @@ public class ExpirationTimerView extends HourglassView {
|
||||
private boolean visible = false;
|
||||
private boolean stopped = true;
|
||||
|
||||
private final int[] frames = new int[]{ R.drawable.timer00,
|
||||
R.drawable.timer05,
|
||||
R.drawable.timer10,
|
||||
R.drawable.timer15,
|
||||
R.drawable.timer20,
|
||||
R.drawable.timer25,
|
||||
R.drawable.timer30,
|
||||
R.drawable.timer35,
|
||||
R.drawable.timer40,
|
||||
R.drawable.timer45,
|
||||
R.drawable.timer50,
|
||||
R.drawable.timer55,
|
||||
R.drawable.timer60 };
|
||||
|
||||
public ExpirationTimerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -33,8 +48,13 @@ public class ExpirationTimerView extends HourglassView {
|
||||
public void setExpirationTime(long startedAt, long expiresIn) {
|
||||
this.startedAt = startedAt;
|
||||
this.expiresIn = expiresIn;
|
||||
setPercentComplete(calculateProgress(this.startedAt, this.expiresIn));
|
||||
}
|
||||
|
||||
setPercentage(calculateProgress(this.startedAt, this.expiresIn));
|
||||
public void setPercentComplete(float percentage) {
|
||||
float percentFull = 1 - percentage;
|
||||
int frame = (int) Math.ceil(percentFull * (frames.length - 1));
|
||||
setImageResource(frames[frame]);
|
||||
}
|
||||
|
||||
public void startAnimation() {
|
||||
@@ -57,7 +77,7 @@ public class ExpirationTimerView extends HourglassView {
|
||||
long progressed = System.currentTimeMillis() - startedAt;
|
||||
float percentComplete = (float)progressed / (float)expiresIn;
|
||||
|
||||
return percentComplete * 100;
|
||||
return percentComplete;
|
||||
}
|
||||
|
||||
private long calculateAnimationDelay(long startedAt, long expiresIn) {
|
||||
|
@@ -24,7 +24,6 @@ public class HourglassView extends View {
|
||||
|
||||
private Bitmap empty;
|
||||
private Bitmap full;
|
||||
private int tint;
|
||||
|
||||
private float percentage;
|
||||
private int offset;
|
||||
@@ -40,13 +39,15 @@ public class HourglassView extends View {
|
||||
public HourglassView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
int tint = 0;
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.HourglassView, 0, 0);
|
||||
this.empty = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.HourglassView_empty, 0));
|
||||
this.full = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.HourglassView_full, 0));
|
||||
this.tint = typedArray.getColor(R.styleable.HourglassView_tint, 0);
|
||||
this.percentage = typedArray.getInt(R.styleable.HourglassView_percentage, 50);
|
||||
this.offset = typedArray.getInt(R.styleable.HourglassView_offset, 0);
|
||||
tint = typedArray.getColor(R.styleable.HourglassView_tint, 0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
@@ -80,6 +81,8 @@ public class HourglassView extends View {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setTint(int tint) {
|
||||
this.backgroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
|
||||
this.foregroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
|
||||
}
|
||||
}
|
||||
|
@@ -125,8 +125,8 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR ? -.25f : .25f;
|
||||
|
||||
AnimationSet animation = new AnimationSet(true);
|
||||
animation.addAnimation(new TranslateAnimation(Animation.RELATIVE_TO_SELF, translation,
|
||||
Animation.RELATIVE_TO_SELF, translation,
|
||||
animation.addAnimation(new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, -.25f,
|
||||
Animation.RELATIVE_TO_SELF, -.25f));
|
||||
|
||||
@@ -145,11 +145,10 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
public void moveTo(float x) {
|
||||
this.lastPositionX = x;
|
||||
|
||||
float offset = getOffset(x);
|
||||
int widthAdjustment = getWidthAdjustment();
|
||||
float offset = getOffset(x);
|
||||
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, widthAdjustment + offset,
|
||||
Animation.ABSOLUTE, widthAdjustment + offset,
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, offset,
|
||||
Animation.ABSOLUTE, offset,
|
||||
Animation.RELATIVE_TO_SELF, -.25f,
|
||||
Animation.RELATIVE_TO_SELF, -.25f);
|
||||
|
||||
@@ -163,16 +162,15 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
public void hide(float x) {
|
||||
this.lastPositionX = x;
|
||||
|
||||
float offset = getOffset(x);
|
||||
int widthAdjustment = getWidthAdjustment();
|
||||
float offset = getOffset(x);
|
||||
|
||||
AnimationSet animation = new AnimationSet(false);
|
||||
Animation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, offset + widthAdjustment,
|
||||
Animation.ABSOLUTE, widthAdjustment,
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, offset,
|
||||
Animation.ABSOLUTE, 0,
|
||||
Animation.RELATIVE_TO_SELF, -.25f,
|
||||
Animation.RELATIVE_TO_SELF, -.25f);
|
||||
|
||||
|
@@ -3,12 +3,7 @@ package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -26,14 +21,10 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
@@ -49,16 +40,15 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
private static final int MESSAGE_TYPE_OUTGOING = 1;
|
||||
private static final int MESSAGE_TYPE_INCOMING = 2;
|
||||
|
||||
private View rootView;
|
||||
private TextView authorView;
|
||||
private TextView bodyView;
|
||||
private ImageView quoteBarView;
|
||||
private ImageView attachmentView;
|
||||
private ImageView attachmentVideoOverlayView;
|
||||
private ViewGroup attachmentIconContainerView;
|
||||
private ImageView attachmentIconView;
|
||||
private ImageView attachmentIconBackgroundView;
|
||||
private ImageView dismissView;
|
||||
private CornerMaskingView rootView;
|
||||
private TextView authorView;
|
||||
private TextView bodyView;
|
||||
private ImageView quoteBarView;
|
||||
private ImageView thumbnailView;
|
||||
private View attachmentVideoOverlayView;
|
||||
private ViewGroup attachmentContainerView;
|
||||
private TextView attachmentNameView;
|
||||
private ImageView dismissView;
|
||||
|
||||
private long id;
|
||||
private Recipient author;
|
||||
@@ -66,10 +56,9 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
private TextView mediaDescriptionText;
|
||||
private SlideDeck attachments;
|
||||
private int messageType;
|
||||
private int roundedCornerRadiusPx;
|
||||
private int largeCornerRadius;
|
||||
private int smallCornerRadius;
|
||||
|
||||
private final Path clipPath = new Path();
|
||||
private final RectF drawRect = new RectF();
|
||||
|
||||
public QuoteView(Context context) {
|
||||
super(context);
|
||||
@@ -99,21 +88,36 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
this.authorView = findViewById(R.id.quote_author);
|
||||
this.bodyView = findViewById(R.id.quote_text);
|
||||
this.quoteBarView = findViewById(R.id.quote_bar);
|
||||
this.attachmentView = findViewById(R.id.quote_attachment);
|
||||
this.thumbnailView = findViewById(R.id.quote_thumbnail);
|
||||
this.attachmentVideoOverlayView = findViewById(R.id.quote_video_overlay);
|
||||
this.attachmentIconContainerView = findViewById(R.id.quote_attachment_icon_container);
|
||||
this.attachmentIconView = findViewById(R.id.quote_attachment_icon);
|
||||
this.attachmentIconBackgroundView = findViewById(R.id.quote_attachment_icon_background);
|
||||
this.attachmentContainerView = findViewById(R.id.quote_attachment_container);
|
||||
this.attachmentNameView = findViewById(R.id.quote_attachment_name);
|
||||
this.dismissView = findViewById(R.id.quote_dismiss);
|
||||
this.mediaDescriptionText = findViewById(R.id.media_name);
|
||||
this.roundedCornerRadiusPx = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius);
|
||||
this.mediaDescriptionText = findViewById(R.id.media_type);
|
||||
this.largeCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_large);
|
||||
this.smallCornerRadius = getResources().getDimensionPixelSize(R.dimen.quote_corner_radius_bottom);
|
||||
|
||||
rootView.setRadii(largeCornerRadius, largeCornerRadius, smallCornerRadius, smallCornerRadius);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.QuoteView, 0, 0);
|
||||
messageType = typedArray.getInt(R.styleable.QuoteView_message_type, 0);
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.QuoteView, 0, 0);
|
||||
int primaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorPrimary, Color.BLACK);
|
||||
int secondaryColor = typedArray.getColor(R.styleable.QuoteView_quote_colorSecondary, Color.BLACK);
|
||||
messageType = typedArray.getInt(R.styleable.QuoteView_message_type, 0);
|
||||
typedArray.recycle();
|
||||
|
||||
dismissView.setVisibility(messageType == MESSAGE_TYPE_PREVIEW ? VISIBLE : GONE);
|
||||
|
||||
authorView.setTextColor(primaryColor);
|
||||
bodyView.setTextColor(primaryColor);
|
||||
attachmentNameView.setTextColor(primaryColor);
|
||||
mediaDescriptionText.setTextColor(secondaryColor);
|
||||
|
||||
if (messageType == MESSAGE_TYPE_PREVIEW) {
|
||||
int radius = getResources().getDimensionPixelOffset(R.dimen.quote_corner_radius_preview);
|
||||
rootView.setTopLeftRadius(radius);
|
||||
rootView.setTopRightRadius(radius);
|
||||
}
|
||||
}
|
||||
|
||||
dismissView.setOnClickListener(view -> setVisibility(GONE));
|
||||
@@ -124,20 +128,6 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
drawRect.left = 0;
|
||||
drawRect.top = 0;
|
||||
drawRect.right = getWidth();
|
||||
drawRect.bottom = getHeight();
|
||||
|
||||
clipPath.reset();
|
||||
clipPath.addRoundRect(drawRect, roundedCornerRadiusPx, roundedCornerRadiusPx, Path.Direction.CW);
|
||||
canvas.clipPath(clipPath);
|
||||
}
|
||||
|
||||
public void setQuote(GlideRequests glideRequests, long id, @NonNull Recipient author, @Nullable String body, @NonNull SlideDeck attachments) {
|
||||
if (this.author != null) this.author.removeListener(this);
|
||||
|
||||
@@ -149,7 +139,12 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
author.addListener(this);
|
||||
setQuoteAuthor(author);
|
||||
setQuoteText(body, attachments);
|
||||
setQuoteAttachment(glideRequests, attachments, author);
|
||||
setQuoteAttachment(glideRequests, attachments);
|
||||
}
|
||||
|
||||
public void setTopCornerSizes(boolean topLeftLarge, boolean topRightLarge) {
|
||||
rootView.setTopLeftRadius(topLeftLarge ? largeCornerRadius : smallCornerRadius);
|
||||
rootView.setTopRightRadius(topRightLarge ? largeCornerRadius : smallCornerRadius);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
@@ -177,14 +172,11 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
|
||||
authorView.setText(isOwnNumber ? getContext().getString(R.string.QuoteView_you)
|
||||
: author.toShortString());
|
||||
authorView.setTextColor(author.getColor().toQuoteTitleColor(getContext()));
|
||||
authorView.setTextColor(author.getColor().toActionBarColor(getContext()));
|
||||
|
||||
// We use the raw color resource because Android 4.x was struggling with tints here
|
||||
quoteBarView.setImageResource(author.getColor().toQuoteBarColorResource(getContext(), outgoing));
|
||||
|
||||
GradientDrawable background = (GradientDrawable) rootView.getBackground();
|
||||
background.setColor(author.getColor().toQuoteBackgroundColor(getContext(), outgoing));
|
||||
background.setStroke(getResources().getDimensionPixelSize(R.dimen.quote_outline_width),
|
||||
author.getColor().toQuoteOutlineColor(getContext(), outgoing));
|
||||
rootView.setBackgroundColor(author.getColor().toQuoteBackgroundColor(getContext(), outgoing));
|
||||
}
|
||||
|
||||
private void setQuoteText(@Nullable String body, @NonNull SlideDeck attachments) {
|
||||
@@ -197,7 +189,6 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
|
||||
bodyView.setVisibility(GONE);
|
||||
mediaDescriptionText.setVisibility(VISIBLE);
|
||||
mediaDescriptionText.setTypeface(null, Typeface.ITALIC);
|
||||
|
||||
List<Slide> audioSlides = Stream.of(attachments.getSlides()).filter(Slide::hasAudio).limit(1).toList();
|
||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
||||
@@ -208,13 +199,7 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
if (!audioSlides.isEmpty()) {
|
||||
mediaDescriptionText.setText(R.string.QuoteView_audio);
|
||||
} else if (!documentSlides.isEmpty()) {
|
||||
String filename = documentSlides.get(0).getFileName().orNull();
|
||||
if (!TextUtils.isEmpty(filename)) {
|
||||
mediaDescriptionText.setTypeface(null, Typeface.NORMAL);
|
||||
mediaDescriptionText.setText(filename);
|
||||
} else {
|
||||
mediaDescriptionText.setText(R.string.QuoteView_document);
|
||||
}
|
||||
mediaDescriptionText.setVisibility(GONE);
|
||||
} else if (!videoSlides.isEmpty()) {
|
||||
mediaDescriptionText.setText(R.string.QuoteView_video);
|
||||
} else if (!imageSlides.isEmpty()) {
|
||||
@@ -222,19 +207,15 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuoteAttachment(@NonNull GlideRequests glideRequests,
|
||||
@NonNull SlideDeck slideDeck,
|
||||
@NonNull Recipient author)
|
||||
{
|
||||
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
|
||||
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList();
|
||||
List<Slide> audioSlides = Stream.of(attachments.getSlides()).filter(Slide::hasAudio).limit(1).toList();
|
||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
||||
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
|
||||
|
||||
attachmentVideoOverlayView.setVisibility(GONE);
|
||||
|
||||
if (!imageVideoSlides.isEmpty() && imageVideoSlides.get(0).getThumbnailUri() != null) {
|
||||
attachmentView.setVisibility(VISIBLE);
|
||||
attachmentIconContainerView.setVisibility(GONE);
|
||||
thumbnailView.setVisibility(VISIBLE);
|
||||
attachmentContainerView.setVisibility(GONE);
|
||||
dismissView.setBackgroundResource(R.drawable.dismiss_background);
|
||||
if (imageVideoSlides.get(0).hasVideo()) {
|
||||
attachmentVideoOverlayView.setVisibility(VISIBLE);
|
||||
@@ -242,26 +223,14 @@ public class QuoteView extends LinearLayout implements RecipientModifiedListener
|
||||
glideRequests.load(new DecryptableUri(imageVideoSlides.get(0).getThumbnailUri()))
|
||||
.centerCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.into(attachmentView);
|
||||
} else if (!audioSlides.isEmpty() || !documentSlides.isEmpty()){
|
||||
boolean outgoing = messageType != MESSAGE_TYPE_INCOMING;
|
||||
|
||||
dismissView.setBackgroundResource(R.drawable.circle_alpha);
|
||||
attachmentView.setVisibility(GONE);
|
||||
attachmentIconContainerView.setVisibility(VISIBLE);
|
||||
|
||||
if (!audioSlides.isEmpty()) {
|
||||
attachmentIconView.setImageResource(R.drawable.ic_mic_white_48dp);
|
||||
} else {
|
||||
attachmentIconView.setImageResource(R.drawable.ic_insert_drive_file_white_24dp);
|
||||
}
|
||||
|
||||
attachmentIconView.setColorFilter(author.getColor().toQuoteIconForegroundColor(getContext(), outgoing), PorterDuff.Mode.SRC_IN);
|
||||
attachmentIconBackgroundView.setColorFilter(author.getColor().toQuoteIconBackgroundColor(getContext(), outgoing), PorterDuff.Mode.SRC_IN);
|
||||
|
||||
.into(thumbnailView);
|
||||
} else if (!documentSlides.isEmpty()){
|
||||
thumbnailView.setVisibility(GONE);
|
||||
attachmentContainerView.setVisibility(VISIBLE);
|
||||
attachmentNameView.setText(documentSlides.get(0).getFileName().or(""));
|
||||
} else {
|
||||
attachmentView.setVisibility(GONE);
|
||||
attachmentIconContainerView.setVisibility(GONE);
|
||||
thumbnailView.setVisibility(GONE);
|
||||
attachmentContainerView.setVisibility(GONE);
|
||||
dismissView.setBackgroundDrawable(null);
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -34,10 +36,11 @@ import java.util.Map;
|
||||
|
||||
public class SharedContactView extends LinearLayout implements RecipientModifiedListener {
|
||||
|
||||
private ImageView avatarView;
|
||||
private TextView nameView;
|
||||
private TextView numberView;
|
||||
private TextView actionButtonView;
|
||||
private ImageView avatarView;
|
||||
private TextView nameView;
|
||||
private TextView numberView;
|
||||
private TextView actionButtonView;
|
||||
private ConversationItemFooter footer;
|
||||
|
||||
private Contact contact;
|
||||
private Locale locale;
|
||||
@@ -48,32 +51,44 @@ public class SharedContactView extends LinearLayout implements RecipientModified
|
||||
|
||||
public SharedContactView(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
initialize(null);
|
||||
}
|
||||
|
||||
public SharedContactView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public SharedContactView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public SharedContactView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize();
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
private void initialize(@Nullable AttributeSet attrs) {
|
||||
inflate(getContext(), R.layout.shared_contact_view, this);
|
||||
|
||||
avatarView = findViewById(R.id.contact_avatar);
|
||||
nameView = findViewById(R.id.contact_name);
|
||||
numberView = findViewById(R.id.contact_number);
|
||||
actionButtonView = findViewById(R.id.contact_action_button);
|
||||
footer = findViewById(R.id.contact_footer);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.SharedContactView, 0, 0);
|
||||
int titleColor = typedArray.getInt(R.styleable.SharedContactView_contact_titleColor, Color.BLACK);
|
||||
int captionColor = typedArray.getInt(R.styleable.SharedContactView_contact_captionColor, Color.BLACK);
|
||||
typedArray.recycle();
|
||||
|
||||
nameView.setTextColor(titleColor);
|
||||
numberView.setTextColor(captionColor);
|
||||
footer.setColor(captionColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void setContact(@NonNull Contact contact, @NonNull GlideRequests glideRequests, @NonNull Locale locale) {
|
||||
@@ -89,6 +104,18 @@ public class SharedContactView extends LinearLayout implements RecipientModified
|
||||
presentActionButtons(ContactUtil.getRecipients(getContext(), contact));
|
||||
}
|
||||
|
||||
public void setSingularStyle() {
|
||||
actionButtonView.setBackgroundResource(R.drawable.shared_contact_button_background_alone);
|
||||
}
|
||||
|
||||
public void setClusteredIncomingStyle() {
|
||||
actionButtonView.setBackgroundResource(R.drawable.shared_contact_button_background_clustered_received);
|
||||
}
|
||||
|
||||
public void setClusteredOutgoingStyle() {
|
||||
actionButtonView.setBackgroundResource(R.drawable.shared_contact_button_background_clustered_sent);
|
||||
}
|
||||
|
||||
public void setEventListener(@NonNull EventListener eventListener) {
|
||||
this.eventListener = eventListener;
|
||||
}
|
||||
@@ -97,6 +124,10 @@ public class SharedContactView extends LinearLayout implements RecipientModified
|
||||
return avatarView;
|
||||
}
|
||||
|
||||
public ConversationItemFooter getFooter() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
Util.runOnMain(() -> presentActionButtons(Collections.singletonList(recipient)));
|
||||
|
@@ -0,0 +1,49 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class SpinningImageView extends AppCompatImageView {
|
||||
|
||||
private static final float DEGREES_PER_SECOND = 180;
|
||||
|
||||
private long lastDrawTime;
|
||||
private float currentRotation;
|
||||
|
||||
public SpinningImageView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public SpinningImageView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public SpinningImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
lastDrawTime = System.currentTimeMillis();
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long elapsedTime = currentTime - lastDrawTime;
|
||||
float rotate = ((float) elapsedTime / 1000) * DEGREES_PER_SECOND;
|
||||
|
||||
currentRotation += rotate;
|
||||
canvas.rotate(currentRotation, canvas.getWidth() / 2, canvas.getHeight() / 2);
|
||||
lastDrawTime = currentTime;
|
||||
|
||||
super.onDraw(canvas);
|
||||
invalidate();
|
||||
}
|
||||
}
|
@@ -2,13 +2,12 @@ package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
@@ -39,7 +38,7 @@ import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOption
|
||||
|
||||
public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||
private static final int WIDTH = 0;
|
||||
private static final int HEIGHT = 1;
|
||||
private static final int MIN_WIDTH = 0;
|
||||
@@ -48,9 +47,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
private static final int MAX_HEIGHT = 3;
|
||||
|
||||
private ImageView image;
|
||||
private ImageView playOverlay;
|
||||
private int backgroundColorHint;
|
||||
private int radius;
|
||||
private View playOverlay;
|
||||
private OnClickListener parentClickListener;
|
||||
|
||||
private final int[] dimens = new int[2];
|
||||
@@ -62,6 +59,8 @@ public class ThumbnailView extends FrameLayout {
|
||||
private SlideClickListener downloadClickListener = null;
|
||||
private Slide slide = null;
|
||||
|
||||
private int radius;
|
||||
|
||||
public ThumbnailView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -75,20 +74,20 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
inflate(context, R.layout.thumbnail_view, this);
|
||||
|
||||
this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius);
|
||||
this.image = findViewById(R.id.thumbnail_image);
|
||||
this.playOverlay = findViewById(R.id.play_overlay);
|
||||
super.setOnClickListener(new ThumbnailClickDispatcher());
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0);
|
||||
backgroundColorHint = typedArray.getColor(R.styleable.ThumbnailView_backgroundColorHint, Color.BLACK);
|
||||
bounds[MIN_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0);
|
||||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
||||
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
||||
bounds[MIN_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0);
|
||||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
||||
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
radius = getResources().getDimensionPixelOffset(R.dimen.message_corner_collapse_radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -210,8 +209,17 @@ public class ThumbnailView extends FrameLayout {
|
||||
return transferControls.get();
|
||||
}
|
||||
|
||||
public void setBackgroundColorHint(int color) {
|
||||
this.backgroundColorHint = color;
|
||||
public void setBounds(int minWidth, int maxWidth, int minHeight, int maxHeight) {
|
||||
bounds[MIN_WIDTH] = minWidth;
|
||||
bounds[MAX_WIDTH] = maxWidth;
|
||||
bounds[MIN_HEIGHT] = minHeight;
|
||||
bounds[MAX_HEIGHT] = maxHeight;
|
||||
|
||||
forceLayout();
|
||||
}
|
||||
|
||||
public void setImageBackground(@DrawableRes int resId) {
|
||||
image.setBackgroundResource(resId);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -273,10 +281,9 @@ public class ThumbnailView extends FrameLayout {
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transforms(new CenterCrop(), new RoundedCorners(radius))
|
||||
.transition(withCrossFade())
|
||||
.centerCrop()
|
||||
.into(image);
|
||||
}
|
||||
|
||||
|
@@ -55,7 +55,7 @@ public abstract class FastCursorRecyclerViewAdapter<VH extends RecyclerView.View
|
||||
}
|
||||
|
||||
protected abstract T getRecordFromCursor(@NonNull Cursor cursor);
|
||||
protected abstract void onBindItemViewHolder(VH viewHolder, @NonNull T record);
|
||||
protected abstract void onBindItemViewHolder(VH viewHolder, @NonNull T record, int position);
|
||||
protected abstract long getItemId(@NonNull T record);
|
||||
protected abstract int getItemViewType(@NonNull T record);
|
||||
protected abstract boolean isRecordForId(@NonNull T record, long id);
|
||||
@@ -69,12 +69,13 @@ public abstract class FastCursorRecyclerViewAdapter<VH extends RecyclerView.View
|
||||
@Override
|
||||
public void onBindItemViewHolder(VH viewHolder, @NonNull Cursor cursor) {
|
||||
T record = getRecordFromCursor(cursor);
|
||||
onBindItemViewHolder(viewHolder, record);
|
||||
onBindItemViewHolder(viewHolder, record, cursor.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindFastAccessItemViewHolder(VH viewHolder, int position) {
|
||||
onBindItemViewHolder(viewHolder, fastRecords.get(getCalculatedPosition(position)));
|
||||
int calculatedPosition = getCalculatedPosition(position);
|
||||
onBindItemViewHolder(viewHolder, fastRecords.get(calculatedPosition), calculatedPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -89,33 +89,33 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
@Override
|
||||
public SpannableString getDisplayBody() {
|
||||
if (isGroupUpdate() && isOutgoing()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_you_updated_group));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_you_updated_group));
|
||||
} else if (isGroupUpdate()) {
|
||||
return emphasisAdded(GroupUtil.getDescription(context, getBody()).toString(getIndividualRecipient()));
|
||||
return new SpannableString(GroupUtil.getDescription(context, getBody()).toString(getIndividualRecipient()));
|
||||
} else if (isGroupQuit() && isOutgoing()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_left_group));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_left_group));
|
||||
} else if (isGroupQuit()) {
|
||||
return emphasisAdded(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString()));
|
||||
return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString()));
|
||||
} else if (isIncomingCall()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_s_called_you, getIndividualRecipient().toShortString()));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_called_you));
|
||||
} else if (isOutgoingCall()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_called_s, getIndividualRecipient().toShortString()));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_you_called));
|
||||
} else if (isMissedCall()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_missed_call_from, getIndividualRecipient().toShortString()));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_missed_call));
|
||||
} else if (isJoined()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().toShortString()));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_s_joined_signal, getIndividualRecipient().toShortString()));
|
||||
} else if (isExpirationTimerUpdate()) {
|
||||
String time = ExpirationUtil.getExpirationDisplayValue(context, (int)(getExpiresIn() / 1000));
|
||||
return isOutgoing() ? emphasisAdded(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time))
|
||||
: emphasisAdded(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().toShortString(), time));
|
||||
return isOutgoing() ? new SpannableString(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time))
|
||||
: new SpannableString(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().toShortString(), time));
|
||||
} else if (isIdentityUpdate()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString()));
|
||||
return new SpannableString(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString()));
|
||||
} else if (isIdentityVerified()) {
|
||||
if (isOutgoing()) return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified, getIndividualRecipient().toShortString()));
|
||||
else return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device, getIndividualRecipient().toShortString()));
|
||||
if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified, getIndividualRecipient().toShortString()));
|
||||
else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device, getIndividualRecipient().toShortString()));
|
||||
} else if (isIdentityDefault()) {
|
||||
if (isOutgoing()) return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, getIndividualRecipient().toShortString()));
|
||||
else return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, getIndividualRecipient().toShortString()));
|
||||
if (isOutgoing()) return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, getIndividualRecipient().toShortString()));
|
||||
else return new SpannableString(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, getIndividualRecipient().toShortString()));
|
||||
} else if (getBody().length() > MAX_DISPLAY_LENGTH) {
|
||||
return new SpannableString(getBody().substring(0, MAX_DISPLAY_LENGTH));
|
||||
}
|
||||
@@ -174,6 +174,11 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return SmsDatabase.Types.isInvalidVersionKeyExchange(type);
|
||||
}
|
||||
|
||||
public boolean isUpdate() {
|
||||
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
|
||||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault();
|
||||
}
|
||||
|
||||
public boolean isMediaPending() {
|
||||
return false;
|
||||
}
|
||||
|
@@ -75,7 +75,7 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
|
||||
|
||||
out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()),
|
||||
members, getAvatar(record.getAvatar()),
|
||||
record.isActive(), expirationTimer));
|
||||
record.isActive(), expirationTimer, Optional.of(recipient.getColor().serialize())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -46,7 +46,7 @@ public class ColorPickerPreference extends DialogPreference {
|
||||
|
||||
colorDescriptions = a.getTextArray(R.styleable.ColorPickerPreference_colorDescriptions);
|
||||
color = a.getColor(R.styleable.ColorPickerPreference_currentColor, 0);
|
||||
columns = a.getInt(R.styleable.ColorPickerPreference_columns, 4);
|
||||
columns = a.getInt(R.styleable.ColorPickerPreference_columns, 3);
|
||||
size = a.getInt(R.styleable.ColorPickerPreference_colorSize, 2);
|
||||
sortColors = a.getBoolean(R.styleable.ColorPickerPreference_sortColors, false);
|
||||
|
||||
|
@@ -286,8 +286,7 @@ public class Recipient implements RecipientModifiedListener {
|
||||
}
|
||||
|
||||
public synchronized @NonNull MaterialColor getColor() {
|
||||
if (isGroupRecipient()) return MaterialColor.GROUP;
|
||||
else if (color != null) return color;
|
||||
if (color != null) return color;
|
||||
else if (name != null) return ContactColors.generateFor(name);
|
||||
else return ContactColors.UNKNOWN_COLOR;
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ public class DateUtils extends android.text.format.DateUtils {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DateUtils.class.getSimpleName();
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
|
||||
|
||||
private static boolean isWithin(final long millis, final long span, final TimeUnit unit) {
|
||||
return System.currentTimeMillis() - millis <= unit.toMillis(span);
|
||||
@@ -131,6 +132,14 @@ public class DateUtils extends android.text.format.DateUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSameDay(long t1, long t2) {
|
||||
return DATE_FORMAT.format(new Date(t1)).equals(DATE_FORMAT.format(new Date(t2)));
|
||||
}
|
||||
|
||||
public static boolean isSameBriefRelativeTimestamp(@NonNull Context context, @NonNull Locale locale, long t1, long t2) {
|
||||
return getBriefRelativeTimeSpanString(context, locale, t1).equals(getBriefRelativeTimeSpanString(context, locale, t2));
|
||||
}
|
||||
|
||||
private static String getLocalizedPattern(String template, Locale locale) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return DateFormat.getBestDateTimePattern(locale, template);
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.TypedValue;
|
||||
|
||||
@@ -12,6 +15,16 @@ public class ThemeUtil {
|
||||
return getAttribute(context, R.attr.theme_type, "light").equals("dark");
|
||||
}
|
||||
|
||||
public static int getThemedColor(@NonNull Context context, @AttrRes int attr) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
|
||||
if (theme.resolveAttribute(attr, typedValue, true)) {
|
||||
return typedValue.data;
|
||||
}
|
||||
return Color.RED;
|
||||
}
|
||||
|
||||
private static String getAttribute(Context context, int attribute, String defaultValue) {
|
||||
TypedValue outValue = new TypedValue();
|
||||
|
||||
@@ -21,5 +34,4 @@ public class ThemeUtil {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -197,4 +197,51 @@ public class ViewUtil {
|
||||
public static int dpToPx(Context context, int dp) {
|
||||
return (int)((dp * context.getResources().getDisplayMetrics().density) + 0.5);
|
||||
}
|
||||
|
||||
public static void updateLayoutParams(@NonNull View view, int width, int height) {
|
||||
view.getLayoutParams().width = width;
|
||||
view.getLayoutParams().height = height;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static int getLeftMargin(@NonNull View view) {
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin;
|
||||
}
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin;
|
||||
}
|
||||
|
||||
public static int getRightMargin(@NonNull View view) {
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin;
|
||||
}
|
||||
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin;
|
||||
}
|
||||
|
||||
public static void setLeftMargin(@NonNull View view, int margin) {
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin = margin;
|
||||
} else {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin = margin;
|
||||
}
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static void setTopMargin(@NonNull View view, int margin) {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin = margin;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static void setBottomMargin(@NonNull View view, int margin) {
|
||||
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).bottomMargin = margin;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
public static void setPaddingTop(@NonNull View view, int padding) {
|
||||
view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), view.getPaddingBottom());
|
||||
}
|
||||
|
||||
public static void setPaddingBottom(@NonNull View view, int padding) {
|
||||
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), padding);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user