From 9fd2c4753e1b99e67613f4025af2830cf654511a Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Sat, 18 Jan 2014 18:17:08 -0800 Subject: [PATCH] ui wip --- AndroidManifest.xml | 10 + .../textsecure/directory/Directory.java | 11 +- .../ic_action_add_group_holo_dark.png | Bin 0 -> 876 bytes .../ic_action_add_group_holo_light.png | Bin 0 -> 968 bytes .../ic_menu_accept_holo_dark.png | Bin 0 -> 401 bytes .../ic_menu_accept_holo_light.png | Bin 0 -> 420 bytes .../ic_menu_remove_holo_light.png | Bin 0 -> 448 bytes .../ic_action_add_group_holo_dark.png | Bin 0 -> 634 bytes .../ic_action_add_group_holo_light.png | Bin 0 -> 660 bytes .../ic_menu_accept_holo_dark.png | Bin 0 -> 246 bytes .../ic_menu_accept_holo_light.png | Bin 0 -> 325 bytes .../ic_menu_remove_holo_light.png | Bin 0 -> 282 bytes .../ic_action_add_group_holo_dark.png | Bin 0 -> 1122 bytes .../ic_action_add_group_holo_light.png | Bin 0 -> 1195 bytes .../ic_menu_accept_holo_dark.png | Bin 0 -> 475 bytes .../ic_menu_accept_holo_light.png | Bin 0 -> 478 bytes .../ic_menu_remove_holo_light.png | Bin 0 -> 513 bytes .../ic_action_add_group_holo_dark.png | Bin 0 -> 1643 bytes .../ic_action_add_group_holo_light.png | Bin 0 -> 1731 bytes res/drawable-xxhdpi/ic_action_add_person.png | Bin 0 -> 1171 bytes .../ic_menu_accept_holo_dark.png | Bin 0 -> 619 bytes .../ic_menu_accept_holo_light.png | Bin 0 -> 649 bytes .../ic_menu_remove_holo_light.png | Bin 0 -> 681 bytes res/layout/group_create_activity.xml | 45 +++ .../push_contact_selection_activity.xml | 16 + .../push_contact_selection_list_activity.xml | 20 ++ .../push_contact_selection_list_item.xml | 60 ++++ res/layout/selected_recipient_list_item.xml | 34 ++ res/menu/group_create.xml | 10 + res/menu/text_secure_normal.xml | 4 + res/values/attrs.xml | 1 + res/values/strings.xml | 3 +- res/values/themes.xml | 2 + .../securesms/ContactSelectionActivity.java | 1 - .../securesms/ConversationActivity.java | 1 - .../securesms/ConversationFragment.java | 1 + .../securesms/ConversationListActivity.java | 6 + .../securesms/GroupCreateActivity.java | 151 ++++++++ .../PushContactSelectionActivity.java | 94 +++++ .../PushContactSelectionListFragment.java | 336 ++++++++++++++++++ .../contacts/PushFilterCursorWrapper.java | 106 ++++++ .../securesms/recipients/Recipient.java | 34 ++ .../util/SelectedRecipientsAdapter.java | 72 ++++ src/org/thoughtcrime/securesms/util/Util.java | 30 ++ 44 files changed, 1042 insertions(+), 6 deletions(-) create mode 100644 res/drawable-hdpi/ic_action_add_group_holo_dark.png create mode 100644 res/drawable-hdpi/ic_action_add_group_holo_light.png create mode 100644 res/drawable-hdpi/ic_menu_accept_holo_dark.png create mode 100644 res/drawable-hdpi/ic_menu_accept_holo_light.png create mode 100644 res/drawable-hdpi/ic_menu_remove_holo_light.png create mode 100644 res/drawable-mdpi/ic_action_add_group_holo_dark.png create mode 100644 res/drawable-mdpi/ic_action_add_group_holo_light.png create mode 100644 res/drawable-mdpi/ic_menu_accept_holo_dark.png create mode 100644 res/drawable-mdpi/ic_menu_accept_holo_light.png create mode 100644 res/drawable-mdpi/ic_menu_remove_holo_light.png create mode 100644 res/drawable-xhdpi/ic_action_add_group_holo_dark.png create mode 100644 res/drawable-xhdpi/ic_action_add_group_holo_light.png create mode 100644 res/drawable-xhdpi/ic_menu_accept_holo_dark.png create mode 100644 res/drawable-xhdpi/ic_menu_accept_holo_light.png create mode 100644 res/drawable-xhdpi/ic_menu_remove_holo_light.png create mode 100644 res/drawable-xxhdpi/ic_action_add_group_holo_dark.png create mode 100644 res/drawable-xxhdpi/ic_action_add_group_holo_light.png create mode 100644 res/drawable-xxhdpi/ic_action_add_person.png create mode 100644 res/drawable-xxhdpi/ic_menu_accept_holo_dark.png create mode 100644 res/drawable-xxhdpi/ic_menu_accept_holo_light.png create mode 100644 res/drawable-xxhdpi/ic_menu_remove_holo_light.png create mode 100644 res/layout/group_create_activity.xml create mode 100644 res/layout/push_contact_selection_activity.xml create mode 100644 res/layout/push_contact_selection_list_activity.xml create mode 100644 res/layout/push_contact_selection_list_item.xml create mode 100644 res/layout/selected_recipient_list_item.xml create mode 100644 res/menu/group_create.xml create mode 100644 src/org/thoughtcrime/securesms/GroupCreateActivity.java create mode 100644 src/org/thoughtcrime/securesms/PushContactSelectionActivity.java create mode 100644 src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java create mode 100644 src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java create mode 100644 src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d57253f3fd..746a8b6dca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -101,6 +101,10 @@ android:windowSoftInputMode="stateUnchanged" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> + + + + + RK++puY0p`9?KzxS+a04!2U4Zw_N^ie;dJ}s*wg;HJMeofs*y9;}jr1jj zJU)DLdAtL-v3Rx@Je^JR+;Q9dah0-~`ZyM%tTpmm<%#?Pdu>~W2 zIRU;;V~=@9)IwboVS;zxvEH(&RKZ?hr}_qqe{wRz29dZQ(uf6sJzUGU_!I9=gwYZt z&j;*Vft+_PC0Icmov--nhL~uF)7grkFHzg%Z6{2Z6}}#5{u|bcYw9X?)pc9F)Rc(> zRz~tKF9b_-_AK!^5g^Y18~TM;L5Dd2m%=?{FqhT-#sHxBQk2KJ2p%69xY}TcpbHcb zl_Hto_KA|c`Pm!ZS&3(vAcPS21D#>c01Qfq+N?~AzSIEM(Pia=tl%%hCP)@{>m~qQ zYWo7#Kgd!TNyd=wmWD=UjZjl#8BwLRmU2K7 zf3YUtb3pk)AV!ADPcN)I%d4z;M7fd-1^}MFwD^7gSTHiM{``uK&aAEq9(n>#SMJa{ zx$4;PXtOH<2(1E8GoNcRzO@2KjCE3#7NKTN>Nx=5INBh=+VVy!5&PzG;DpWxM2~Jz-5ro2;10#d;Y`cv9qN!v` z6(5|l0^plwqHrEkHmWk#M`VqvWecEc=d5^Ou?hAokx>d|A0)2Q4rS7^n?O4t^q`beNE3Taz^r0zKdTkNiLy4 z2NM7%~Tdc~`a{o-69y%h-Twt*748O9nXlqNBpk8%}r81N^DlnXBKA*Acibp4wfrxyB7 zP>FIo;XW$RZj#Om)F&%f$+b`P>@T|ii2mPz1K%;(k$#3T1Enug1VP}wP$QoTz@mgmEGuZx_X)sFaw$_#AO-VO zqnrivGyszv<#CJ(@>*3XI5oh^x!Us^oruaYG*4MR)j8o{1wUF2z$;IsgG#%(B1d0cG`ktab=hS*JNoKhKn6F$Rx92?p|-nNs=!=04OzV#lUc zN~o`dAsy#sKD{_OoewO?TEyl>Ke=>j;JiHK7y44sv2>o*<;@+J)gFQPMj&ows?zl- zwWoA1EL%=bj&Lq;i+n6-Tdcl+#Dk8X_fv~a1O`Vwq2=OP*D|!x5dnxw0q8ql-ORaf z2e2_FsuEg^oj%Ey-5M<7*dnFUp4rp^KxA-So(Ix4xLc>LxvkV(k%@=x)<13yfCj+- q4xn3O-2f8Ov5KmD!o+ z=itOYrFxxJ=dOewZg!7u>_{WbW;r8SJ6b%6~Jp s=yLf~Y01Dpy*yA4GIE^w!C}C_#CG_t@}%d+z%XX;boFyt=akR{0HbJ|djJ3c literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_accept_holo_light.png b/res/drawable-hdpi/ic_menu_accept_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..d93146d9902e3d7631f4d215abcdf50445264481 GIT binary patch literal 420 zcmV;V0bBlwP)s*f*=Tk znQ#@$vMhzm0V*$w;;CylV=RA!ilNp?lHAL(e7CioGL~PcM|j=n+E1CwBRBYjuIvMV zJWB6cv literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_remove_holo_light.png b/res/drawable-hdpi/ic_menu_remove_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..58a56e457b0bc39aade617acddcf52b501d495e7 GIT binary patch literal 448 zcmV;x0YCnUP)P4JAQ8dR_a&lVlZKy!ug}vT`lWF+@e-?m3aEez zsDKLml|Y)NTd8w2h>dAu`+jFU8`;ZcCv}}9$+52M2MS&;xzvrsT}s>&T)_3t&OI(r z5)Js3d$p3dcZqvOPoYxkrSWVV%tNMA3w-3-DB&Yud)oH6?Rhr>K_k%tUsP3fqpSgx z1Pb0;03m^dA0!YWfrcL}Fhqg@A0*%-!GgDC2HuEX0Np(W9wyKu5wYNdr_hJfX~BYr z3Uo-&;C~{WhK|6Sbr7KwK#D-eBEBMWC#pctZ?r@ufE0ngO$wdGLIpnEVVM$!4bPS^ zW56>d%vkU&2{R@Ao#mPCGKjsl?eHI=Kd5iCN1?loIzqOP;>% qXmRpIkJnW|1yn!N!0CyUdCLgTF6@F`C7RLrw<|qKZzb@&zb?M( zhSTXZpvx0!h*uS9?)!e-DFAZJ4yUBCVabj_EIR>!s6Ry1CMifWmuv)<>!udYS%#$P z-5H=r*}#~zPs&jo@M+AP)McjEaK3It`h6$4nWWFi$oU=lcqI^}sRT1k znoDHERR-XLlu%cmpi#fJM3qqY{w@-_N?{vNPu%B7-T4^d_|+g3U+0F%lqBdA44@!8 znc&z$QuC4yiXPEQ|>)iL~{J%ttk&@Zv60_qT8YGjAyO@J%#O#$Y86CA%p1mKA^{QNHa zfsY>ld8N~_;Vo_2VQqrQ6fX*7MK!Gyr8C)MZj2>>1ju!=;UR;2E<;J1j|L8K3|t14>)yh!ZgfA{{Y&s5w#9Ala{$~(PMnC z17X66HhfJsT>;Q#V1jlp=Rwu$&p9=hWrRH06Zqp Ugb*)q$p8QV07*qoM6N<$f@*LWg#Z8m literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_action_add_group_holo_light.png b/res/drawable-mdpi/ic_action_add_group_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8f9004707ea422bafbd8818f35d2eb32cee32f GIT binary patch literal 660 zcmV;F0&D$=P)0KvFyG>}F@d5hQlAvtSU_vok}WmJG@4mdx-BqB98Y$oHC~X~*l-7p$0R-6N ziZynAM<7(%5wsF$R$wEm@Sb^Wjcs49$^kdW^43R~w7ymnW|g^VKxJ`10m7(^P<|m2 zimwkTk!kVKC%7DEWTz`~tdnpo-vDspTywqC$DDi)5~&BLe1;wwy-(>Cu5hk|9fbfK z1hBFUYoJai<10{~q8DhU0-4RU&lmNt0W*8YqFv8v&nyuD(08y&K^=ez!46APfMa7z z(nGBAo<3Of@y!%h{S80&Wk1+KkLNbKFUqANp5K9fRIOH@=>7xiM*ztT4M`sXh*>-! z-G!YB?sXNMEEbDV!~p1PrX#w{F!A|TV09N@^8^nPM6xR`~B(J&%o+&nG>^$Juf zL2pgG|Ed=4V54bB&f9CW;Y{P%{vq)X@Gi}9IY;+EC0uko=Q)>7N=f>!casUl18u#UbrePCjo|Y3)pD7AJV9J1P{eob~ u@ucm>J88-KW7!%23T3UVe~HPb00RL1MzjQKn9Cag0000}1B}lL?P7vu~`SAaLeVl@yw8!b`j2|Tx@*Jk8 zFWUOlAxlkQwPWi!zA5J&Ze6VcFTPZtRc}1?JvSDAtwC4+o{u&F|yKCK+mXwquU}R%>+m>-ZX1@3)pc@%HUHx3vIVCg!05Z{8}1B}pV4FnQGw$lhbsAU91yTDq8VqrRDCT2%z! zlCFjuAzofyPkw%WF8)G{$z$4Sr`Ehnx?CF~_C0uWbMx_+j~J44mv=IIY@G9oPvL6O zDys&W(~b?S?T#NE_ONCgNREo?(`GShyupy7a3LWQ$UDury5WbkK(SyE%hGAdx}1%w zSJe|bOLIHElq zgf&8S)K{4udBjnd73o*v$fC>tYr#ba)6*w4-=;L)n3QPIc+u_U3T8K127v^Ig&wuM U`^=cm1HH`P>FVdQ&MBb@0H(it4*&oF literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_remove_holo_light.png b/res/drawable-mdpi/ic_menu_remove_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..342a79de6b7d3f18a7dbf7b97b6c8944e37d575a GIT binary patch literal 282 zcmV+#0pTpO6{U?C(jk_j4kWfkngEf(U{J>J_0T!D@Ue32A%s&x%(XKg8;9rG zqm(yRc%t|ei4vhyd`cXI8rV55iHS`7N*q`VN`$3$NOGGEv=SrY%?4@$sJO^LrN5B4 z*g(Mqq2iE%7ZZevw~V3gChV>Ss+Ul83{@AA>MBMEr8TuN6u;7x^Z1JX*#jQA*_jD% g|9u8S*SG~30I0v97|?&!(*OVf07*qoM6N<$f?e`)$^ZZW literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_action_add_group_holo_dark.png b/res/drawable-xhdpi/ic_action_add_group_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c493aa5a46324be03003a85c99371076cdb92c38 GIT binary patch literal 1122 zcmV-o1fBbdP)-COR>^x*-*14sv;duK6u$RG?zd_CWo-UZeQ#|ti{=T4XPHTpp(~;JJRz~Yb7>0)r1po=YqAjKsY5qx0Yezujg9d;E zXPVwX<2Lj}AUq5b0GRul)}-ZC1+7~Gq8tnWBs}x))Zvw#gD`R^{FJt>WB!;6C0$y; z$l+bz9GJa~mawezxd0fLK|q$eaxrCfVER=$(tEK9Pr5W#2!2D`N~T%*9ZOaBcr@-f zSVv4?mL3sg6Ip5nPY-miXzc=Y*%lAgnKjeECvK)E$XK!PZq&fjBk>-$0)Q@HIeek? zA40lI|C9*FGlPoOx_pgl{9Q+*d4;jAiECZg&o{Of5C=UaQqIJnOpZ{I8Urv7`b(yz z^}+^#sISr^3plMc+7v<)p{4D!6&M4M8#40iM1toQ04S9sD}*w{0oH(gRaI9=g=M8) z$TZWHjt|eo^hfVKv36MzEK|r}K38BqM(dTOR!6NyG52VLA&uqs~XFQD1y8lJYOVVJe-S~WwG!3Lk- zO$4*d&<~Utf2}IlJiug4wZEwpW7iA-g+6Epie-5EltGDF&KD!y3ak*8`=!dylY zq+aOUM%V0NIm& zJRNmg474fEEhGil-@zz^N-C~b2vNR{ zr=2p3DSXd_O3d#HSi%bKV{;FSD;du)miPR_lQIh+h_&tO7d(?dd)Lr}Zqc$sz~jPC zTbAZb5ouFX6bcVL+m5SXYI{X+<0Gt@B#LKES^|G<6vWc#Z4&^troV%)b~?MS0I|00 zSii_X<^a3~W)oehZ-K!pxI&I7elQer4xfL6=Jt{^vYJCh(yvXSL(SBgD3AKNOapKa zo(W{xpak=~F03kaAiSwIN+G4r4P#AWGV$#kfR<^~Hm!X(t@b(r;0(fxbjMTWSpcvM zLar_0zgb14ZqO=+M;5}*&MZ7HaMqHaFVQWgy4Gnk6HJ1B(A?w;kP`rqqx&>AU!4?y zpno4tv_5{g1mG&@AGK1-*f97NJp^EK5&%$v-3A|wM!|(sI21-vQ~^5p44>9+s_0C{c}tVNa^ng9R*07*qoM6N<$f^5b3uK)l5 literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_action_add_group_holo_light.png b/res/drawable-xhdpi/ic_action_add_group_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..6bfacaab23be69e777c9c28de6b81467a5944899 GIT binary patch literal 1195 zcmV;c1XTNpP) z>NI}R$x?E;d+&Sy&+)v13M#0e4W`m6F4^1LYtf@gtAVGW1=fVu-QC^dMFBv9cj(ck zRWtlaKx;@q1Zxcd3C^1S1dS)55rHsRO8{W*EPOTPQBzvy1jI5+0Jan#f!V954a>@eDi)^QfPnWLt>u)3 z?^_uvs!QwGh9@UMkS;l($6c^c5ZX=)dsi+UQf zZc5)}Ej-h{bZ;$&rxW76oeBVGRBO_Vbe8_95Kg2=t;?A5RgUm?69h%I_I5=8)THv+ zG;Soafc?BSE~K2QMVVYfMQRN|)5|aISnh8n0ic#w>5&DT)*5LFp=~_xb8ZFJ0GRri z@UPj51P?;@nvhmGs!DKzIKW-lsjj+0DqM!@g^43zb|rzQxNpSmvL;v_L53g+0As28 z>(OD6Eh$%J^xXu%IS5(DRhm{7zW!035#Tc}&@N(l>O%ODNoibGXp|4Wk_0&k7Zu$3 z0p(9%YNQCHX}h?dWL2Wh--U2z5j^!!!?3knr|Jb1gAFmi+m_67A~cQh*J0?G0Zi6b z`{CZ=7(EBy`$fZ35N9E_%}^DlE&JjUmyE(i3GF{o1gSCYyRRnw%BL(N2) zsEGQ3N&|3@9SOLa0_+?5?Jx_gDmf6|RvSyHQEFPonhTqWZ{`53XdE5}Z(v*P#nILp z2`|!Gi_+SR(%SE5vw(y_$h9T>H>*G_dYm37YKIMkZ#=W`qV(-5KIe({5th+(JIPGY z_wqqan>RpC06>n;)7ZSTQUJXCy)-d;`QZ|PT`zx>$_*+O=HKvEHv+c}h}i>x3aszF zFe-Z+PNj8W6h)3M2+LV2@H8(*xWTHdk+N!*1(-?79&Wq~tdj-I41(~!loq*TynQhM zVC)uX;)MKnorTH2NjBaE3IGZK3IGZK3IGZ!sGx!h+EDr@zyL23xOqM1_lp1k002ov JPDHLkV1mNeDf9pU literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_menu_accept_holo_dark.png b/res/drawable-xhdpi/ic_menu_accept_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee32b64df5f43346d78bf38a30c6194ae37e6a6 GIT binary patch literal 475 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7;8OU978H@y_t1Tu*pEcwX>Z20@IqqOj{Uq9k@3ze9U9M z)lmL*9cu(5Z$ty@gS0m0_72Y*2ipFaZ*FFOJmGtrEg@N+t&rB zf~VDorivcQ(D+nh5!jo(T)d>t_y-MR|LI)6<2eD96V+>(Eu1DOz27U4CETR)GfdlY zzQB@`+vB))wD$5rQ^8CwRq`x!iF{!ymgfZk@ zZ-HgQq*gTHMd+ntbm7eIi(t9~8hRf>n93)gTG3=+N|Fw5M^hDlfVmeiMOLaG6Ex+p_`b3N0000000000=<_eY0BfW# U&Otv?od5s;07*qoM6N<$g1eK=4FCWD literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_menu_remove_holo_light.png b/res/drawable-xhdpi/ic_menu_remove_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..58e2e3b4d86d43293595bfbcdf7aa14d462d7dfd GIT binary patch literal 513 zcmV+c0{;DpP)2M=_pR@i* zPEjZcR^hV+AS76ZFCxHFf_3;}0xTpT!WR`#QUWS`aRDVHAj6vo$dP~!Zzf=k1ds5h z0`8IE6+U}6SYkz=AE7zA$Wy1p8|=~!7V&gK%#^meXz0%8wWZ964n9e$r4n+ z9g84Tf;flor_-F)OM-ZYj|(39WCS&}7NhWg&~OaFi+)mMKi zl|l#tfj}S-2m}JrI-)*I^5f%UOn(gEVSg^@bNsb{D9Vul_W~c$ACL4&>62)Dg^|IC z*ik}`NPxZInXoeuye-c3*-#>ONPxcJnQNKTXX5a+jEH6EE-DH@_)Oau;NN{C8J?@3_OUyC(p& zZ`JXdClL7W(gJ24vmFyGLUBcLi`qYeO;!jPgff>03qdkJ`UE9{VMR%R<=&?f?GFhV zUCUO^is-*hp$w8p)>^>Y-d*mpBSQ^d5WU*W#Rdq>{og>`kBDzvn2;PSOO&c=m;Fk= zduYW4f!6;p5I)a1IMM)xPe9W-Mw-GW@VOQ2W&9EV!e3hmP^`fuBuu0g^EJt+mfi>e z*Ri(L-aZh(T@QhnRalac`Lcj{1J`kuOg2=Er6j^Cy|ztjsrT`u5R5V)|AF{n_Q zR?^y7GeEgoVCV##8oX!fnt+7d2V7a`(8dkLJ=R=TICn`VTEL~-8MQsQihxGl1rWsh z);2N>iGaf4V$J~zJ$GmUMKBg1xv>8OCg3PoY!eG8YCFB2}T2wdZg4MkFC`D`}VXbs?UA6<}M}MGY`-+b)TRNSRPvic~3C3TT6xQI} zfL99kwk?ryO*tW#NML#H8!uofCdUN7CYOgW8%405d)EH6MEl=`!^mTp;}0GL2V%nsGBdNVan#+;I%ILlzCSzBpd3u4w3e1Q2jO4lR9 z1@VLRi9(2H2yWZ5$$>jiwWc^BK_Pr*vCkFX(do`Uz}lQ74pD;zCgk!MkD231Cm7N1 z&WXBw+eo_<)~eN>2o^DxTxl+*t*#ImXg-_FL3aYMhy*5m(u^ze9^t!*HGopgD(YAT z2>)1Nm4%5`!&rl+2lJ6es+Ez?DjyF{hqB2ELQf^(mYS_*gDxL4=K;S&5Nv)^QeBO_ z3YNmYKn|)q80T2w))HwfzM0(GSVyGL-W92@h%$eF5>nK<0L>=45hn^HzYu@r?<;p^ za3|pi0v?e1QgZ?092ybOpbNsJ_VO$V#I)x~w=j0)!hrCF$8CpE5)mnC8$Q9TU}NaR zO$1-+5`gF*#Q$NFHVHBTE7I(OT%EgmgKc|V7Q%gcs;LY{ZG*zj*w>*Go6kQw8?;E$ zzO^U8Ti;p~Qo^zrlM~A_uwW4{o|KL$+R}u8wFiyZ*k%W9!muyY1Yhc`AeQocmx1<) z`FRa@Gx99l+V?|x{6yTJO=MaY;P|%U9%?OxMoEMFc;}a^g*N($5F_?=X`!| zJLQ`ppo7cUO;Y7~7r$vGLg8Pyr02Fth*0=;J6KnUJqSPRsqlGFU$uFMICd=j;C>Z= z1;(W2N-e7z(C0tlnT28jOL7~?>RNAtvjEsZj>+v{)++)?csJpjB5A`4Vu#v4B)5lI zUo2okZWD>`YHjR5`(2l^gA2%^Tfif^ZKRMo zO76t;9XS&4dmm%m7X)SM3j)g8?a>8c3_u_ONB{x}Kmrg*01|*e0+0X%5)c@$7-t8$ pfQRSe2m}IwKp+r^KH;YT0|0;J&PG&^m_Yyl002ovPDHLkV1mN<_?iF! literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_action_add_group_holo_light.png b/res/drawable-xxhdpi/ic_action_add_group_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..0db7fdb1d63a7d7006e84024d70288e11bafab00 GIT binary patch literal 1731 zcmV;!20ZzRP)`HwR`YJ9-&D?aA% z+2iBmj0o^A@H+pI;Ak0q1;_v*CY+Et5#TO(5OxOz?~0U{_tzf~e;ICt}fUir; zc5Ki`WjG!Ju)6I8V)?aD=5p#F$RmB>=g1s(jWBECYPY*D_X9Q~rN#sRbfi z$qLst^;u;{mKr=@X1$pg1rVD1_kp+{8ABmVOb!MaN|je-kN9_|mAD|(`a28Zi;P3y zivWes*vp<%-B9@AZ?CYHDVG2g{)aj|SrEFgkWtG~no82z*w8-~ z!CBx0oOvLCxhkuUfEGIr__ENtiyMj!P4keEdc4*-S-_qp3pxS{oi4y2zHi4yf+Aq; zaWQGYLVxy1z&aQUP^#aM&+05Bi!DOH%H>Rwt{Xa0a@%2nF5omLt%b_-w+e;bQexX& zbVIRj`Nq=CLZWhq#67u8=u}fk&G`*x(u8~P?~FFH37#~W)^inc0)_Q9L&?PFi9>g@ zCt1z7j8??GBCIRZTo)=}JoCd)%kr#eP|@y`x*NBd6W+gX(^$iO15tcAABl{&v=cxx zAC#iyPBgGIlLNtDNC6k7bxE^MNivBmJ1Ra>urq?Q`DYu z*5uF~Txo9Og#?Z8!D4^N`Hsoy>?y9zOX3g}SfJRH|5P%^)Fc@3@AjC!eOpMoG}dZ# zMiedLT&ZQCU|aY;Um96xeoF~lCTU*_bM4eHuBbJ_$i^B_DY`j*tQ87>o@13O8?DBB z)rxM+M+T`@hyCBO6~SqmHCajMi6Y!gvo#<=zUNE?{0u=f@j6XSRsSMb;#@l!L3M)n zndi8*mU1sqOpaZwBhqN^72DnsO{G51xj}%2!1o|e6iF_!^=Ta}EuKl3V}Iw2ZQnOo z;eDos2q@p7rCwv_#U(dqY0x}8`pGeUKO$@RS;nNdCGOUgb zN|kY0n@%vFcP0f|qy)2@k_7Mk)S{FUHZ$BJq=swPJ`7q!uOy`dMH|`>a8rUtJh)~D zRl@RIs10tnSwXDi`91?JnBcvEyQ!Bf+^yHg^!OcfY$I5scm zP#|93A%e7xJF{+e>?HvXZS1PyrLF9WlahrGT7SuQ`&8sMe8$IDPz3Vg>FH;ftbGg%!jewZF~|58ZvSfH&+g z(JH%J8+*`x->vN60&?gUkg&r>5;_6x11F$f=ELSb3b7G8e5An=ul$VcvRjt$g1aHQ-?V5J8XxZk`A(_hdRdP8RHqbAc_G5azWe|Gy50zsm;fk!!@2M z2)hUFV0Xj_L;w*$AOeU00uew25QqRGfItKg0R#=G8RrJIf%i-zArJ@z0)apv>=8Z) ZFaUXXfwCr43a0=7002ovPDHLkV1m-~D6;?n literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_action_add_person.png b/res/drawable-xxhdpi/ic_action_add_person.png new file mode 100644 index 0000000000000000000000000000000000000000..f18aa61444395f573a4b77c0a9abafda7dd85cbb GIT binary patch literal 1171 zcmV;E1Z?|>P)4yaBs z32;F`PfE!J#-t#np)n{aO+kM}aaESmkbs1`G^CMS&;pZ!L~=n*3KGzQ&NLG#y4=P0 zwRijRp0u*1v(LBh?ac1%&R8h`00000003wmfhiBNwY4>rw*zV+wO`a0)UMCZ&o3ZA z^Mo_e3ALe+z6>*JQ*I>40s>Sgo=Ja2tzY9dEXD~5F@pf*T+Cj<(Owj65l1##M9>oX zO#DdG?XN_HGYD{>c%Dq(h@aPu35jrIn*b}C$v>dB(NbSFq9|IM&*xK{1n6nJ_d&z{ zz6cU=cYa%dkPvs&C$@x3vV@;rT7ax}MjiKSUtFM)*CF0?=rS4I2G-tswNp0un>}nD`=qss488ya2Mb972HZEMVQ>K87aG15F9A zX82-b-E{!~2mk>92;i>(zYOo^!gT?RJ`^|k(7xAD2Te{7F*~*xx82feN{I(I`_5$GDy)uKL5@*O}^<#Ta5`IBm8sD z<&m5h=mZ}@rq3fLzQqBP94lb#%dV-8(a(GS>3#p14Y{L@3LsYSDd+P4M6ISp5c1Qm zho9rnhCrz;V+5s zqw0i9-+xVSUuIlBBl3@J*^1DXoO>rXbg(2yDwXFiqkd0ipTIHshsq+fksVzmN0dvD zIKBKLEr#4N1xV_uU&vU1nw!l*$G@9Fi{qw%?g<~XRTfbGa=vFBED8@#Kamn zad$`6%c5YyC$?6_bS6NCMR0pwPz>Hun2N7=rfk)SI$;u{FRtlXMnJ`6U$(KC2LRee z_`v4-+Zu?aeSG7^)v2OyE9Zzn1M$l$;h892v(Ks!C5JBYlAz3*O8TJT5npzlzqp-b zEkB)nd7`D%Qe5HvtM6SA+mtBNdwQxf6CIRRw($d&LmOoC@(4Mr_zS89;Y zq`y<uP&Zr5ePt(Nlm5;is4)6@ ziMgxn$NPttsnpMpWj^1E1<0i90{M)wjV@@c+(BQg*$x3D0v#&1u+wz`Ul|M7R_-BG z9T#%<3XcB1GQ8rv21YhmK&;$Fzsv3Hq5>q!Z6wADAns@2xB#75c##5)YYsvb(2W4H zdpHACW&~mY1aJ$151pii#M=sAOGo_|G9Q+~(&NxoPX<~OTPRglf{)>XFb4o|K}wxg z_)hxVgAet~)*LVWe1I{SJ8_$LA_ia%fB*mlfB*mlfB*mlfB*mlfB--}tb&4Eq&M&u lpacK_00000a0LGZ7y#EUBa6FqPfY*-002ovPDHLkV1gy*4*mcD literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_menu_accept_holo_dark.png b/res/drawable-xxhdpi/ic_menu_accept_holo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..68c41deccb550fc809319aa8efb46f392c6281c7 GIT binary patch literal 619 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n0P&1978H@y_s{+_ppJ8!{n}P_Jl^g15&Add<^BctYsTm z4>5e_GDLs#>D%YM3-mt2b-{ zQReav(feL9Xr3^aRuKKE#=J!3eoVuaC$qUGEUGbP44Rs+eFZc*?GRW@Ar0{5NQ)mVA*nb!T+$r)Oc&qeP0Zskz@Y3FJFO#+uE1vLFh(0+Ks$ZMkfmrxfS zP1Te9QVMNzE_s!9pjCjHI8;qcJzn3vHZ_l!SQ3-SwA=Ftc~CDzuKzYpZh%^ z^;pvS#m=#u;d_4ln7V&*@>UiOiG?LUyV*Zrda4p1AWHC^m8a)MB(z5>b!WJ^Un6l^l sf0mUm#gi8ZJe2&25@A5j&d@Q3OilarG)?VsosHc6X{kFE8|r_utCc zb^hr(TDA9H+1%eud(S;t@AFLE!QrP&)2yfd=O*mmGSR;w?dN&j*=t???S5Na$gyg= zeb&VN1s`izI5g%nG(Htp*igg7GHtz`L&ncl%tsXBa~mR_tmYAzTvN)JG(^Jy*f2`2a7taYt1E~`Fiv++F%=bn3Gp8Vp#eT5CeKiL;`Oqf)& zn8EbZ;*JTk{%}p+uaf_5ao?*sCnY~sSFxVB_gA_4XZxMn35IKKo_VdU82aa~Rq{bZG@b+P#Mt@iqco6CE4p82`js^K+H4XA>%&#t@~3O;?3l3o zPaN|Nw+jmUzcVZo+;U?5hi}uL?37nb)!BODyVL^bme|veZ=U#G#9?G1!wVFWTj0zh z`l*@4pl8C;KM9OhZYRR(E-<9KortRYz@RI*X}Y{?!z>Q1C+@-zR1Qt;*~#`3B_4sC cpP$qZFdVVp7yd2n{x6WEr>mdKI;Vst0Qu`3kN^Mx literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_menu_remove_holo_light.png b/res/drawable-xxhdpi/ic_menu_remove_holo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..331c545b8cb07a97ee63cb4f1256d1dba5557a82 GIT binary patch literal 681 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n7ln*978H@y_sX^mF+0u=KlZOu|s+fxN=%TuH_vu-63%O z0bh(`mh@(>)(|r>v-Oq66AmapP&R+I|NHM}^O&8P#J-=iwAdNbrpXGl5g7zooZfe! zXy=~Q^508s)?R;|&#w7(p8l)j$+D}f&aa<&y-Vlf4wmJ z{m;$!KDWm<)IWJYp?`yVy?a)Rgy+7R7WpU3-7WlQo&Q_^@0!|=9dkD{|8ah%BK}}y zUwJzR`>Fc-=T0VbW}WtqPP|eM_FyzT{_JE!R zD)mzsyaca2=@)ZQY+Cdukl9DlG4$UH#$_D~bm}KDB)bJnw~uTvRI)fR-^3w}Mf~Sv z_7fcsRN|EmXe#eGIi0hhi=*$;Ql>v@O{@PLixhm)QT}Dxr&MO1o`9IAr*$3O_1veg z`?Qpqrz1f--tK_DQpAbv90e{c(m#7YGyP$o^P)53LpC?loVb0lEiVH0g|-+>zh76d zzw5&@{nHH0@veKsKFU^vF}CfCD89*-Dfr;V_F3ly55?}QZh7Ijuejxf>%Q!k7oPi) zTVD9?i*9)lxG%WnMd&{7mKTxxoLgSR?yG8<(OmPC!%+O^D#0YbPom04?mSuT+F^Mr zylcW%?L%qnpM8_#v%0PH_*W!j7~dy$w}_iMj&Eb;mhJQVTeVv6|AD73EMLFabmp+t zH{}!WBYrC$Dt@-J@Me4I8>JZ0$#0vg1>-jHzhy>_Y6gY|m493Y3}5QcuZR|#@EIiM M>FVdQ&MBb@07pA8tN;K2 literal 0 HcmV?d00001 diff --git a/res/layout/group_create_activity.xml b/res/layout/group_create_activity.xml new file mode 100644 index 0000000000..b979bc128a --- /dev/null +++ b/res/layout/group_create_activity.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/push_contact_selection_activity.xml b/res/layout/push_contact_selection_activity.xml new file mode 100644 index 0000000000..9836f8c47c --- /dev/null +++ b/res/layout/push_contact_selection_activity.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/push_contact_selection_list_activity.xml b/res/layout/push_contact_selection_list_activity.xml new file mode 100644 index 0000000000..f25dd83a65 --- /dev/null +++ b/res/layout/push_contact_selection_list_activity.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/res/layout/push_contact_selection_list_item.xml b/res/layout/push_contact_selection_list_item.xml new file mode 100644 index 0000000000..928d599f5e --- /dev/null +++ b/res/layout/push_contact_selection_list_item.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + diff --git a/res/layout/selected_recipient_list_item.xml b/res/layout/selected_recipient_list_item.xml new file mode 100644 index 0000000000..4ef65831f9 --- /dev/null +++ b/res/layout/selected_recipient_list_item.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/group_create.xml b/res/menu/group_create.xml new file mode 100644 index 0000000000..b9a28fedb0 --- /dev/null +++ b/res/menu/group_create.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/res/menu/text_secure_normal.xml b/res/menu/text_secure_normal.xml index 1150efb128..1cc156505e 100644 --- a/res/menu/text_secure_normal.xml +++ b/res/menu/text_secure_normal.xml @@ -5,6 +5,10 @@ android:icon="?attr/menu_new_conversation_icon" android:showAsAction="always" /> + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index d562942f94..e9b232c2ab 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -33,6 +33,7 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index c467194aec..abbef3a19e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -370,7 +370,8 @@ No contacts. - + Finding contacts... + Select for diff --git a/res/values/themes.xml b/res/values/themes.xml index a8874d72a6..3224caf01b 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -27,6 +27,7 @@ @drawable/ic_ime_dark @drawable/ic_action_new_holo_light + @drawable/ic_action_add_group_holo_light @drawable/ic_menu_search_holo_light @drawable/ic_menu_call_holo_light @drawable/ic_menu_unlock_holo_light @@ -75,6 +76,7 @@ @drawable/ic_ime_light @drawable/ic_action_new_holo_dark + @drawable/ic_action_add_group_holo_dark @drawable/ic_menu_search_holo_dark @drawable/ic_menu_call_holo_dark @drawable/ic_menu_unlock_holo_dark diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java index 834b06c1f3..e131252dca 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java @@ -19,7 +19,6 @@ package org.thoughtcrime.securesms; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index a1dd2adb27..8c28c56ccc 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -659,7 +659,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi } } - private void initializeReceivers() { securityUpdateReceiver = new BroadcastReceiver() { @Override diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index c4e608cb9b..3e0103b180 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -140,6 +140,7 @@ public class ConversationFragment extends SherlockListFragment long dateReceived = message.getDateReceived(); long dateSent = message.getDateSent(); + SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d, yyyy 'at' hh:mm:ss a zzz"); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.ConversationFragment_message_details); diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 4661b0bdb5..d0f0335378 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -139,6 +139,7 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment switch (item.getItemId()) { case R.id.menu_new_message: createConversation(-1, null, defaultType); return true; + case R.id.menu_new_group: createGroup(); return true; case R.id.menu_settings: handleDisplaySettings(); return true; case R.id.menu_clear_passphrase: handleClearPassphrase(); return true; case R.id.menu_mark_all_read: handleMarkAllRead(); return true; @@ -153,6 +154,11 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment createConversation(threadId, recipients, distributionType); } + private void createGroup() { + Intent intent = new Intent(this, GroupCreateActivity.class); + startActivity(intent); + } + private void createConversation(long threadId, Recipients recipients, int distributionType) { Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, recipients); diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java new file mode 100644 index 0000000000..61fafba25c --- /dev/null +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -0,0 +1,151 @@ +package org.thoughtcrime.securesms; + +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.util.Log; +import android.view.View; +import android.widget.ListView; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.util.ActionBarUtil; +import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.DynamicTheme; +import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + + +public class GroupCreateActivity extends PassphraseRequiredSherlockFragmentActivity { + + private final DynamicTheme dynamicTheme = new DynamicTheme(); + private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + + private static final int PICK_CONTACT = 1; + private static final int SELECT_PHOTO = 100; + private ListView lv; + + private Set selectedContacts; + + @Override + public void onCreate(Bundle state) { + dynamicTheme.onCreate(this); + dynamicLanguage.onCreate(this); + super.onCreate(state); + + setContentView(R.layout.group_create_activity); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + ActionBarUtil.initializeDefaultActionBar(this, getSupportActionBar(), "New Group"); + + selectedContacts = new HashSet(); + initializeResources(); + } + + @Override + public void onResume() { + super.onResume(); + dynamicTheme.onResume(this); + } + + private void initializeResources() { + lv = (ListView) findViewById(R.id.selected_contacts_list); + lv.setAdapter(new SelectedRecipientsAdapter(this, android.R.id.text1, new ArrayList())); + (findViewById(R.id.add_people_button)).setOnClickListener(new AddRecipientButtonListener()); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuInflater inflater = this.getSupportMenuInflater(); + menu.clear(); + + inflater.inflate(R.menu.group_create, menu); + super.onPrepareOptionsMenu(menu); + return true; + } + + private List selectedContactsAsIdArray() { + final List ids = new ArrayList(); + for (Recipient recipient : selectedContacts) { + ids.add(String.valueOf(recipient.getCanonicalAddress(this))); + } + return ids; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + switch (item.getItemId()) { + case android.R.id.home: + case R.id.menu_create_group: + finish(); // TODO not this + return true; + } + + return false; + } + + @Override + public void onActivityResult(int reqCode, int resultCode, Intent data) { + Log.w("ComposeMessageActivity", "onActivityResult called: " + resultCode + " , " + data); + super.onActivityResult(reqCode, resultCode, data); + + if (data == null || resultCode != Activity.RESULT_OK) + return; + + switch (reqCode) { + case PICK_CONTACT: + Recipients recipients = data.getParcelableExtra("recipients"); + for (Recipient recipient : recipients.getRecipientsList()) { + if (!selectedContacts.contains(recipient)) { + Log.w("poop", "contains that shit."); + selectedContacts.add(recipient); + } + } + SelectedRecipientsAdapter adapter = (SelectedRecipientsAdapter)lv.getAdapter(); + adapter.clear(); + Iterator selectedContactsIter = selectedContacts.iterator(); + while (selectedContactsIter.hasNext()) { + adapter.add(selectedContactsIter.next()); + } + break; + case SELECT_PHOTO: + if(resultCode == RESULT_OK){ + Uri selectedImage = data.getData(); + String[] filePathColumn = {MediaStore.Images.Media.DATA}; + + Cursor cursor = getContentResolver().query( + selectedImage, filePathColumn, null, null, null); + cursor.moveToFirst(); + + int columnIndex = cursor.getColumnIndex(filePathColumn[0]); + String filePath = cursor.getString(columnIndex); + cursor.close(); + + Bitmap selectedBitmap = BitmapFactory.decodeFile(filePath); + break; + } + } + } + + private class AddRecipientButtonListener implements View.OnClickListener { + @Override + public void onClick(View v) { + Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class); + startActivityForResult(intent, PICK_CONTACT); + } + } +} diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java b/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java new file mode 100644 index 0000000000..65f84f2075 --- /dev/null +++ b/src/org/thoughtcrime/securesms/PushContactSelectionActivity.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms; + +import android.content.Intent; +import android.os.Bundle; + +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.util.ActionBarUtil; +import org.thoughtcrime.securesms.util.DynamicTheme; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +/** + * Activity container for selecting a list of contacts. Provides a tab frame for + * contact, group, and "recent contact" activity tabs. Used by ComposeMessageActivity + * when selecting a list of contacts to address a message to. + * + * @author Moxie Marlinspike + * + */ +public class PushContactSelectionActivity extends PassphraseRequiredSherlockFragmentActivity { + + private final DynamicTheme dynamicTheme = new DynamicTheme(); + + private Recipients recipients; + + @Override + protected void onCreate(Bundle icicle) { + dynamicTheme.onCreate(this); + super.onCreate(icicle); + + final ActionBar actionBar = this.getSupportActionBar(); + ActionBarUtil.initializeDefaultActionBar(this, actionBar); + actionBar.setDisplayHomeAsUpEnabled(true); + + setContentView(R.layout.push_contact_selection_activity); + } + + @Override + public void onResume() { + super.onResume(); + dynamicTheme.onResume(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = this.getSupportMenuInflater(); + inflater.inflate(R.menu.contact_selection, menu); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_selection_finished: + case android.R.id.home: + handleSelectionFinished(); return true; + } + + return false; + } + + private void handleSelectionFinished() { + PushContactSelectionListFragment contactsFragment = (PushContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment); + recipients = contactsFragment.getSelectedContacts(); + + Intent resultIntent = getIntent(); + resultIntent.putExtra("recipients", this.recipients); + + setResult(RESULT_OK, resultIntent); + + finish(); + } + +} diff --git a/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java new file mode 100644 index 0000000000..f666125f45 --- /dev/null +++ b/src/org/thoughtcrime/securesms/PushContactSelectionListFragment.java @@ -0,0 +1,336 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms; + + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CursorAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockListFragment; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +import org.thoughtcrime.securesms.contacts.ContactAccessor; +import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; +import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData; +import org.thoughtcrime.securesms.contacts.PushFilterCursorWrapper; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.Recipients; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * Activity for selecting a list of contacts. Displayed inside + * a PushContactSelectionActivity tab frame, and ultimately called by + * ComposeMessageActivity for selecting a list of destination contacts. + * + * @author Moxie Marlinspike + * + */ + +public class PushContactSelectionListFragment extends SherlockListFragment + implements LoaderManager.LoaderCallbacks +{ + + private final HashMap selectedContacts = new HashMap(); + private static LayoutInflater li; + + @Override + public void onActivityCreated(Bundle icicle) { + super.onCreate(icicle); + li = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + initializeResources(); + initializeCursor(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.push_contact_selection_list_activity, container, false); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.contact_selection_list, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + switch (item.getItemId()) { + case R.id.menu_select_all: handleSelectAll(); return true; + case R.id.menu_unselect_all: handleUnselectAll(); return true; + } + + super.onOptionsItemSelected(item); + return false; + } + + public Recipients getSelectedContacts() { + List recipientList = new LinkedList(); + + for (ContactData contactData : selectedContacts.values()) { + for (NumberData numberData : contactData.numbers) { + recipientList.add(new Recipient(contactData.name, numberData.number, null, null)); + } + } + + return new Recipients(recipientList); + } + + + private void handleUnselectAll() { + selectedContacts.clear(); + ((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged(); + } + + private void handleSelectAll() { + selectedContacts.clear(); + + Cursor cursor = null; + + try { + cursor = ContactAccessor.getInstance().getCursorForContactsWithNumbers(getActivity()); + + while (cursor != null && cursor.moveToNext()) { + ContactData contactData = ContactAccessor.getInstance().getContactData(getActivity(), cursor); + + if (contactData.numbers.isEmpty()) continue; + else if (contactData.numbers.size() == 1) addSingleNumberContact(contactData); + else addMultipleNumberContact(contactData, null, null); + } + } finally { + if (cursor != null) + cursor.close(); + } + + ((CursorAdapter)getListView().getAdapter()).notifyDataSetChanged(); + } + + private void addSingleNumberContact(ContactData contactData) { + selectedContacts.put(contactData.id, contactData); + } + + private void removeContact(ContactData contactData) { + selectedContacts.remove(contactData.id); + } + + private void addMultipleNumberContact(ContactData contactData, TextView textView, CheckBox checkBox) { + String[] options = new String[contactData.numbers.size()]; + int i = 0; + + for (NumberData option : contactData.numbers) { + options[i++] = option.type + " " + option.number; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.ContactSelectionlistFragment_select_for + " " + contactData.name); + builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData)); + builder.setPositiveButton(android.R.string.ok, new DiscriminatorFinishedListener(contactData, textView, checkBox)); + builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView, checkBox)); + builder.show(); + } + + private void initializeCursor() { + setListAdapter(new ContactSelectionListAdapter(getActivity(), null)); + this.getLoaderManager().initLoader(0, null, this); + } + + private void initializeResources() { + this.getListView().setFocusable(true); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + ((ContactItemView)v).selected(); + } + + private class ContactSelectionListAdapter extends CursorAdapter { + + public ContactSelectionListAdapter(Context context, Cursor c) { + super(context, c); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + ContactItemView view = new ContactItemView(context); + bindView(view, context, cursor); + + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + PushFilterCursorWrapper wrappedCursor = (PushFilterCursorWrapper) cursor; + boolean isPushUser = wrappedCursor.getPushCount() > wrappedCursor.getPosition(); + ContactData contactData = ContactAccessor.getInstance().getContactData(context, cursor); + ((ContactItemView)view).set(contactData, isPushUser); + } + } + + private class ContactItemView extends RelativeLayout { + private ContactData contactData; + private CheckBox checkBox; + private TextView name; + private TextView number; + private TextView label; + + public ContactItemView(Context context) { + super(context); + + li.inflate(R.layout.push_contact_selection_list_item, this, true); + + this.name = (TextView) findViewById(R.id.name); + this.number = (TextView) findViewById(R.id.number); + this.label = (TextView) findViewById(R.id.label); + this.checkBox = (CheckBox) findViewById(R.id.check_box); + } + + public void selected() { + + checkBox.toggle(); + + if (checkBox.isChecked()) { + if (contactData.numbers.size() == 1) addSingleNumberContact(contactData); + else addMultipleNumberContact(contactData, name, checkBox); + } else { + removeContact(contactData); + } + } + + public void set(ContactData contactData, boolean isPushUser) { + this.contactData = contactData; + + if (!isPushUser) { + this.name.setTextColor(0xa0000000); + this.number.setTextColor(0xa0000000); + this.checkBox.setVisibility(View.GONE); + } else { + this.name.setTextColor(0xff000000); + this.number.setTextColor(0xff000000); + this.checkBox.setVisibility(View.VISIBLE); + } + + if (selectedContacts.containsKey(contactData.id)) + this.checkBox.setChecked(true); + else + this.checkBox.setChecked(false); + + this.name.setText(contactData.name); + + if (contactData.numbers.isEmpty()) { + this.name.setEnabled(false); + this.number.setText(""); + this.label.setText(""); + } else { + this.number.setText(contactData.numbers.get(0).number); + this.label.setText(contactData.numbers.get(0).type); + } + } + } + + private class DiscriminatorFinishedListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { + private final ContactData contactData; + private final TextView textView; + private final CheckBox checkBox; + + public DiscriminatorFinishedListener(ContactData contactData, TextView textView, CheckBox checkBox) { + this.contactData = contactData; + this.textView = textView; + this.checkBox = checkBox; + } + + public void onClick(DialogInterface dialog, int which) { + ContactData selected = selectedContacts.get(contactData.id); + + if (selected == null && textView != null) { + if (textView != null) checkBox.setChecked(false); + } else if (selected.numbers.size() == 0) { + selectedContacts.remove(selected.id); + if (textView != null) checkBox.setChecked(false); + } + + if (textView == null) + ((CursorAdapter) getListView().getAdapter()).notifyDataSetChanged(); + } + + public void onCancel(DialogInterface dialog) { + onClick(dialog, 0); + } + } + + private class DiscriminatorClickedListener implements DialogInterface.OnMultiChoiceClickListener { + private final ContactData contactData; + + public DiscriminatorClickedListener(ContactData contactData) { + this.contactData = contactData; + } + + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + Log.w("ContactSelectionListActivity", "Got checked: " + isChecked); + + ContactData existing = selectedContacts.get(contactData.id); + + if (existing == null) { + Log.w("ContactSelectionListActivity", "No existing contact data, creating..."); + + if (!isChecked) + throw new AssertionError("We shouldn't be unchecking data that doesn't exist."); + + existing = new ContactData(contactData.id, contactData.name); + selectedContacts.put(existing.id, existing); + } + + NumberData selectedData = contactData.numbers.get(which); + + if (!isChecked) existing.numbers.remove(selectedData); + else existing.numbers.add(selectedData); + } + } + + @Override + public Loader onCreateLoader(int arg0, Bundle arg1) { + return ContactAccessor.getInstance().getCursorLoaderForContactsWithNumbers(getActivity()); + } + + @Override + public void onLoadFinished(Loader arg0, Cursor cursor) { + ((CursorAdapter) getListAdapter()).changeCursor(new PushFilterCursorWrapper(cursor, getActivity())); + } + + @Override + public void onLoaderReset(Loader arg0) { + ((CursorAdapter) getListAdapter()).changeCursor(null); + } +} diff --git a/src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java b/src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java new file mode 100644 index 0000000000..6725eb4da2 --- /dev/null +++ b/src/org/thoughtcrime/securesms/contacts/PushFilterCursorWrapper.java @@ -0,0 +1,106 @@ +package org.thoughtcrime.securesms.contacts; + +import android.content.Context; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.os.Debug; +import android.provider.ContactsContract; +import android.util.Log; + +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.util.InvalidNumberException; + +import java.util.List; + +public class PushFilterCursorWrapper extends CursorWrapper { + private int[] pushIndex; + private int[] normalIndex; + private int count = 0; + private int pushCount = 0; + private int pos = 0; + + private final ContactAccessor contactAccessor = ContactAccessor.getInstance(); + + /* + * Don't know of a better way to do this without a large filtering through the entire dataset at first + */ + public PushFilterCursorWrapper(Cursor cursor, Context context) { + super(cursor); + this.count = super.getCount(); + this.pushIndex = new int[this.count]; + this.normalIndex = new int[this.count]; + int pushPos = 0; + int normalPos = 0; + for (int i = 0; i < this.count; i++) { + super.moveToPosition(i); + + + List numbers = contactAccessor.getContactData(context, cursor).numbers; + if (numbers.size() > 0) { + try { + if (Util.isPushTransport(context, Util.canonicalizeNumber(context, numbers.get(0).number))) + this.pushIndex[pushPos++] = i; + else + this.normalIndex[normalPos++] = i; + } catch (InvalidNumberException ine) { + } + } + } + this.pushCount = pushPos; + super.moveToFirst(); + } + + @Override + public boolean move(int offset) { + return this.moveToPosition(this.pos + offset); + } + + @Override + public boolean moveToNext() { + return this.moveToPosition(this.pos + 1); + } + + @Override + public boolean moveToPrevious() { + return this.moveToPosition(this.pos - 1); + } + + @Override + public boolean moveToFirst() { + return this.moveToPosition(0); + } + + @Override + public boolean moveToLast() { + return this.moveToPosition(this.count - 1); + } + + private int getPostFilteredPosition(int preFilteredPosition) { + return preFilteredPosition < this.pushCount + ? this.pushIndex[preFilteredPosition] + : this.normalIndex[preFilteredPosition - pushCount]; + } + + @Override + public boolean moveToPosition(int position) { + if (position >= this.count || position < 0) + return false; + pos = position; + return super.moveToPosition(getPostFilteredPosition(position)); + } + + @Override + public int getCount() { + return this.count; + } + + public int getPushCount() { + return this.pushCount; + } + + @Override + public int getPosition() { + return this.pos; + } + +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 9715f429f0..f420f947cf 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -24,15 +24,25 @@ import android.os.Parcelable; import android.util.Log; import org.thoughtcrime.securesms.database.CanonicalAddressDatabase; +import org.thoughtcrime.securesms.push.PushServiceSocketFactory; import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.directory.NotInDirectoryException; +import org.whispersystems.textsecure.push.ContactTokenDetails; +import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.util.FutureTaskListener; +import org.whispersystems.textsecure.util.InvalidNumberException; import org.whispersystems.textsecure.util.ListenableFutureTask; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; +import java.io.IOException; import java.util.HashSet; public class Recipient implements Parcelable, CanonicalRecipientAddress { + private final static String TAG = "Recipient"; + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Recipient createFromParcel(Parcel in) { return new Recipient(in); @@ -152,7 +162,31 @@ public class Recipient implements Parcelable, CanonicalRecipientAddress { return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(getNumber()); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || ((Object) this).getClass() != o.getClass()) return false; // the Object casting is due to an Android Studio bug... + + Recipient recipient = (Recipient) o; + + if (contactUri != null ? !contactUri.equals(recipient.contactUri) : recipient.contactUri != null) + return false; + if (name != null ? !name.equals(recipient.name) : recipient.name != null) return false; + if (number != null ? !number.equals(recipient.number) : recipient.number != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = number != null ? number.hashCode() : 0; + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (contactUri != null ? contactUri.hashCode() : 0); + return result; + } + public static interface RecipientModifiedListener { public void onModified(Recipient recipient); } + } diff --git a/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java new file mode 100644 index 0000000000..5b64589808 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/SelectedRecipientsAdapter.java @@ -0,0 +1,72 @@ +package org.thoughtcrime.securesms.util; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.TextView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.recipients.Recipient; + +import java.util.ArrayList; +import java.util.List; + +public class SelectedRecipientsAdapter extends ArrayAdapter { + + private ArrayList recipients; + + public SelectedRecipientsAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + } + + public SelectedRecipientsAdapter(Context context, int resource, ArrayList recipients) { + super(context, resource, recipients); + this.recipients = recipients; + } + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + + View v = convertView; + + if (v == null) { + + LayoutInflater vi; + vi = LayoutInflater.from(getContext()); + v = vi.inflate(R.layout.selected_recipient_list_item, null); + + } + + Recipient p = getItem(position); + + if (p != null) { + + TextView name = (TextView) v.findViewById(R.id.name); + TextView phone = (TextView) v.findViewById(R.id.phone); + ImageButton delete = (ImageButton) v.findViewById(R.id.delete); + + if (name != null) { + name.setText(p.getName()); + } + if (phone != null) { + + phone.setText(p.getNumber()); + } + if (delete != null) { + delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + recipients.remove(position); + SelectedRecipientsAdapter.this.notifyDataSetChanged(); + } + }); + } + } + + return v; + + } +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index 03e8626b49..ae09e985fb 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -28,6 +28,11 @@ import android.os.Build; import android.provider.Telephony; import org.thoughtcrime.securesms.mms.MmsRadio; +import org.thoughtcrime.securesms.push.PushServiceSocketFactory; +import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.directory.NotInDirectoryException; +import org.whispersystems.textsecure.push.ContactTokenDetails; +import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.util.InvalidNumberException; import org.whispersystems.textsecure.util.PhoneNumberFormatter; @@ -151,6 +156,31 @@ public class Util { (context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context))); } + public static boolean isPushTransport(Context context, String destination) { + Directory directory = Directory.getInstance(context); + + try { + return directory.isActiveNumber(destination); + } catch (NotInDirectoryException e) { + try { + PushServiceSocket socket = PushServiceSocketFactory.create(context); + String contactToken = directory.getToken(destination); + ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken); + + if (registeredUser == null) { + registeredUser = new ContactTokenDetails(contactToken); + directory.setToken(registeredUser, false); + return false; + } else { + directory.setToken(registeredUser, true); + return true; + } + } catch (IOException e1) { + Log.w("UniversalTransport", e1); + return false; + } + } + } // public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) { // return BitmapFactory.decodeStream(src); //// BitmapFactory.Options options = new BitmapFactory.Options();