From 7c95adc7e61e8157a686caa19ac8b6c8cf593eae Mon Sep 17 00:00:00 2001 From: haffenloher Date: Tue, 24 Nov 2015 16:06:41 +0100 Subject: [PATCH] Add delivery status icons to the conversation list Closes #4710 --- res/drawable-xxhdpi/ic_error_red_18dp.png | Bin 0 -> 997 bytes res/layout/alert_view.xml | 24 +++ res/layout/conversation_item_pending.xml | 3 +- res/layout/conversation_item_pending_v11.xml | 3 +- res/layout/conversation_item_received.xml | 46 ++---- res/layout/conversation_item_sent.xml | 66 ++------ res/layout/conversation_list_item_view.xml | 33 ++-- res/layout/delivery_status_view.xml | 29 ++++ res/values/attrs.xml | 8 + .../securesms/ConversationItem.java | 155 +++++------------- .../securesms/ConversationListItem.java | 74 ++++++--- .../securesms/components/AlertView.java | 69 ++++++++ .../components/DeliveryStatusView.java | 89 ++++++++++ .../securesms/database/DatabaseFactory.java | 8 +- .../securesms/database/MmsDatabase.java | 3 + .../securesms/database/SmsDatabase.java | 6 +- .../securesms/database/ThreadDatabase.java | 35 ++-- .../loaders/ConversationListLoader.java | 5 +- .../database/model/DisplayRecord.java | 39 ++++- .../database/model/MediaMmsMessageRecord.java | 8 +- .../database/model/MessageRecord.java | 39 +---- .../model/NotificationMmsMessageRecord.java | 3 +- .../database/model/SmsMessageRecord.java | 14 +- .../database/model/ThreadRecord.java | 6 +- 24 files changed, 447 insertions(+), 318 deletions(-) create mode 100644 res/drawable-xxhdpi/ic_error_red_18dp.png create mode 100644 res/layout/alert_view.xml create mode 100644 res/layout/delivery_status_view.xml create mode 100644 src/org/thoughtcrime/securesms/components/AlertView.java create mode 100644 src/org/thoughtcrime/securesms/components/DeliveryStatusView.java diff --git a/res/drawable-xxhdpi/ic_error_red_18dp.png b/res/drawable-xxhdpi/ic_error_red_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4faed504cd78d9f6a2d382b6e1522a1417d448 GIT binary patch literal 997 zcmV}3 z*So&W$gO=H5xeB2i|2_Kmi9NF8<;->VGtW629kT5nOpOrrojcMhjXxchVy-CW#lWr z;O$QpHGV#oQT|U72I>Lo-3BrPXcotSd+1|lFFz>MpXnIACfPGM@H1WGS8ucOsy=r1 z@C`%^F}^D*O96W54YfRk&4cfX$`e6$(I=MLx};ut_#L2?htTmWPyc-%J9;E(>M3AG zcLaqby*vfO&vi{+eyC}*r-`TDmsN$4RWMKsD{20CUtS$dT~C!(g+G;5`8!~qf?=Q= zfSwDbj;E^Yod-0guv878EJ0a8(cUflOxy77pz#!}!~|9^z(P!#+#;RnrJMKB!R9F# ze(COe2;@eRJ@ziIIA^eW3WnbmlxC2c1Vx`|=zpMw<+$viN|Mwvc=xfZPnc+ry-m$* zr&g%a(qka}HZ8lE==R?;b-j;6#bcn9MO3@;T|r4YHS+aiDA4mWUmj22yedU*`g0mkVuT69n7<*}#An%{AI zOhNtEko6caQgA0lV54%#CPiR2!d-Zn+1wklg@>8VUEIaJSHPaZ-Uq{3-1`RY#9cwh zB~Kly9(xm?G6{E+s<<7A@v|x|9ZJ-SHJW-1 z*gpK&+9^-c*y7u?td_xE#J|nRsV8e*nHK#sYWBa#p0EDU*lGaEoP*9|Dq7z@G_~nc zG~)uA2Ofm2j99A7k%4CM2hBu8R(?5EXEonu=2Z`PVUd+zNt1bK&`|P#%x;3_pNq>A zXuEQtvDNs6x$Pagm3|g>w?8zs8q#Y43-{aPj80mNk-#y~Y|wNTXl)kVSLl3bX;*(8 z5xeNAyxMQ7c?f9Mo5=VDz!rt-z^|!l?NoT1p3^`{5(Vi6wIoPxBGD}to7yzr + + + + + + + \ No newline at end of file diff --git a/res/layout/conversation_item_pending.xml b/res/layout/conversation_item_pending.xml index 116287978..fd402078d 100644 --- a/res/layout/conversation_item_pending.xml +++ b/res/layout/conversation_item_pending.xml @@ -2,9 +2,8 @@ - - - - - + - - - - - - - + android:layout_alignParentRight="true" + android:orientation="vertical" + android:gravity="center_vertical"/> - - - - - - - + android:orientation="vertical" + android:gravity="left|center_vertical" + android:layout_marginLeft="6dp" + android:layout_alignParentLeft="true" + android:layout_centerVertical="true"/> - - - - - + - + + diff --git a/res/layout/delivery_status_view.xml b/res/layout/delivery_status_view.xml new file mode 100644 index 000000000..8fe3b40cd --- /dev/null +++ b/res/layout/delivery_status_view.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index a58d10e9d..6f47480aa 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -139,6 +139,14 @@ + + + + + + + + diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index e08cfcdb3..0866bd7db 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; -import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; @@ -31,7 +30,6 @@ import android.text.TextUtils; import android.text.util.Linkify; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -42,6 +40,8 @@ import android.widget.Toast; import org.thoughtcrime.securesms.components.AudioView; import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.components.DeliveryStatusView; +import org.thoughtcrime.securesms.components.AlertView; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; @@ -90,22 +90,18 @@ public class ConversationItem extends LinearLayout private boolean groupThread; private Recipient recipient; - private View bodyBubble; - private TextView bodyText; - private TextView dateText; - private TextView indicatorText; - private TextView groupStatusText; - private ImageView secureImage; - private AvatarImageView contactPhoto; - private ImageView failedIndicator; - private ImageView deliveredIndicator; - private ImageView sentIndicator; - private View pendingIndicator; - private ImageView pendingApprovalIndicator; + private View bodyBubble; + private TextView bodyText; + private TextView dateText; + private TextView indicatorText; + private TextView groupStatusText; + private ImageView secureImage; + private AvatarImageView contactPhoto; + private DeliveryStatusView deliveryStatusIndicator; + private AlertView alertView; private @NonNull Set batchSelected = new HashSet<>(); private @Nullable Recipients conversationRecipients; - private @NonNull StatusManager statusManager; private @NonNull ThumbnailView mediaThumbnail; private @NonNull AudioView audioView; private @NonNull Button mmsDownloadButton; @@ -136,31 +132,20 @@ public class ConversationItem extends LinearLayout super.onFinishInflate(); initializeAttributes(); - ViewGroup pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub); - if (pendingIndicatorStub != null) { - LayoutInflater inflater = LayoutInflater.from(context); - if (Build.VERSION.SDK_INT >= 11) inflater.inflate(R.layout.conversation_item_pending_v11, pendingIndicatorStub, true); - else inflater.inflate(R.layout.conversation_item_pending, pendingIndicatorStub, true); - } - - this.bodyText = (TextView) findViewById(R.id.conversation_item_body); - this.dateText = (TextView) findViewById(R.id.conversation_item_date); - this.indicatorText = (TextView) findViewById(R.id.indicator_text); - this.groupStatusText = (TextView) findViewById(R.id.group_message_status); - this.secureImage = (ImageView) findViewById(R.id.secure_indicator); - this.failedIndicator = (ImageView) findViewById(R.id.sms_failed_indicator); - this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button); - this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading); - this.contactPhoto = (AvatarImageView) findViewById(R.id.contact_photo); - this.deliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator); - this.sentIndicator = (ImageView) findViewById(R.id.sent_indicator); - this.bodyBubble = findViewById(R.id.body_bubble); - this.pendingApprovalIndicator = (ImageView) findViewById(R.id.pending_approval_indicator); - this.pendingIndicator = findViewById(R.id.pending_indicator); - this.mediaThumbnail = (ThumbnailView) findViewById(R.id.image_view); - this.audioView = (AudioView) findViewById(R.id.audio_view); - this.statusManager = new StatusManager(pendingIndicator, sentIndicator, deliveredIndicator, failedIndicator, pendingApprovalIndicator); + this.bodyText = (TextView) findViewById(R.id.conversation_item_body); + this.dateText = (TextView) findViewById(R.id.conversation_item_date); + this.indicatorText = (TextView) findViewById(R.id.indicator_text); + this.groupStatusText = (TextView) findViewById(R.id.group_message_status); + this.secureImage = (ImageView) findViewById(R.id.secure_indicator); + this.deliveryStatusIndicator = (DeliveryStatusView) findViewById(R.id.delivery_status); + this.alertView = (AlertView) findViewById(R.id.indicators_parent); + this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button); + this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading); + this.contactPhoto = (AvatarImageView) findViewById(R.id.contact_photo); + this.bodyBubble = findViewById(R.id.body_bubble); + this.mediaThumbnail = (ThumbnailView) findViewById(R.id.image_view); + this.audioView = (AudioView) findViewById(R.id.audio_view); setOnClickListener(new ClickListener(null)); PassthroughClickListener passthroughClickListener = new PassthroughClickListener(); @@ -334,16 +319,25 @@ public class ConversationItem extends LinearLayout dateText.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, timestamp)); - if (messageRecord.isFailed()) setFailedStatusIcons(); - else if (messageRecord.isPendingInsecureSmsFallback()) setFallbackStatusIcons(); - else if (messageRecord.isPending()) statusManager.displayPending(); - else if (messageRecord.isDelivered()) statusManager.displayDelivered(); - else statusManager.displaySent(); + if (messageRecord.isFailed()) { + setFailedStatusIcons(); + } else if (messageRecord.isPendingInsecureSmsFallback()) { + setFallbackStatusIcons(); + } else { + alertView.setNone(); + + if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone(); + else if (messageRecord.isPending()) deliveryStatusIndicator.setPending(); + else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered(); + else deliveryStatusIndicator.setSent(); + } } private void setFailedStatusIcons() { - statusManager.displayFailed(); + 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); @@ -351,7 +345,8 @@ public class ConversationItem extends LinearLayout } private void setFallbackStatusIcons() { - statusManager.displayPendingApproval(); + alertView.setPendingApproval(); + deliveryStatusIndicator.setNone(); indicatorText.setVisibility(View.VISIBLE); indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted); } @@ -612,72 +607,4 @@ public class ConversationItem extends LinearLayout builder.show(); } - private static class StatusManager { - - private final View pendingIndicator; - private final View sentIndicator; - private final View deliveredIndicator; - - private final View failedIndicator; - private final View approvalIndicator; - - - public StatusManager(View pendingIndicator, View sentIndicator, - View deliveredIndicator, View failedIndicator, - View approvalIndicator) - { - this.pendingIndicator = pendingIndicator; - this.sentIndicator = sentIndicator; - this.deliveredIndicator = deliveredIndicator; - this.failedIndicator = failedIndicator; - this.approvalIndicator = approvalIndicator; - } - - public void displayFailed() { - pendingIndicator.setVisibility(View.GONE); - sentIndicator.setVisibility(View.GONE); - deliveredIndicator.setVisibility(View.GONE); - approvalIndicator.setVisibility(View.GONE); - - failedIndicator.setVisibility(View.VISIBLE); - } - - public void displayPendingApproval() { - pendingIndicator.setVisibility(View.GONE); - sentIndicator.setVisibility(View.GONE); - deliveredIndicator.setVisibility(View.GONE); - failedIndicator.setVisibility(View.GONE); - - approvalIndicator.setVisibility(View.VISIBLE); - } - - public void displayPending() { - sentIndicator.setVisibility(View.GONE); - deliveredIndicator.setVisibility(View.GONE); - failedIndicator.setVisibility(View.GONE); - approvalIndicator.setVisibility(View.GONE); - - pendingIndicator.setVisibility(View.VISIBLE); - } - - public void displaySent() { - pendingIndicator.setVisibility(View.GONE); - deliveredIndicator.setVisibility(View.GONE); - failedIndicator.setVisibility(View.GONE); - approvalIndicator.setVisibility(View.GONE); - - sentIndicator.setVisibility(View.VISIBLE); - } - - public void displayDelivered() { - pendingIndicator.setVisibility(View.GONE); - failedIndicator.setVisibility(View.GONE); - approvalIndicator.setVisibility(View.GONE); - sentIndicator.setVisibility(View.GONE); - - deliveredIndicator.setVisibility(View.VISIBLE); - } - - } - } diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index ce80d4ae2..edd48f001 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -27,12 +27,13 @@ import android.os.Handler; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.components.DeliveryStatusView; +import org.thoughtcrime.securesms.components.AlertView; import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.crypto.MasterSecret; @@ -63,13 +64,16 @@ public class ConversationListItem extends RelativeLayout 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 Set selectedThreads; - private Recipients recipients; - private long threadId; - private TextView subjectView; - private FromTextView fromView; - private TextView dateView; - private TextView archivedView; + private Set selectedThreads; + private Recipients recipients; + private long threadId; + private TextView subjectView; + private FromTextView fromView; + private TextView dateView; + private TextView archivedView; + private DeliveryStatusView deliveryStatusIndicator; + private AlertView alertView; + private boolean read; private AvatarImageView contactPhotoImage; private ThumbnailView thumbnailView; @@ -93,12 +97,14 @@ public class ConversationListItem extends RelativeLayout @Override protected void onFinishInflate() { super.onFinishInflate(); - this.subjectView = (TextView) findViewById(R.id.subject); - this.fromView = (FromTextView) findViewById(R.id.from); - this.dateView = (TextView) findViewById(R.id.date); - this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image); - this.thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail); - this.archivedView = ViewUtil.findById(this, R.id.archived); + this.subjectView = (TextView) findViewById(R.id.subject); + this.fromView = (FromTextView) findViewById(R.id.from); + this.dateView = (TextView) findViewById(R.id.date); + this.deliveryStatusIndicator = (DeliveryStatusView) findViewById(R.id.delivery_status); + this.alertView = (AlertView) findViewById(R.id.indicators_parent); + this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image); + this.thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail); + this.archivedView = ViewUtil.findById(this, R.id.archived); thumbnailView.setClickable(false); } @@ -129,6 +135,7 @@ public class ConversationListItem extends RelativeLayout this.archivedView.setVisibility(View.GONE); } + setStatusIcons(thread); setThumbnailSnippet(masterSecret, thread); setBatchState(batchMode); setBackground(thread); @@ -165,16 +172,35 @@ public class ConversationListItem extends RelativeLayout LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams(); subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.thumbnail); this.subjectView.setLayoutParams(subjectParams); - this.post(new ThumbnailPositioner(thumbnailView, archivedView, dateView)); + this.post(new ThumbnailPositioner(thumbnailView, archivedView, deliveryStatusIndicator, dateView)); } else { this.thumbnailView.setVisibility(View.GONE); LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams(); - subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.archived); + subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.delivery_status); this.subjectView.setLayoutParams(subjectParams); } } + private void setStatusIcons(ThreadRecord thread) { + if (!thread.isOutgoing()) { + deliveryStatusIndicator.setNone(); + alertView.setNone(); + } else if (thread.isFailed()) { + deliveryStatusIndicator.setNone(); + alertView.setFailed(); + } else if (thread.isPendingInsecureSmsFallback()) { + deliveryStatusIndicator.setNone(); + alertView.setPendingApproval(); + } else { + alertView.setNone(); + + if (thread.isPending()) deliveryStatusIndicator.setPending(); + else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered(); + else deliveryStatusIndicator.setSent(); + } + } + private void setBackground(ThreadRecord thread) { if (thread.isRead()) setBackgroundResource(readBackground); else setBackgroundResource(unreadBackround); @@ -204,20 +230,24 @@ public class ConversationListItem extends RelativeLayout private final View thumbnailView; private final View archivedView; + private final View deliveryStatusView; private final View dateView; - public ThumbnailPositioner(View thumbnailView, View archivedView, View dateView) { - this.thumbnailView = thumbnailView; - this.archivedView = archivedView; - this.dateView = dateView; + public ThumbnailPositioner(View thumbnailView, View archivedView, View deliveryStatusView, View dateView) { + this.thumbnailView = thumbnailView; + this.archivedView = archivedView; + this.deliveryStatusView = deliveryStatusView; + this.dateView = dateView; } @Override public void run() { LayoutParams thumbnailParams = (RelativeLayout.LayoutParams)thumbnailView.getLayoutParams(); - if (archivedView.getVisibility() == View.VISIBLE && archivedView.getWidth() > dateView.getWidth()) { - thumbnailParams.addRule(RelativeLayout.LEFT_OF, R.id.archived); + if (archivedView.getVisibility() == View.VISIBLE && + (archivedView.getWidth() + deliveryStatusView.getWidth()) > dateView.getWidth()) + { + thumbnailParams.addRule(RelativeLayout.LEFT_OF, R.id.delivery_status); } else { thumbnailParams.addRule(RelativeLayout.LEFT_OF, R.id.date); } diff --git a/src/org/thoughtcrime/securesms/components/AlertView.java b/src/org/thoughtcrime/securesms/components/AlertView.java new file mode 100644 index 000000000..706f69ebe --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/AlertView.java @@ -0,0 +1,69 @@ +package org.thoughtcrime.securesms.components; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build.VERSION_CODES; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import org.thoughtcrime.securesms.R; + +public class AlertView extends LinearLayout { + + private static final String TAG = AlertView.class.getSimpleName(); + + private ImageView approvalIndicator; + private ImageView failedIndicator; + + public AlertView(Context context) { + this(context, null); + } + + public AlertView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(attrs); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public AlertView(final Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(attrs); + } + + 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); + + if (attrs != null) { + TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.AlertView, 0, 0); + boolean useSmallIcon = typedArray.getBoolean(R.styleable.AlertView_useSmallIcon, false); + typedArray.recycle(); + + if (useSmallIcon) { + failedIndicator.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_error_red_18dp)); + } + } + } + + public void setNone() { + this.setVisibility(View.GONE); + } + + public void setPendingApproval() { + this.setVisibility(View.VISIBLE); + approvalIndicator.setVisibility(View.VISIBLE); + failedIndicator.setVisibility(View.GONE); + } + + public void setFailed() { + this.setVisibility(View.VISIBLE); + approvalIndicator.setVisibility(View.GONE); + failedIndicator.setVisibility(View.VISIBLE); + } +} diff --git a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java new file mode 100644 index 000000000..faedeafbd --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java @@ -0,0 +1,89 @@ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.thoughtcrime.securesms.R; + +import pl.tajchert.sample.DotsTextView; + +public class DeliveryStatusView extends FrameLayout { + + private static final String TAG = DeliveryStatusView.class.getSimpleName(); + + private final ViewGroup pendingIndicatorStub; + private final ImageView sentIndicator; + private final ImageView deliveredIndicator; + + public DeliveryStatusView(Context context) { + this(context, null); + } + + public DeliveryStatusView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DeliveryStatusView(final Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + 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); + + int iconColor = Color.GRAY; + + if (attrs != null) { + TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DeliveryStatusView, 0, 0); + iconColor = typedArray.getColor(R.styleable.DeliveryStatusView_iconColor, iconColor); + 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() { + this.setVisibility(View.GONE); + } + + public void setPending() { + this.setVisibility(View.VISIBLE); + pendingIndicatorStub.setVisibility(View.VISIBLE); + sentIndicator.setVisibility(View.GONE); + deliveredIndicator.setVisibility(View.GONE); + } + + public void setSent() { + this.setVisibility(View.VISIBLE); + pendingIndicatorStub.setVisibility(View.GONE); + sentIndicator.setVisibility(View.VISIBLE); + deliveredIndicator.setVisibility(View.GONE); + } + + public void setDelivered() { + this.setVisibility(View.VISIBLE); + pendingIndicatorStub.setVisibility(View.GONE); + sentIndicator.setVisibility(View.GONE); + deliveredIndicator.setVisibility(View.VISIBLE); + } +} diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 4d16f8967..8044742c3 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -69,7 +69,8 @@ public class DatabaseFactory { private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23; private static final int INTRODUCED_ARCHIVE_VERSION = 24; - private static final int DATABASE_VERSION = 24; + private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; + private static final int DATABASE_VERSION = 25; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -784,6 +785,11 @@ public class DatabaseFactory { db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)"); } + if (oldVersion < INTRODUCED_CONVERSATION_LIST_STATUS_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN status INTEGER DEFAULT -1"); + db.execSQL("ALTER TABLE thread ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index a94234174..293452ff3 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -214,6 +214,7 @@ public class MmsDatabase extends MessagingDatabase { RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?", new String[] {String.valueOf(id)}); + DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); } } catch (InvalidNumberException e) { @@ -337,6 +338,8 @@ public class MmsDatabase extends MessagingDatabase { db.execSQL("UPDATE " + TABLE_NAME + " SET " + MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" + " WHERE " + ID + " = ?", new String[] {id + ""}); + + DatabaseFactory.getThreadDatabase(context).update(getThreadIdForMessage(id), false); } public void markAsOutbox(long messageId) { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 365c471fa..92bb00115 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -120,7 +120,6 @@ public class SmsDatabase extends MessagingDatabase { DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); - notifyConversationListListeners(); } public long getThreadIdForMessage(long id) { @@ -265,12 +264,15 @@ public class SmsDatabase extends MessagingDatabase { String ourAddress = canonicalizeNumber(context, cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); if (ourAddress.equals(theirAddress)) { + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); + database.execSQL("UPDATE " + TABLE_NAME + " SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?", new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))}); - notifyConversationListeners(cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID))); + DatabaseFactory.getThreadDatabase(context).update(threadId, false); + notifyConversationListeners(threadId); } } catch (InvalidNumberException e) { Log.w("SmsDatabase", e); diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index b46815966..f5afa1c67 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -64,12 +64,17 @@ public class ThreadDatabase extends Database { public static final String SNIPPET_TYPE = "snippet_type"; public static final String SNIPPET_URI = "snippet_uri"; public static final String ARCHIVED = "archived"; + public static final String STATUS = "status"; + public static final String RECEIPT_COUNT = "delivery_receipt_count"; - public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + - DATE + " INTEGER DEFAULT 0, " + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + - RECIPIENT_IDS + " TEXT, " + SNIPPET + " TEXT, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + - READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + - SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + ARCHIVED + " INTEGER DEFAULT 0);"; + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + RECIPIENT_IDS + " TEXT, " + SNIPPET + " TEXT, " + + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " + + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + + ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " + + RECEIPT_COUNT + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");", @@ -126,14 +131,17 @@ public class ThreadDatabase extends Database { return db.insert(TABLE_NAME, null, contentValues); } - private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, long date, long type, boolean unarchive) + private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, + long date, int status, int receiptCount, long type, boolean unarchive) { - ContentValues contentValues = new ContentValues(5); + ContentValues contentValues = new ContentValues(7); contentValues.put(DATE, date - date % 1000); contentValues.put(MESSAGE_COUNT, count); contentValues.put(SNIPPET, body); contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); contentValues.put(SNIPPET_TYPE, type); + contentValues.put(STATUS, status); + contentValues.put(RECEIPT_COUNT, receiptCount); if (unarchive) { contentValues.put(ARCHIVED, 0); @@ -145,7 +153,7 @@ public class ThreadDatabase extends Database { } public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { - ContentValues contentValues = new ContentValues(3); + ContentValues contentValues = new ContentValues(4); contentValues.put(DATE, date - date % 1000); contentValues.put(SNIPPET, snippet); @@ -479,10 +487,9 @@ public class ThreadDatabase extends Database { if (record.isPush()) timestamp = record.getDateSent(); else timestamp = record.getDateReceived(); - updateThread(threadId, count, record.getBody().getBody(), - getAttachmentUriFor(record), timestamp, + updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record), + timestamp, record.getDeliveryStatus(), record.getReceiptCount(), record.getType(), unarchive); - notifyConversationListListeners(); return false; } else { @@ -549,10 +556,12 @@ public class ThreadDatabase extends Database { long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE)); boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; + int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); + int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.RECEIPT_COUNT)); Uri snippetUri = getSnippetUri(cursor); - return new ThreadRecord(context, body, snippetUri, recipients, date, count, - read == 1, threadId, type, distributionType, archived); + return new ThreadRecord(context, body, snippetUri, recipients, date, count, read == 1, + threadId, receiptCount, status, type, distributionType, archived); } private DisplayRecord.Body getPlaintextBody(Cursor cursor) { diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java index f0749c40f..9701d086f 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java @@ -43,10 +43,11 @@ public class ConversationListLoader extends AbstractCursorLoader { ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT, ThreadDatabase.RECIPIENT_IDS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI, - ThreadDatabase.ARCHIVED}, 1); + ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.RECEIPT_COUNT}, 1); switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount, - "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, 0, null, 0}); + "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, + 0, null, 0, -1, 0}); cursorList.add(switchToArchiveCursor); } diff --git a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java index c47343882..f3a2c6d64 100644 --- a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.database.model; import android.content.Context; import android.text.SpannableString; +import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.recipients.Recipients; @@ -40,9 +41,11 @@ public abstract class DisplayRecord { private final long dateReceived; private final long threadId; private final Body body; + private final int deliveryStatus; + private final int receiptCount; public DisplayRecord(Context context, Body body, Recipients recipients, long dateSent, - long dateReceived, long threadId, long type) + long dateReceived, long threadId, int deliveryStatus, int receiptCount, long type) { this.context = context.getApplicationContext(); this.threadId = threadId; @@ -51,12 +54,29 @@ public abstract class DisplayRecord { this.dateReceived = dateReceived; this.type = type; this.body = body; + this.receiptCount = receiptCount; + this.deliveryStatus = deliveryStatus; } public Body getBody() { return body; } + public boolean isFailed() { + return + MmsSmsColumns.Types.isFailedMessageType(type) || + MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) || + deliveryStatus >= SmsDatabase.Status.STATUS_FAILED; + } + + public boolean isPending() { + return MmsSmsColumns.Types.isPendingMessageType(type); + } + + public boolean isOutgoing() { + return MmsSmsColumns.Types.isOutgoingMessageType(type); + } + public abstract SpannableString getDisplayBody(); public Recipients getRecipients() { @@ -115,6 +135,23 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isMissedCall(type); } + public int getDeliveryStatus() { + return deliveryStatus; + } + + public int getReceiptCount() { + return receiptCount; + } + + public boolean isDelivered() { + return (deliveryStatus >= SmsDatabase.Status.STATUS_COMPLETE && + deliveryStatus < SmsDatabase.Status.STATUS_PENDING) || receiptCount > 0; + } + + public boolean isPendingInsecureSmsFallback() { + return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type); + } + public static class Body { private final String body; private final boolean plaintext; diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index c2473713c..deb32c37b 100644 --- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -21,6 +21,7 @@ import android.support.annotation.NonNull; import android.text.SpannableString; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.NetworkFailure; @@ -47,16 +48,15 @@ public class MediaMmsMessageRecord extends MessageRecord { public MediaMmsMessageRecord(Context context, long id, Recipients recipients, Recipient individualRecipient, int recipientDeviceId, - long dateSent, long dateReceived, int deliveredCount, + long dateSent, long dateReceived, int receiptCount, long threadId, Body body, @NonNull SlideDeck slideDeck, int partCount, long mailbox, List mismatches, List failures) { - super(context, id, body, recipients, individualRecipient, recipientDeviceId, - dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, deliveredCount, mailbox, - mismatches, failures); + super(context, id, body, recipients, individualRecipient, recipientDeviceId, dateSent, + dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures); this.context = context.getApplicationContext(); this.partCount = partCount; diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index 5093d6616..1eb524daa 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -43,18 +43,11 @@ import java.util.List; */ public abstract class MessageRecord extends DisplayRecord { - public static final int DELIVERY_STATUS_NONE = 0; - public static final int DELIVERY_STATUS_RECEIVED = 1; - public static final int DELIVERY_STATUS_PENDING = 2; - public static final int DELIVERY_STATUS_FAILED = 3; - private static final int MAX_DISPLAY_LENGTH = 2000; private final Recipient individualRecipient; private final int recipientDeviceId; private final long id; - private final int deliveryStatus; - private final int receiptCount; private final List mismatches; private final List networkFailures; @@ -65,12 +58,11 @@ public abstract class MessageRecord extends DisplayRecord { List mismatches, List networkFailures) { - super(context, body, recipients, dateSent, dateReceived, threadId, type); + super(context, body, recipients, dateSent, dateReceived, threadId, deliveryStatus, receiptCount, + type); this.id = id; this.individualRecipient = individualRecipient; this.recipientDeviceId = recipientDeviceId; - this.deliveryStatus = deliveryStatus; - this.receiptCount = receiptCount; this.mismatches = mismatches; this.networkFailures = networkFailures; } @@ -78,21 +70,6 @@ public abstract class MessageRecord extends DisplayRecord { public abstract boolean isMms(); public abstract boolean isMmsNotification(); - public boolean isFailed() { - return - MmsSmsColumns.Types.isFailedMessageType(type) || - MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) || - getDeliveryStatus() == DELIVERY_STATUS_FAILED; - } - - public boolean isOutgoing() { - return MmsSmsColumns.Types.isOutgoingMessageType(type); - } - - public boolean isPending() { - return MmsSmsColumns.Types.isPendingMessageType(type); - } - public boolean isSecure() { return MmsSmsColumns.Types.isSecureType(type); } @@ -134,14 +111,6 @@ public abstract class MessageRecord extends DisplayRecord { return id; } - public int getDeliveryStatus() { - return deliveryStatus; - } - - public boolean isDelivered() { - return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED || receiptCount > 0; - } - public boolean isPush() { return SmsDatabase.Types.isPushType(type) && !SmsDatabase.Types.isForcedSms(type); } @@ -158,10 +127,6 @@ public abstract class MessageRecord extends DisplayRecord { return SmsDatabase.Types.isProcessedKeyExchange(type); } - public boolean isPendingInsecureSmsFallback() { - return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type); - } - public boolean isIdentityMismatchFailure() { return mismatches != null && !mismatches.isEmpty(); } diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index b9829d228..2dfa01547 100644 --- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -20,6 +20,7 @@ import android.content.Context; import android.text.SpannableString; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; @@ -51,7 +52,7 @@ public class NotificationMmsMessageRecord extends MessageRecord { long expiry, int status, byte[] transactionId, long mailbox) { super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId, - dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox, + dateSent, dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, new LinkedList(), new LinkedList()); this.contentLocation = contentLocation; diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 03a8d87fe..107df9951 100644 --- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -50,7 +50,7 @@ public class SmsMessageRecord extends MessageRecord { int status, List mismatches) { super(context, id, body, recipients, individualRecipient, recipientDeviceId, - dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), receiptCount, type, + dateSent, dateReceived, threadId, status, receiptCount, type, mismatches, new LinkedList()); } @@ -104,16 +104,4 @@ public class SmsMessageRecord extends MessageRecord { public boolean isMmsNotification() { return false; } - - private static int getGenericDeliveryStatus(int status) { - if (status == SmsDatabase.Status.STATUS_NONE) { - return MessageRecord.DELIVERY_STATUS_NONE; - } else if (status >= SmsDatabase.Status.STATUS_FAILED) { - return MessageRecord.DELIVERY_STATUS_FAILED; - } else if (status >= SmsDatabase.Status.STATUS_PENDING) { - return MessageRecord.DELIVERY_STATUS_PENDING; - } else { - return MessageRecord.DELIVERY_STATUS_RECEIVED; - } - } } diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 471d26da6..f6a8af904 100644 --- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -28,6 +28,7 @@ import android.text.style.StyleSpan; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.GroupUtil; @@ -48,9 +49,10 @@ public class ThreadRecord extends DisplayRecord { public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, @NonNull Recipients recipients, long date, long count, boolean read, - long threadId, long snippetType, int distributionType, boolean archived) + long threadId, int receiptCount, int status, long snippetType, + int distributionType, boolean archived) { - super(context, body, recipients, date, date, threadId, snippetType); + super(context, body, recipients, date, date, threadId, status, receiptCount, snippetType); this.context = context.getApplicationContext(); this.snippetUri = snippetUri; this.count = count;