Improve the look of message bubbles.

This commit is contained in:
Greyson Parrelli
2018-06-26 10:27:44 -07:00
parent 7cfcb62c25
commit 24b062d8dd
202 changed files with 2671 additions and 1451 deletions

View File

@@ -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();

View File

@@ -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;

View File

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

View File

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

View File

@@ -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;

View File

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

View File

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

View File

@@ -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()));

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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