From e98d4f2260cf2951a1430b46f7fadb850381b14b Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> 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 | 85 +++++---- .../android/contact/ConversationAdapter.java | 177 +++++++++++++++--- .../contact/ConversationItemComparator.java | 18 -- 12 files changed, 204 insertions(+), 92 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 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 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 zcmV<S0}1?zP)<h;3K|Lk000e1NJLTq001HY001Tk1^@s7*3o%F00009a7bBm000XU z000XU0RWnu7ytkRs7XXYR9HvVSWRmaK^UHiUuha*#fx4<kXCTpdMF`C5k>HeErtF7 zZ^5w?3cV;r6hypuuqc$WMS3WRhTeoA(o#r@htiXW2vr3A0V-`%+*X_QeYd;o?#|3+ zHcbQvmLxOpJoC&m@5gKi5i|;YR=cLt>gb?_<LPIwX#zfJ_2S8={t4Jgh9)P+xXyV^ zuZgg|K5AKjJw0tNfX&9@aJUZ3G&kH%BuWq`-9136=Z-%h+3bD)443%dzK{X8xVvQ$ zcaI^$$~L#(KQP4DKGe_Gw?i~tDvT$Ftq$%A8^>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@ z<F1`hSQzw2sT9w(i_T^$bY$Cs@Ks{suE8+@QPcB=@l}xo@39B~r{WbYRUnT^cK29g z0|a6x1;4C?g;~|r{dOJ`NaI@rb8{mA^dc@$Kv)2(0?;jMaq(3gAntS!=vtw0XHOu` zWML7M0k_U$VMSFYww){h*GRcsh|&ebe+dD&hBnfqbQzov#t)*601T7THss<hOiF>0 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<Urt0ahbIL{aka?Dv^w>~OCbN<M;43Wlk-`TY1^7nIG9B;pb% zjf^}Zy}hHYA|Z{Me@VdatlUQ76(m_29)8?v75obeb3BxEQ=gi;eVwtcMk;b6M<~6E zV<Co{>u0R$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)<h;3K|Lk000e1NJLTq002S&002q|1^@s64+nyU00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU%KS@MERCwC# zoC|CeMHt6t_I1ZoibRYW6G@0c@sVms35wz?Vll>O4KW%?uxN}_3n3P(G=f-Pr=d19 zl%_y>vDH3^QUjuaC?Tndnkd!;Q6Y^QCG@WC-SuwovE%pc?VZPQC!Tfp&dy9anR_%l z|NY(fee><k&QMbNg=M{`L=^jAOM*eNC^_*kHtA2CQhI)W%Liim!W9$h8>(cr)B#^- z8P&)UYe;DDqxAnOPQbSS$WT?Qm2HNR0+6t3%xZH2$?F=0l;C&$dA|b-N<q+;Ra`f- z`K6lbi!PkC!tK(^!8BD{&4IC4M2~)bdU&M&>4v7AhX=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<2<k}o_;0gonk!gN{tJtL;>QAk03kr+10imG z35yUQ1PB2_fG8peyeVTaCKLAmnJ!T5fPlvZ5bBPt>ngYek%9nMHSW%)7jM4nrW;$h z1Ca@#cYDJvcPzPKk0jBlT*U|rf^1sCw)(}lFDdr|gc)1~%M1upyl>SlT7K)IPFa#J z;BC}MTS8moy6Y=%tLOp<vw0gn(-2y=Jag>>%a?X5vOI^kJR%L@^}6Z>EAL%~5a#lh zP?->LDz9<F>iMf5x~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)tE<r$yNWhSxLk)cR$<ZG-zSg=HU<~s(YA^^BF-l1gT7TERLxY<<?Q5gan4c>U 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 zVKAbz<m0}4f=I!DlJ`k?Za}1SaRGv(%u;|5AOr{jLVyq;1PEISEy08;pC&ukYnY2r zOX&s;t_8)$(&i8dLb1a{Fcki&R6vXc$B%<W7^sa5{mz_n4eT&};^Z%%mI?^eLMcV7 zFo?U%84$8e&AZ%BJn!iUgvL)68v<%TEvN~#k$HZ@5fF+b8Q>cFZ-1Qe;cOU~<*6dN z#HqShchsO3)P&kdceml3Yfu`6Um(!8|MMS~R@FRu^vh%0eIsLoP60UM8$E`7V&8iY z9663!P!mZml+PYbaz@vj2QGB~h}qNKvcZ@O4mS<n&Vb|2gd@8NvDRMNHMl0v3Qsl$ zFT-&1IOzlsNC;iS7mS><NZ#&snFe+Y!jNs8fmy<5E&!k~+(W{~CoCwg@}(pWFLemo z;{YK-{@OO-TvxV}0!{#dRba<a97;iuZuUvj8)?{@Hjw0!=eSkPjWn!DDQ7{XV4w&K zNW%J69N$_cAAr9Fk-ZU3s!Y4Oa>77L^;&~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 zcmV-M3Ay%(P)<h;3K|Lk000e1NJLTq002S&002q|1^@s7cmT2!00009a7bBm000XU z000XU0RWnu7ytkW_(?=TRCr$PTx)C;RTRF{=k}$9M@U*QU=#xdMF^r4XxfM+v`X-w z#6%JZBO#kwYg2xRMnW11!lF%$Mf{=hkr*Rj+5S*@Zfism4Y4(dF_a%zX;ErvOKG<| zerGx}o!On)nY%mFQteGP+wPuw&Ue3i?(3X8%b0nQ^dAfKk0gzL77&E|s>a4^1_4nN zQb<PqFj_$UoP=a;@d^paWsM-@S_x`GZ!xyPR%oRpeQqn{2$M|3i4%v+gteCC6qcJR z>u(R*HD2L|H1xHdVC?W=Lst}wqEBnOnJyn=bK?q7r7?jh_4<Bd>)L~sn&dCODJ+j{ z62%NN99qqzk~ADwz=Dz6GMiRR(svr&<eIsQD-4PrX%D6uT52>ia<Q;{gt~)-3`J(0 zicHNjar<kXD9-zqv8%R(+qhP`u~T%?7y^0rE;A-!XwHiEU^<#H6l!eb(V=q>RJ%}E zK8#LIza@&>d9=iE0!IkvNz(gCDdDNC%EolD>MJGf!JG(V595_-;i5g+j9uY1Y8bI< zWy2(@_f~i+eJA^XNs-{G@&GdR)FMF;S{obD0_!3T4E&l>RkhI6OEF($*|kr|&VJ}| zAT(6~SdQF(0S2riL7TB+2R8oqF(eX!E%o((I8|$C0(BiKcM2I9jz_6MK}#TT8I@-{ zwH`B1R8&>1=BXT`;A~;}^>R_1!=ogo8-tQ`4NB%aRbuJ~Va9GGrNC+B7!G(Us<KwD zj8@necw5ABJ}7Pv-VYUX&BipW(#Mz3Yu$!@RJ?W49?t|y7+Rt3F$s@4MQZD@0ulo@ z^?Al-zPNASabeoDl|0y`NMu-OYui)Q&~O0t1~Iitv<ct>Q6dQGr|aszXESCj<1ua~ zTOx|H7BP17UB*VZCP(bKGTi_`prC77pR&FfcJax!F46jqP6>%BcUsSJchCwl#26M* z_e;!j6>Wly18FW+p$;r0)7^()vhh%j(DDc4+2;a*OV}fDts%TXExY3$Y)sx^Y^B!# z;;tH@!t$X#jGe<GnE$&dZsBbmk9=Im#E~qF)wuH&#y*jk^&LB2=27lc*3syo9Y1Xi z$|8{uoGRv#a|A-CFzzmy$FN?O7eu0V;@0B@AxT3+w_QS_vC0Jk=c>U9zkd<y>gq@a z$XQW*8na~gM~q<-j*2ds?I)s*A<Cg1zi0}#opGb;(^>Bhy)g1d=sQO-o&;J<hD;BZ z3FsQPDw0O0ldjOME2;Q9k!=7uBgJ-Prqr&m?Nj|8lU7vJ^|kKYSuHJI{F;!GVs$h( zrBUkXIsaa5?MBufyg@-GRmc#nQa{wk0rVh>1tIfHAaIdR-pO=UKokqUV65}IQQwic zhA4#dk_ABEtLo~Su*cMTfeQp`<s`#^Ru{W<7}mOy3xvL6vS9S_5-t$Q8n&uG1wv(J ziV2ko+Zj_8#B7<~T2Qdc3+HqMLI$vGSs4?>54`|k>k|Nw*1BN9S^#MB0s!fZ3xYBo zEwg4l#;U84i}0>kf*=6r7+WIj-P`J&`BBZ5AX;*A=A||^{^&J;xFFW|_xE7^H{?dW z0%XYqmfHS`+S;e2-rm2wGHyF#V(bnA;nj+onqp~i@E<RMSTcbvIc-1(WFVE5t8wV* z_X>#JshoiH%Rv18RRAI%R@^SOlNC<n1f&mwSJwOe#cl(zovc)YDiHkt_k@8U!vq9x zQ=tJbdO_1^>zJNeSCmgm5Uw~T<vl=x7Czu*f&v`Lm*(aMDA5kYTp%!M^@c>zc*lbm z5<*QCO-*kj^rYQLu|1XRZ6H*Xig}xY>;-t`p>G^FLJ=e;yYTm)rwhvml=#G0ki<!Y zD9!{RG;7jgMi&=sZ&XF;r5xoTUWR))rb@;ut*A2Lgn<8pjJZqbV`%?X7Dl6QW5v`h zt%!)e1m=V!<@ybcPYOCxfQa7dMYegzmJ^dCqlR;7I;0nkMS72@!Ala-#gh3Z2{RPY ziFCaTCDp+|W+*M~L23W~PZ9P*T6+8ot}6W^)YNQV6Au4|j=7@`psHw+_6RE?hy)y- za!zmC_7YpM;*IfdzKO*P7hW$pa^w(}oqhyyX*Zy*;(Wnep2i>#Lc<_eTE6^Mo_b!& zDp+y}+mlgg8b^VmM8IK4q(XTXZw??4TTbf`7I<kETp=|-DkVgdm?t?RDE4wPblP?^ zIl6%mGbK3QmL+3(GSnp^yjsF0L;E$1Qy!sEF-3%zL{Rn^$E#GHqv1r4BUhJCC4HU- zH}joby0lc<y7l9UZlF{9t`MORK-=l8(2^xjNZYr6&N4FYolLWEQbf>!N;yyXLInkn zO1pM_2?AzLCIVECNr(vLL`fdt3o*7(s;@r=0`ey#0SOZU5>n_y`88t?_yU3BEIWJt zWF|l*f_wR-n=i=A-sd}T;3$sOj-QcmI%!Rm95)dNyGqY8c6~#2_4<>dxDoluu89Kg zrd@6#;1OZG`Y^e49>fE7$dQ)*B#N66{J7~Ru<_Q#O$6m<>B2JQXhBFfrAHBPID?0p z)^>~HHYAR3jdvs5u$R!PL57JCyoC|pr`Y}=c9OTY;PDVvS^2|PjIB+nuiuYb{<aV7 zxzPwWbR<lKYFx?AhBpa<N=Ppr$-Noz`&*lHa>|5*2j6r9sMk8UhlUj(B80BhQxYFT z<~roA;nqpB*9~w>f6^Wi4Mg#f;W9y>3+<HFniRz~kFaR1Ct*M@nDv?nFED$ebBYLs z)+r*!iHN&#jxfdKrAJUoEsUp-+B_N5i6|Y#A}50{n<RJ=QE6KT>+&#I?yuUt`vunB z&Ha-Xwgz|wfPt_86P=TW@uanOafnoTmITI5lb+_K-?X9M9-2OnvDpo!rO&aVqGC2@ zP9bkQ5|Z!e_?ewQAKH29)Mp)|n>@PiLmR%KW1|YSByiG1!~>{28%M)017{I>J`SgN zmWeS#8#%fo;boym;_0puU78x88^w61d$SScoaFBPh<Ghg43Z1p1}OxjSMkE}$cw6% sMdX{RYU8c&svdDS&hB1%OSEp`Uk$Uf)+qMfEC2ui07*qoM6N<$f{X1{FaQ7m literal 0 HcmV?d00001 diff --git a/briar-android/res/drawable-xhdpi/msg_out.9.png b/briar-android/res/drawable-xhdpi/msg_out.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c2816f1339f91a2abe9fa4bd1b72b05a5b5f47 GIT binary patch literal 2258 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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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 literal 0 HcmV?d00001 diff --git a/briar-android/res/layout/activity_conversation.xml b/briar-android/res/layout/activity_conversation.xml index 8bc064d2ad..673dd14f9f 100644 --- a/briar-android/res/layout/activity_conversation.xml +++ b/briar-android/res/layout/activity_conversation.xml @@ -1,12 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/layout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> - <!-- ListView will get inserted here --> + <android.support.v7.widget.RecyclerView + android:id="@+id/conversationView" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:scrollbars="vertical"/> <ProgressBar android:id="@+id/listLoadingProgressBar" @@ -15,7 +21,8 @@ android:layout_gravity="center" android:gravity="center" android:layout_weight="1" - android:indeterminate="true"/> + android:indeterminate="true" + android:visibility="gone"/> <TextView android:id="@+id/emptyView" @@ -26,7 +33,8 @@ android:gravity="center" android:padding="@dimen/margin_large" android:textSize="@dimen/text_size_large" - android:text="@string/no_private_messages"/> + android:text="@string/no_private_messages" + android:visibility="gone"/> <View android:layout_width="match_parent" diff --git a/briar-android/res/layout/list_item_msg_in.xml b/briar-android/res/layout/list_item_msg_in.xml index 8194f5453a..d93d269e77 100644 --- a/briar-android/res/layout/list_item_msg_in.xml +++ b/briar-android/res/layout/list_item_msg_in.xml @@ -11,6 +11,7 @@ android:paddingBottom="@dimen/margin_small"> <RelativeLayout + android:id="@+id/msgLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|start" diff --git a/briar-android/res/layout/list_item_msg_out.xml b/briar-android/res/layout/list_item_msg_out.xml index baacbb39fc..a82a07f1e0 100644 --- a/briar-android/res/layout/list_item_msg_out.xml +++ b/briar-android/res/layout/list_item_msg_out.xml @@ -11,6 +11,7 @@ android:paddingBottom="@dimen/margin_small"> <RelativeLayout + android:id="@+id/msgLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right|end" diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 02bc2b83a1..d62f6bf638 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -2,30 +2,26 @@ package org.briarproject.android.contact; import android.content.DialogInterface; import android.content.Intent; -import android.content.res.Resources; import android.graphics.PorterDuff; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.EditText; import android.widget.ImageButton; -import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.BriarActivity; -import org.briarproject.android.util.LayoutUtils; import org.briarproject.api.android.AndroidNotificationManager; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; @@ -77,12 +73,11 @@ import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.android.contact.ReadPrivateMessageActivity.RESULT_PREV_NEXT; -import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1; import static org.briarproject.api.messaging.PrivateMessageHeader.Status.DELIVERED; import static org.briarproject.api.messaging.PrivateMessageHeader.Status.SENT; public class ConversationActivity extends BriarActivity -implements EventListener, OnClickListener, OnItemClickListener { + implements EventListener, OnClickListener { private static final int REQUEST_READ = 2; private static final Logger LOG = @@ -95,7 +90,7 @@ implements EventListener, OnClickListener, OnItemClickListener { private TextView empty = null; private ProgressBar loading = null; private ConversationAdapter adapter = null; - private ListView list = null; + private RecyclerView list = null; private EditText content = null; private ImageButton sendButton = null; @@ -133,20 +128,26 @@ implements EventListener, OnClickListener, OnItemClickListener { loading.setVisibility(VISIBLE); adapter = new ConversationAdapter(this); - list = new ListView(this) { + list = (RecyclerView) findViewById(R.id.conversationView); + list.setLayoutManager(new LinearLayoutManager(this)); + list.setAdapter(adapter); + list.setVisibility(GONE); + // scroll down when opening keyboard + list.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override - public void onSizeChanged(int w, int h, int oldw, int oldh) { - // Scroll to the bottom when the keyboard is shown - super.onSizeChanged(w, h, oldw, oldh); - setSelection(getCount() - 1); + public void onLayoutChange(View v, + int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (bottom < oldBottom) { + list.postDelayed(new Runnable() { + @Override + public void run() { + list.scrollToPosition(adapter.getItemCount() - 1); + } + }, 100); + } } - }; - list.setLayoutParams(MATCH_WRAP_1); - list.setDivider(null); - list.setAdapter(adapter); - list.setOnItemClickListener(this); - list.setEmptyView(loading); - layout.addView(list, 0); + }); content = (EditText) findViewById(R.id.contentView); sendButton = (ImageButton) findViewById(R.id.sendButton); @@ -260,12 +261,10 @@ implements EventListener, OnClickListener, OnItemClickListener { runOnUiThread(new Runnable() { public void run() { loading.setVisibility(GONE); - empty.setVisibility(VISIBLE); - list.setEmptyView(empty); - displayContactDetails(); sendButton.setEnabled(true); - adapter.clear(); if (!headers.isEmpty()) { + list.setVisibility(VISIBLE); + empty.setVisibility(GONE); for (PrivateMessageHeader h : headers) { ConversationItem item = new ConversationItem(h); byte[] body = bodyCache.get(h.getId()); @@ -273,11 +272,12 @@ implements EventListener, OnClickListener, OnItemClickListener { else item.setBody(body); adapter.add(item); } - adapter.sort(ConversationItemComparator.INSTANCE); // Scroll to the bottom - list.setSelection(adapter.getCount() - 1); + list.scrollToPosition(adapter.getItemCount() - 1); + } else { + empty.setVisibility(VISIBLE); + list.setVisibility(GONE); } - adapter.notifyDataSetChanged(); } }); } @@ -306,14 +306,18 @@ implements EventListener, OnClickListener, OnItemClickListener { runOnUiThread(new Runnable() { public void run() { bodyCache.put(m, body); - int count = adapter.getCount(); + int count = adapter.getItemCount(); + for (int i = 0; i < count; i++) { ConversationItem item = adapter.getItem(i); + if (item.getHeader().getId().equals(m)) { item.setBody(body); - adapter.notifyDataSetChanged(); + adapter.notifyItemChanged(i); + // Scroll to the bottom - list.setSelection(count - 1); + list.scrollToPosition(count - 1); + return; } } @@ -326,7 +330,7 @@ implements EventListener, OnClickListener, OnItemClickListener { super.onActivityResult(request, result, data); if (request == REQUEST_READ && result == RESULT_PREV_NEXT) { int position = data.getIntExtra("briar.POSITION", -1); - if (position >= 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<MessageId> unread = new ArrayList<MessageId>(); - 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<MessageId> messages = new HashSet<MessageId>(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 0e2499c7ec..4b945f7d37 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<ConversationItem> { +class ConversationAdapter extends + RecyclerView.Adapter<ConversationAdapter.MessageHolder> { + + private static final int MSG_OUT = 0; + private static final int MSG_IN = 1; + private static final int MSG_IN_UNREAD = 2; + + private SortedList<ConversationItem> messages = + new SortedList<ConversationItem>(ConversationItem.class, + new SortedList.Callback<ConversationItem>() { + @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; + } - ConversationAdapter(Context ctx) { - super(ctx, android.R.layout.simple_expandable_list_item_1, - new ArrayList<ConversationItem>()); + @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) { + public int getItemViewType(int position) { + // return different type for incoming and outgoing (local) messages + PrivateMessageHeader header = getItem(position).getHeader(); + if (header.isLocal()) { + return MSG_OUT; + } else if (header.isRead()) { + return MSG_IN; + } else { + 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); + } + + return new MessageHolder(v, type); + } + + @Override + public void onBindViewHolder(final MessageHolder ui, final int position) { ConversationItem item = getItem(position); PrivateMessageHeader header = item.getHeader(); - Context ctx = getContext(); - - LayoutInflater inflater = (LayoutInflater) ctx.getSystemService - (Context.LAYOUT_INFLATER_SERVICE); - View v; 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); + ui.status.setImageResource(R.drawable.message_delivered); } else if (item.getStatus() == SENT) { - status.setImageResource(R.drawable.message_sent); + ui.status.setImageResource(R.drawable.message_sent); } else { - status.setImageResource(R.drawable.message_stored); + ui.status.setImageResource(R.drawable.message_stored); } - } else { - v = inflater.inflate(R.layout.list_item_msg_in, null); + } else if (!header.isRead()) { + // show unread messages in different color to not miss them + ui.layout.setBackgroundResource(R.drawable.msg_in_unread); } - TextView body = (TextView) v.findViewById(R.id.msgBody); - 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)); + } + + @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); + } - return v; + 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 76eb1d28dd..0000000000 --- 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<ConversationItem> { - - 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; - } -} -- GitLab