diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java index 1e7065df7b68732bf1d539e80d92301173e0fa6c..84fddd0846882ceaf55341feab784316ce9380bc 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java @@ -13,6 +13,8 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.util.BriarAdapter; import org.briarproject.briar.android.util.ItemReturningAdapter; +import java.util.Collection; + import androidx.annotation.LayoutRes; import androidx.annotation.Nullable; import androidx.recyclerview.selection.SelectionTracker; @@ -20,13 +22,14 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView.RecycledViewPool; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; +import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; @NotNullByDefault class ConversationAdapter extends BriarAdapter implements ItemReturningAdapter { - private ConversationListener listener; + private final ConversationListener listener; private final RecycledViewPool imageViewPool; private final ImageItemDecoration imageItemDecoration; @Nullable @@ -65,22 +68,20 @@ class ConversationAdapter @LayoutRes int type) { View v = LayoutInflater.from(viewGroup.getContext()).inflate( type, viewGroup, false); - switch (type) { - case R.layout.list_item_conversation_msg_in: - return new ConversationMessageViewHolder(v, listener, true, - imageViewPool, imageItemDecoration); - case R.layout.list_item_conversation_msg_out: - return new ConversationMessageViewHolder(v, listener, false, - imageViewPool, imageItemDecoration); - case R.layout.list_item_conversation_notice_in: - return new ConversationNoticeViewHolder(v, listener, true); - case R.layout.list_item_conversation_notice_out: - return new ConversationNoticeViewHolder(v, listener, false); - case R.layout.list_item_conversation_request: - return new ConversationRequestViewHolder(v, listener, true); - default: - throw new IllegalArgumentException("Unknown ConversationItem"); + if (type == R.layout.list_item_conversation_msg_in) { + return new ConversationMessageViewHolder(v, listener, true, + imageViewPool, imageItemDecoration); + } else if (type == R.layout.list_item_conversation_msg_out) { + return new ConversationMessageViewHolder(v, listener, false, + imageViewPool, imageItemDecoration); + } else if (type == R.layout.list_item_conversation_notice_in) { + return new ConversationNoticeViewHolder(v, listener, true); + } else if (type == R.layout.list_item_conversation_notice_out) { + return new ConversationNoticeViewHolder(v, listener, false); + } else if (type == R.layout.list_item_conversation_request) { + return new ConversationRequestViewHolder(v, listener, true); } + throw new IllegalArgumentException("Unknown ConversationItem"); } @Override @@ -107,22 +108,49 @@ class ConversationAdapter return c1.equals(c2); } - void setSelectionTracker(SelectionTracker tracker) { - this.tracker = tracker; + @Override + public void add(ConversationItem item) { + items.beginBatchedUpdates(); + items.add(item); + updateTimersInBatch(); + items.endBatchedUpdates(); } - @Nullable - ConversationItem getLastItem() { - if (items.size() > 0) { - return items.get(items.size() - 1); - } else { - return null; + @Override + public void addAll(Collection itemsToAdd) { + items.beginBatchedUpdates(); + // there can be items already in the adapter + // SortedList takes care of duplicates and detecting changed items + items.addAll(itemsToAdd); + updateTimersInBatch(); + items.endBatchedUpdates(); + } + + private void updateTimersInBatch() { + long lastTimerIncoming = NO_AUTO_DELETE_TIMER; + long lastTimerOutgoing = NO_AUTO_DELETE_TIMER; + for (int i = 0; i < items.size(); i++) { + ConversationItem c = items.get(i); + boolean itemChanged; + boolean timerChanged; + if (c.isIncoming()) { + timerChanged = lastTimerIncoming != c.getAutoDeleteTimer(); + lastTimerIncoming = c.getAutoDeleteTimer(); + } else { + timerChanged = lastTimerOutgoing != c.getAutoDeleteTimer(); + lastTimerOutgoing = c.getAutoDeleteTimer(); + } + itemChanged = c.setTimerNoticeVisible(timerChanged); + if (itemChanged) items.updateItemAt(i, c); } } + void setSelectionTracker(SelectionTracker tracker) { + this.tracker = tracker; + } + SparseArray getOutgoingMessages() { SparseArray messages = new SparseArray<>(); - for (int i = 0; i < items.size(); i++) { ConversationItem item = items.get(i); if (!item.isIncoming()) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItem.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItem.java index 470a5fd2c870ef3d319ccbda1864eb2d3709f341..c3d96e46262464a841a406b906503605fa7fc19b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItem.java @@ -9,6 +9,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import androidx.annotation.LayoutRes; +import androidx.lifecycle.LiveData; import static org.briarproject.bramble.util.StringUtils.toHexString; @@ -24,9 +25,11 @@ abstract class ConversationItem { private final GroupId groupId; private final long time, autoDeleteTimer; private final boolean isIncoming; - private boolean read, sent, seen; + private final LiveData contactName; + private boolean read, sent, seen, showTimerNotice; - ConversationItem(@LayoutRes int layoutRes, ConversationMessageHeader h) { + ConversationItem(@LayoutRes int layoutRes, ConversationMessageHeader h, + LiveData contactName) { this.layoutRes = layoutRes; this.text = null; this.id = h.getId(); @@ -37,6 +40,8 @@ abstract class ConversationItem { this.sent = h.isSent(); this.seen = h.isSeen(); this.isIncoming = !h.isLocal(); + this.contactName = contactName; + this.showTimerNotice = false; } @LayoutRes @@ -116,4 +121,25 @@ abstract class ConversationItem { return isIncoming; } + public LiveData getContactName() { + return contactName; + } + + /** + * Set this to true when {@link #getAutoDeleteTimer()} has changed + * since the last message from the same peer. + * + * @return true if the value was set, false if it was already set. + */ + boolean setTimerNoticeVisible(boolean visible) { + if (this.showTimerNotice != visible) { + this.showTimerNotice = visible; + return true; + } + return false; + } + + boolean isTimerNoticeVisible() { + return showTimerNotice; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java index 7e9cd582d2b5dbc2a690088d7150ca7a7aff75a3..8c3dc790d4647342b0f3167d373cee892475614a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java @@ -1,5 +1,6 @@ package org.briarproject.briar.android.conversation; +import android.content.Context; import android.view.View; import android.widget.TextView; @@ -27,7 +28,7 @@ abstract class ConversationItemViewHolder extends ViewHolder { protected final ConstraintLayout layout; @Nullable private final OutItemViewHolder outViewHolder; - private final TextView text; + private final TextView topNotice, text; protected final TextView time; private final View bomb; @Nullable @@ -39,6 +40,7 @@ abstract class ConversationItemViewHolder extends ViewHolder { this.listener = listener; this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v); root = v; + topNotice = v.findViewById(R.id.topNotice); layout = v.findViewById(R.id.layout); text = v.findViewById(R.id.text); time = v.findViewById(R.id.time); @@ -50,6 +52,8 @@ abstract class ConversationItemViewHolder extends ViewHolder { itemKey = item.getKey(); root.setActivated(selected); + setTopNotice(item); + if (item.getText() != null) { text.setText(trim(item.getText())); } @@ -72,4 +76,28 @@ abstract class ConversationItemViewHolder extends ViewHolder { return itemKey; } + private void setTopNotice(ConversationItem item) { + if (item.isTimerNoticeVisible()) { + Context ctx = itemView.getContext(); + topNotice.setVisibility(VISIBLE); + boolean enabled = item.getAutoDeleteTimer() != NO_AUTO_DELETE_TIMER; + String text; + if (item.isIncoming()) { + String name = item.getContactName().getValue(); + int strRes = enabled ? + R.string.auto_delete_msg_contact_enabled : + R.string.auto_delete_msg_contact_disabled; + text = ctx.getString(strRes, name); + } else { + int strRes = enabled ? + R.string.auto_delete_msg_you_enabled : + R.string.auto_delete_msg_you_disabled; + text = ctx.getString(strRes); + } + topNotice.setText(text); + } else { + topNotice.setVisibility(GONE); + } + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java index 8fc50d2996f5ebc4e9907ec1b1fd013adf90394f..b9185c1cea3f154c860143762a73717604f79e85 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageItem.java @@ -10,6 +10,7 @@ import javax.annotation.concurrent.NotThreadSafe; import androidx.annotation.LayoutRes; import androidx.annotation.UiThread; +import androidx.lifecycle.LiveData; @NotThreadSafe @NotNullByDefault @@ -18,8 +19,8 @@ class ConversationMessageItem extends ConversationItem { private final List attachments; ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h, - List attachments) { - super(layoutRes, h); + LiveData contactName, List attachments) { + super(layoutRes, h, contactName); this.attachments = attachments; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeItem.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeItem.java index 0694a0762d8e4b2149e0a4f7cbd0634a5644184b..50400017a17520b06f7ef447dbe50c3a6000648e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeItem.java @@ -8,6 +8,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import androidx.annotation.LayoutRes; +import androidx.lifecycle.LiveData; @NotThreadSafe @NotNullByDefault @@ -17,15 +18,15 @@ class ConversationNoticeItem extends ConversationItem { private final String msgText; ConversationNoticeItem(@LayoutRes int layoutRes, String text, - ConversationRequest r) { - super(layoutRes, r); + LiveData contactName, ConversationRequest r) { + super(layoutRes, r, contactName); this.text = text; this.msgText = r.getText(); } ConversationNoticeItem(@LayoutRes int layoutRes, String text, - ConversationResponse r) { - super(layoutRes, r); + LiveData contactName, ConversationResponse r) { + super(layoutRes, r, contactName); this.text = text; this.msgText = null; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestItem.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestItem.java index 71984db657f6778038ffa41678efa45a41ee9472..5f7027d636ba5f3ecdff3badc37b1d1ef972830a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestItem.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestItem.java @@ -11,6 +11,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; import androidx.annotation.LayoutRes; +import androidx.lifecycle.LiveData; @NotThreadSafe @NotNullByDefault @@ -26,14 +27,15 @@ class ConversationRequestItem extends ConversationNoticeItem { private boolean answered; ConversationRequestItem(@LayoutRes int layoutRes, String text, - RequestType type, ConversationRequest r) { - super(layoutRes, text, r); + LiveData contactName, RequestType type, + ConversationRequest r) { + super(layoutRes, text, contactName, r); this.requestType = type; this.sessionId = r.getSessionId(); this.answered = r.wasAnswered(); if (r instanceof InvitationRequest) { this.requestedGroupId = ((Shareable) r.getNameable()).getId(); - this.canBeOpened = ((InvitationRequest) r).canBeOpened(); + this.canBeOpened = ((InvitationRequest) r).canBeOpened(); } else { this.requestedGroupId = null; this.canBeOpened = false; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java index 46468c03060bfff86addd1d0b371d0cec3f79857..756c8535e5837be1c4ef918e58ed8b61f8e873a2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationVisitor.java @@ -60,10 +60,12 @@ class ConversationVisitor implements } if (h.isLocal()) { item = new ConversationMessageItem( - R.layout.list_item_conversation_msg_out, h, attachments); + R.layout.list_item_conversation_msg_out, h, contactName, + attachments); } else { item = new ConversationMessageItem( - R.layout.list_item_conversation_msg_in, h, attachments); + R.layout.list_item_conversation_msg_in, h, contactName, + attachments); } if (h.hasText()) { String text = textCache.getText(h.getId()); @@ -79,13 +81,15 @@ class ConversationVisitor implements String text = ctx.getString(R.string.blogs_sharing_invitation_sent, r.getName(), contactName.getValue()); return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text = ctx.getString( R.string.blogs_sharing_invitation_received, contactName.getValue(), r.getName()); return new ConversationRequestItem( - R.layout.list_item_conversation_request, text, BLOG, r); + R.layout.list_item_conversation_request, text, contactName, + BLOG, r); } } @@ -104,7 +108,8 @@ class ConversationVisitor implements contactName.getValue()); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text; if (r.wasAccepted()) { @@ -117,7 +122,8 @@ class ConversationVisitor implements contactName.getValue()); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_in, text, r); + R.layout.list_item_conversation_notice_in, text, + contactName, r); } } @@ -128,13 +134,15 @@ class ConversationVisitor implements String text = ctx.getString(R.string.forum_invitation_sent, r.getName(), contactName.getValue()); return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text = ctx.getString( R.string.forum_invitation_received, contactName.getValue(), r.getName()); return new ConversationRequestItem( - R.layout.list_item_conversation_request, text, FORUM, r); + R.layout.list_item_conversation_request, text, contactName, + FORUM, r); } } @@ -153,7 +161,8 @@ class ConversationVisitor implements contactName.getValue()); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text; if (r.wasAccepted()) { @@ -166,7 +175,8 @@ class ConversationVisitor implements contactName.getValue()); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_in, text, r); + R.layout.list_item_conversation_notice_in, text, + contactName, r); } } @@ -178,13 +188,15 @@ class ConversationVisitor implements R.string.groups_invitations_invitation_sent, contactName.getValue(), r.getName()); return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text = ctx.getString( R.string.groups_invitations_invitation_received, contactName.getValue(), r.getName()); return new ConversationRequestItem( - R.layout.list_item_conversation_request, text, GROUP, r); + R.layout.list_item_conversation_request, text, contactName, + GROUP, r); } } @@ -203,7 +215,8 @@ class ConversationVisitor implements contactName.getValue()); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text; if (r.wasAccepted()) { @@ -216,7 +229,8 @@ class ConversationVisitor implements contactName.getValue()); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_in, text, r); + R.layout.list_item_conversation_notice_in, text, + contactName, r); } } @@ -227,7 +241,8 @@ class ConversationVisitor implements String text = ctx.getString(R.string.introduction_request_sent, contactName.getValue(), name); return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text; if (r.wasAnswered()) { @@ -243,7 +258,7 @@ class ConversationVisitor implements contactName.getValue(), name); } return new ConversationRequestItem( - R.layout.list_item_conversation_request, text, + R.layout.list_item_conversation_request, text, contactName, INTRODUCTION, r); } } @@ -268,7 +283,8 @@ class ConversationVisitor implements introducedAuthor); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_out, text, r); + R.layout.list_item_conversation_notice_out, text, + contactName, r); } else { String text; if (r.wasAccepted()) { @@ -288,7 +304,8 @@ class ConversationVisitor implements introducedAuthor); } return new ConversationNoticeItem( - R.layout.list_item_conversation_notice_in, text, r); + R.layout.list_item_conversation_notice_in, text, + contactName, r); } } diff --git a/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml b/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml index 93f197983a3fcf1927a878bdd2f59f426a66d83b..bb85bc7b5f0405479f79c0cd739affb17faa17ce 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml @@ -1,4 +1,6 @@ + + + + - + android:background="@drawable/list_item_background_selectable" + android:orientation="vertical"> + + + - + diff --git a/briar-android/src/main/res/layout/list_item_conversation_msg_in_content.xml b/briar-android/src/main/res/layout/list_item_conversation_msg_in_content.xml index eb1397747e3bb52eea2c92bb9fc5ae29650954d7..398f6507b8be97a36f9cd245965a84d90cfb90f0 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_msg_in_content.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_msg_in_content.xml @@ -1,4 +1,6 @@ + + - + android:background="@drawable/list_item_background_selectable" + android:orientation="vertical" + android:paddingTop="@dimen/message_bubble_margin"> + + - \ No newline at end of file + \ No newline at end of file diff --git a/briar-android/src/main/res/layout/list_item_conversation_notice_in.xml b/briar-android/src/main/res/layout/list_item_conversation_notice_in.xml index 65c20141706692efb454ded0f218408bec997341..829c0e1ba06b3a6ae0fee539e30d581ce5f7afff 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_notice_in.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_notice_in.xml @@ -5,8 +5,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_item_background_selectable" - android:orientation="vertical" - android:paddingTop="@dimen/message_bubble_margin"> + android:orientation="vertical"> + + + tools:text="Short message" + tools:visibility="visible" /> + + + tools:text="This is a long long long message that spans over several lines.\n\nIt ends here." + tools:visibility="visible" /> + android:orientation="vertical"> + + + diff --git a/briar-android/src/main/res/layout/list_item_conversation_top_notice_out.xml b/briar-android/src/main/res/layout/list_item_conversation_top_notice_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..38935f5fd2d78f8e983c2c7bdb999ca59e210aad --- /dev/null +++ b/briar-android/src/main/res/layout/list_item_conversation_top_notice_out.xml @@ -0,0 +1,21 @@ + + diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index f7c72e850ac2fa2ee2830cb1db8a4b065560df79..3cef1a96f2ee7143cdc8502500aeda512b8aca4c 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -166,6 +166,10 @@ Contact name Change Disappearing messages + Your messages will disappear after 7 days. + Your messages will not disappear. + %1$s\'s messages will disappear after 7 days. + %1$s\'s messages will not disappear. Delete all messages Confirm Message Deletion Are you sure that you want to delete all messages?