From e98d4f2260cf2951a1430b46f7fadb850381b14b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 23 Dec 2015 15:33:41 -0200 Subject: [PATCH] Use a RecyclerView for the ConversationView and properly notify the view adapter of dataset changes in order to avoid invalidating the entire dataset when not absolutely necessary. This change also shows unread messages in a different color, so users do not fail to notice delayed messages. --- .../res/drawable-hdpi/msg_in_unread.9.png | Bin 0 -> 1560 bytes .../res/drawable-mdpi/msg_in_unread.9.png | Bin 0 -> 1014 bytes briar-android/res/drawable-xhdpi/msg_in.9.png | Bin 0 -> 1453 bytes .../res/drawable-xhdpi/msg_in_unread.9.png | Bin 0 -> 2374 bytes .../res/drawable-xhdpi/msg_out.9.png | Bin 0 -> 2258 bytes .../res/drawable-xxhdpi/msg_in_unread.9.png | Bin 0 -> 3680 bytes .../res/layout/activity_conversation.xml | 14 +- briar-android/res/layout/list_item_msg_in.xml | 1 + .../res/layout/list_item_msg_out.xml | 1 + .../android/contact/ConversationActivity.java | 87 ++++---- .../android/contact/ConversationAdapter.java | 187 ++++++++++++++---- .../contact/ConversationItemComparator.java | 18 -- 12 files changed, 210 insertions(+), 98 deletions(-) create mode 100644 briar-android/res/drawable-hdpi/msg_in_unread.9.png create mode 100644 briar-android/res/drawable-mdpi/msg_in_unread.9.png create mode 100644 briar-android/res/drawable-xhdpi/msg_in.9.png create mode 100644 briar-android/res/drawable-xhdpi/msg_in_unread.9.png create mode 100644 briar-android/res/drawable-xhdpi/msg_out.9.png create mode 100644 briar-android/res/drawable-xxhdpi/msg_in_unread.9.png delete mode 100644 briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java diff --git a/briar-android/res/drawable-hdpi/msg_in_unread.9.png b/briar-android/res/drawable-hdpi/msg_in_unread.9.png new file mode 100644 index 0000000000000000000000000000000000000000..c22cc86322f710ab37c0f56c192f9721271f58d9 GIT binary patch literal 1560 zcmV+z2Iu*SP)Os!AFXzgz-nQ#Refiq{V=b5*2nq&<0H!A5bKf)W(uZh?_J` zlZ@XPXNKLK*_=DCfR76yyK~Pu-}%nH_uO;utPswoTaOg_P#;f-=IOD9FEnl5mOXSx zsj|LQ3sGBc#P0l5(`GAnx?l@ibFZpCP*D?7x7@T{pDDVoi&$e)6uQ$kH+^Gc!^SFB zQ>xJDLE-UTFS$C9ID2-&wd1RJ;yWp_`h^RpjNn?H8F-gI7W#=3-zs%=yNj*B)0Nh2 zwmz@xhQ~Gq656XM_m?ZsbFiq*YuY+}CM5&SYM*2>FLKe_ZV6!Qw`1&Ya|#xcA@@x@ zu@-yhywa|!PjZTmQP>dTS5CADe&&00xhjVRLiP@~yH z?Xl^xdW5y$urfK>6Z>-JO$QfOVbX4DdZQ#_oNrdY>zv(8xkLf}m&^n=MO%S{;Xj6B z^JXjLhK6o(!X>2lb#?8eg~%z{2%pCjxAtYTF=+)7gc9pku&{FOP`|yPEaB%=bq78> zk{q5b2qay5ipyB{E93CWn(3B7OoD1m0{LufDNatliWPC3vKkv)jOX3H7&JBYd080j z+~=HNnQ#VV`YVFaf-F37U$@()Dr2}<#xd|@JeSEluXJl8UX4|fcQCmZyHRp<^pd{1 zx)v6Qg9+9jX=(bCzh9cD*@EYDrihARkC?h4wZLTT3-LD4GdD~h7a zr24A-;lnTMjg5U&KXT6K%gYyAj~sdG`*>o>3YCLN`Ir>i9y*5N$4=WEh}GYssx_#9 zSBVKbyTIUeS;NJ0s2piaHW~{;2L+!XHPRq{08AZwv-9PpSK$8$2zJRky@tk3ezQHE z4sL%&1_B1Px#8hO0Bnl{1OPPzfc0@l7yzz|G1t-YY?uJ9An9}GJ`=sYyrfRIrF0{UEPVqzdN5DN&5ev1^u0*V|Y z3;?N3D-0ku)yo{|D>!18ODu>OAAjEu8yLo+;P1wQq(?_zqq0*P2<0r5wWysa!5WP% ziEwR2nfM<3?Id{#Fk)~WYppC#W4z_) z(KK|HiwThpb|rE^Fi@Wx9Gr@r1UZ605f)*V`xTf7&LA<|=s(O64y&KA2-A&-)Zw*~ zBbb|^6qsI!x?u($5ibrzDN97c$}Um*$=d!`jR?!Ok^rS%y!qJjHR9%vuYJVaJ8JU> z52~WMdC+$xLR9ob#3WFY@9gXpt*yi64j!UA{!K{{F$q-X_V3S#wzg9u#3Q$V*Gn~7 zA|SCXf8an;-?QgKnQO; zK1g5%}fVq%@IoLQuR0LO-_W^Z&ekq&Q48Lim2OWqK@>J6z90 zP^RE{M1lfFrT;&Xs3PmvZWtr7t}iZL^Xq_DJrm3E3Oz6pq~oPmdU{e~c6QpUQ=a94 zL0E)YuFKY192ipY(l0{%oa*bNAH3a#^9kP68)uZjK{B!F$6o6&wv7LOz~Omd(&v&E zmQTM1Y|Rc3p)BC%WX65JK}4d*8|<8aHF;b{mNf{z0A3Rt2XQa0000< KMNUMnLSTaApWM#? literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-mdpi/msg_in_unread.9.png b/briar-android/res/drawable-mdpi/msg_in_unread.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6e5418856d9fb9ed59be30ccbb013e277ed6345e GIT binary patch literal 1014 zcmVHeErtF7 zZ^5w?3cV;r6hypuuqc$WMS3WRhTeoA(o#r@htiXW2vr3A0V-`%+*X_QeYd;o?#|3+ zHcbQvmLxOpJoC&m@5gKi5i|;YR=cLt>gb?_vR`5vcM05g|+=oy@?6otn268)4W z-WkS@bh{ZsPRQumuuGc$MQ7}Eq+lgY{}^c?8pM+htBFdYqHe2tE`U9*VE7%jjbm-_ z1VDU~f5Z2m?={_$84!>r+KU}D-p`~*96i9D6rNr<>gr(!wo|>*^@NNL1|qZ`0FDQQ z9RfEo2|4w~FofqQ0^~HolmOoH$q5P-61I)dd+X+x5WF;A#u?N6IVDUB$L=zAMwzZ- z=!J54;%?##d8^~G2jDa5iXWPYlT7f|Uz?Sp*kk)D#X}{-fP;!|7Z^*HWx|5<9H|F@ z0 z5$8mjX-g>;XGj0#z`r0?SkN%Jl+QD(v-1YYWV&f0fvH%jS+$x;*4Ha^b#?aq%*=Dh zIQFFiCrd9DB4p6`@Z!Y82a-x1lU^?)C;{mt>FDSqxm;hlr)T`q*ceu=A5tfgVF8gg z3M7=aU&jb>5YIJ3Z~OCbNu0R$Hldi6JuaEZ`in0I9-%_v=2gae<1V0TuN1z{+sa0NrTASHIIdk&ZX|IR z71{rvM1ZwPR#xVkp#b98cuo^ujAZu_xN#-632BG9N8y1#VC>sh+y)S29iDG6c@zE5 kyP-j6oC+LwVHJD&2RE55#s2gDG5`Po07*qoM6N<$f>(dd*Z=?k literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xhdpi/msg_in.9.png b/briar-android/res/drawable-xhdpi/msg_in.9.png new file mode 100644 index 0000000000000000000000000000000000000000..f5db8372dda83faf1082e8700e0c2996c6801fa6 GIT binary patch literal 1453 zcmV;e1ycHnP)O4KW%?uxN}_3n3P(G=f-Pr=d19 zl%_y>vDH3^QUjuaC?Tndnkd!;Q6Y^QCG@WC-SuwovE%pc?VZPQC!Tfp&dy9anR_%l z|NY(fee>(cr)B#^- z8P&)UYe;DDqxAnOPQbSS$WT?Qm2HNR0+6t3%xZH2$?F=0l;C&$dA|b-N4v7AhX=kHgO8*6U`*IG&I3Z0D0RKD zan1Zy58nN*OH;2ZMm*4?vF~^H9C&y`W9tcnG2OC`vs}ZrQ3I_3C@71|7t8_(?-dII z7VH}}pcd4G1^^JwG6Fk?PcR1RYS~izI0(72*rNn$KrN^#*Lm9pLXji@LA1GZ%I+(b zArrNrCe%i%s-*ukSmrCTOl1f)+SDnoYf1$KYN3Q`k_@BP&t)VPD{OZSi4xWB(V@U3 zN<~fPc}M)t#0Vgde+ntJ0-8*1mtX7<2QAk03kr+10imG z35yUQ1PB2_fG8peyeVTaCKLAmnJ!T5fPlvZ5bBPt>ngYek%9nMHSW%)7jM4nrW;$h z1Ca@#cYDJvcPzPKk0jBlT*U|rf^1sCw)(}lFDdr|gc)1~%M1upyl>SlT7K)IPFa#J z;BC}MTS8moy6Y=%tLOp>%a?X5vOI^kJR%L@^}6Z>EAL%~5a#lh zP?->LDz9iMf5x~C5yT*6~!Ndbbox%RPn)hjD|HC4H6GNjlA=Mjn|$)5TrFMZ_x zJNsOkdIh)F8aR;`3P)r@*rd9%RWJxmk;v9}I)-2Hw66(A^j~?l2nIFY(R*;{+0D&Q zMfB)tEU zzF@?BNDCn70AmbVU&r2q-@e@Jt&PRvqg-W{nJ^?u0tjPg=f1BzZSSv-#S;Opt~F9H zAkB_gF#Oxvd%kGe)w@2PNQAh$`pkkciZI@6?HXwDe(+qvFv48jj~E*0$%J9Xj8~ey zN7_33>j6fT8xUDA!YLRHZ|^$1r|08MT!F}88KcmSK#uVlcOWug#1RH#ECg>tT!P4i zVKAbzcFZ-1Qe;cOU~<*6dN z#HqShchsO3)P&kdceml3Yfu`6Um(!8|MMS~R@FRu^vh%0eIsLoP60UM8$E`7V&8iY z9663!P!mZml+PYbaz@vj2QGB~h}qNKvcZ@O4mS77L^;&~w(Brw#KLr>7)=CgJM_{rr00000NkvXX Hu0mjf06vr@ literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xhdpi/msg_in_unread.9.png b/briar-android/res/drawable-xhdpi/msg_in_unread.9.png new file mode 100644 index 0000000000000000000000000000000000000000..341ec4f7226f0362bf1cd5d5f354f06f5666845a GIT binary patch literal 2374 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 literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xxhdpi/msg_in_unread.9.png b/briar-android/res/drawable-xxhdpi/msg_in_unread.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3a3bb3e7e65841e35b46dbd4c4fc2a7584bf2761 GIT binary patch literal 3680 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 - + + android:indeterminate="true" + android:visibility="gone"/> + android:text="@string/no_private_messages" + android:visibility="gone"/> = 0 && position < adapter.getCount()) + if (position >= 0 && position < adapter.getItemCount()) displayMessage(position); } } @@ -341,7 +345,7 @@ implements EventListener, OnClickListener, OnItemClickListener { private void markMessagesRead() { notificationManager.clearPrivateMessageNotification(contactId); List unread = new ArrayList(); - int count = adapter.getCount(); + int count = adapter.getItemCount(); for (int i = 0; i < count; i++) { PrivateMessageHeader h = adapter.getItem(i).getHeader(); if (!h.isRead()) unread.add(h.getId()); @@ -381,6 +385,8 @@ implements EventListener, OnClickListener, OnItemClickListener { GroupId g = ((MessageAddedEvent) e).getGroupId(); if (g.equals(groupId)) { LOG.info("Message added, reloading"); + // TODO: find a way of not needing to reload the entire + // conversation just because one message was added loadHeaders(); } } else if (e instanceof MessagesSentEvent) { @@ -417,16 +423,14 @@ implements EventListener, OnClickListener, OnItemClickListener { runOnUiThread(new Runnable() { public void run() { Set messages = new HashSet(messageIds); - boolean changed = false; - int count = adapter.getCount(); + int count = adapter.getItemCount(); for (int i = 0; i < count; i++) { ConversationItem item = adapter.getItem(i); if (messages.contains(item.getHeader().getId())) { item.setStatus(status); - changed = true; + adapter.notifyItemChanged(i); } } - if (changed) adapter.notifyDataSetChanged(); } }); } @@ -444,7 +448,7 @@ implements EventListener, OnClickListener, OnItemClickListener { private long getMinTimestampForNewMessage() { // Don't use an earlier timestamp than the newest message long timestamp = 0; - int count = adapter.getCount(); + int count = adapter.getItemCount(); for (int i = 0; i < count; i++) { long t = adapter.getItem(i).getHeader().getTimestamp(); if (t > timestamp) timestamp = t; @@ -485,11 +489,6 @@ implements EventListener, OnClickListener, OnItemClickListener { }); } - public void onItemClick(AdapterView parent, View view, int position, - long id) { - displayMessage(position); - } - private void displayMessage(int position) { ConversationItem item = adapter.getItem(position); PrivateMessageHeader header = item.getHeader(); diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index 0e2499c7e..4b945f7d3 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -1,11 +1,12 @@ package org.briarproject.android.contact; import android.content.Context; +import android.support.v7.util.SortedList; +import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; @@ -13,57 +14,177 @@ import org.briarproject.R; import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.util.StringUtils; -import java.util.ArrayList; - import static org.briarproject.api.messaging.PrivateMessageHeader.Status.DELIVERED; import static org.briarproject.api.messaging.PrivateMessageHeader.Status.SENT; -class ConversationAdapter extends ArrayAdapter { +class ConversationAdapter extends + RecyclerView.Adapter { - ConversationAdapter(Context ctx) { - super(ctx, android.R.layout.simple_expandable_list_item_1, - new ArrayList()); + private static final int MSG_OUT = 0; + private static final int MSG_IN = 1; + private static final int MSG_IN_UNREAD = 2; + + private SortedList messages = + new SortedList(ConversationItem.class, + new SortedList.Callback() { + @Override + public void onInserted(int position, int count) { + notifyItemRangeInserted(position, count); + } + + @Override + public void onChanged(int position, int count) { + notifyItemRangeChanged(position, count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + notifyItemMoved(fromPosition, toPosition); + } + + @Override + public void onRemoved(int position, int count) { + notifyItemRangeRemoved(position, count); + } + + @Override + public int compare(ConversationItem c1, + ConversationItem c2) { + long time1 = c1.getHeader().getTimestamp(); + long time2 = c2.getHeader().getTimestamp(); + if (time1 < time2) return -1; + if (time1 > time2) return 1; + return 0; + } + + @Override + public boolean areItemsTheSame(ConversationItem c1, + ConversationItem c2) { + return c1.getHeader().getId() + .equals(c2.getHeader().getId()); + } + + @Override + public boolean areContentsTheSame(ConversationItem c1, + ConversationItem c2) { + return c1.equals(c2); + } + }); + private Context ctx; + + public ConversationAdapter(Context context) { + ctx = context; } @Override - public View getView(int position, View convertView, ViewGroup parent) { - ConversationItem item = getItem(position); - PrivateMessageHeader header = item.getHeader(); - Context ctx = getContext(); - - LayoutInflater inflater = (LayoutInflater) ctx.getSystemService - (Context.LAYOUT_INFLATER_SERVICE); - - View v; + public int getItemViewType(int position) { + // return different type for incoming and outgoing (local) messages + PrivateMessageHeader header = getItem(position).getHeader(); if (header.isLocal()) { - v = inflater.inflate(R.layout.list_item_msg_out, null); - - ImageView status = (ImageView) v.findViewById(R.id.msgStatus); - if (item.getStatus() == DELIVERED) { - status.setImageResource(R.drawable.message_delivered); - } else if (item.getStatus() == SENT) { - status.setImageResource(R.drawable.message_sent); - } else { - status.setImageResource(R.drawable.message_stored); - } + return MSG_OUT; + } else if (header.isRead()) { + return MSG_IN; } else { - v = inflater.inflate(R.layout.list_item_msg_in, null); + return MSG_IN_UNREAD; + } + } + + @Override + public MessageHolder onCreateViewHolder(ViewGroup viewGroup, int type) { + View v; + + // outgoing message (local) + if (type == MSG_OUT) { + v = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.list_item_msg_out, viewGroup, false); + } + // incoming message (non-local) + else { + v = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.list_item_msg_in, viewGroup, false); } - TextView body = (TextView) v.findViewById(R.id.msgBody); + return new MessageHolder(v, type); + } + + @Override + public void onBindViewHolder(final MessageHolder ui, final int position) { + ConversationItem item = getItem(position); + PrivateMessageHeader header = item.getHeader(); + + if (header.isLocal()) { + if (item.getStatus() == DELIVERED) { + ui.status.setImageResource(R.drawable.message_delivered); + } else if (item.getStatus() == SENT) { + ui.status.setImageResource(R.drawable.message_sent); + } else { + ui.status.setImageResource(R.drawable.message_stored); + } + } else if (!header.isRead()) { + // show unread messages in different color to not miss them + ui.layout.setBackgroundResource(R.drawable.msg_in_unread); + } if (item.getBody() == null) { - body.setText("\u2026"); + ui.body.setText("\u2026"); } else if (header.getContentType().equals("text/plain")) { - body.setText(StringUtils.fromUtf8(item.getBody())); + ui.body.setText(StringUtils.fromUtf8(item.getBody())); } else { // TODO support other content types } - TextView date = (TextView) v.findViewById(R.id.msgTime); long timestamp = header.getTimestamp(); - date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp)); + ui.date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp)); + } - return v; + @Override + public int getItemCount() { + return messages == null ? 0 : messages.size(); + } + + public boolean isEmpty() { + return messages == null || messages.size() == 0; + } + + public ConversationItem getItem(int position) { + return messages.get(position); + } + + public void add(final ConversationItem contact) { + this.messages.add(contact); + } + + public void remove(final ConversationItem contact) { + this.messages.remove(contact); + } + + public void clear() { + this.messages.beginBatchedUpdates(); + + while(messages.size() != 0) { + messages.removeItemAt(0); + } + + this.messages.endBatchedUpdates(); + } + + public static class MessageHolder extends RecyclerView.ViewHolder { + public ViewGroup layout; + public TextView body; + public TextView date; + public ImageView status; + + public MessageHolder(View v, int type) { + super(v); + + layout = (ViewGroup) v.findViewById(R.id.msgLayout); + body = (TextView) v.findViewById(R.id.msgBody); + date = (TextView) v.findViewById(R.id.msgTime); + + // outgoing message (local) + if (type == MSG_OUT) { + status = (ImageView) v.findViewById(R.id.msgStatus); + } + } } } \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java b/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java deleted file mode 100644 index 76eb1d28d..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationItemComparator.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.briarproject.android.contact; - -import java.util.Comparator; - -class ConversationItemComparator implements Comparator { - - static final ConversationItemComparator INSTANCE = - new ConversationItemComparator(); - - public int compare(ConversationItem a, ConversationItem b) { - // The oldest message comes first - long aTime = a.getHeader().getTimestamp(); - long bTime = b.getHeader().getTimestamp(); - if (aTime < bTime) return -1; - if (aTime > bTime) return 1; - return 0; - } -}