From 2ab0029d4929e020414ab56e5afdfddc3a695726 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 23 Nov 2015 15:07:41 -0800 Subject: [PATCH] Support for archive actions Fixes #1141 Closes #4698 // FREEBIE --- AndroidManifest.xml | 10 + res/drawable-hdpi/ic_archive_white_24dp.png | Bin 0 -> 247 bytes res/drawable-hdpi/ic_unarchive_white_24dp.png | Bin 0 -> 258 bytes res/drawable-hdpi/ic_unarchive_white_36dp.png | Bin 0 -> 354 bytes res/drawable-mdpi/ic_archive_white_24dp.png | Bin 0 -> 181 bytes res/drawable-mdpi/ic_unarchive_white_24dp.png | Bin 0 -> 181 bytes res/drawable-mdpi/ic_unarchive_white_36dp.png | Bin 0 -> 258 bytes ...conversation_list_item_read_background.xml | 11 + ...sation_list_item_read_background_dark.xml} | 1 + res/drawable-xhdpi/ic_archive_white_24dp.png | Bin 0 -> 267 bytes .../ic_unarchive_white_24dp.png | Bin 0 -> 273 bytes .../ic_unarchive_white_36dp.png | Bin 0 -> 391 bytes res/drawable-xxhdpi/ic_archive_white_24dp.png | Bin 0 -> 390 bytes .../ic_unarchive_white_24dp.png | Bin 0 -> 391 bytes .../ic_unarchive_white_36dp.png | Bin 0 -> 600 bytes .../ic_archive_white_24dp.png | Bin 0 -> 489 bytes .../ic_archive_white_36dp.png | Bin 0 -> 1102 bytes .../ic_archive_white_48dp.png | Bin 0 -> 1457 bytes .../ic_unarchive_white_24dp.png | Bin 0 -> 503 bytes .../ic_unarchive_white_36dp.png | Bin 0 -> 753 bytes ...conversation_list_item_read_background.xml | 6 + ...sation_list_item_read_background_dark.xml} | 1 + res/drawable/rounded_rectangle.xml | 7 + res/layout/contact_selection_list_item.xml | 3 +- res/layout/conversation_list_fragment.xml | 12 +- res/layout/conversation_list_item_action.xml | 17 ++ res/layout/conversation_list_item_view.xml | 16 +- res/menu/conversation.xml | 3 - res/menu/conversation_list_batch.xml | 5 +- res/menu/conversation_list_batch_archive.xml | 10 + .../conversation_list_batch_unarchive.xml | 10 + res/values/colors.xml | 2 +- res/values/dimens.xml | 1 + res/values/strings.xml | 12 + res/values/themes.xml | 5 +- .../BindableConversationListItem.java | 15 ++ .../securesms/ConversationActivity.java | 27 +-- .../securesms/ConversationFragment.java | 2 +- .../securesms/ConversationListActivity.java | 20 +- .../securesms/ConversationListAdapter.java | 88 +++++-- .../ConversationListArchiveActivity.java | 71 ++++++ .../securesms/ConversationListFragment.java | 217 ++++++++++++++++-- .../securesms/ConversationListItem.java | 19 +- .../securesms/ConversationListItemAction.java | 50 ++++ .../securesms/DeviceActivity.java | 2 +- .../securesms/DeviceListFragment.java | 2 +- .../securesms/DeviceProvisioningActivity.java | 2 +- .../securesms/GroupCreateActivity.java | 2 +- .../securesms/InviteActivity.java | 2 +- .../thoughtcrime/securesms/ShareFragment.java | 2 +- .../securesms/database/DatabaseFactory.java | 8 +- .../securesms/database/MmsDatabase.java | 10 +- .../database/PlaintextBackupImporter.java | 2 +- .../securesms/database/SmsDatabase.java | 14 +- .../securesms/database/SmsMigrator.java | 2 +- .../securesms/database/ThreadDatabase.java | 89 +++++-- .../loaders/ConversationListLoader.java | 48 +++- .../database/model/ThreadRecord.java | 8 +- .../AdvancedPreferenceFragment.java | 2 +- .../securesms/util/SaveAttachmentTask.java | 1 + .../{ => task}/ProgressDialogAsyncTask.java | 3 +- .../util/task/SnackbarAsyncTask.java | 94 ++++++++ 62 files changed, 796 insertions(+), 138 deletions(-) create mode 100644 res/drawable-hdpi/ic_archive_white_24dp.png create mode 100644 res/drawable-hdpi/ic_unarchive_white_24dp.png create mode 100644 res/drawable-hdpi/ic_unarchive_white_36dp.png create mode 100644 res/drawable-mdpi/ic_archive_white_24dp.png create mode 100644 res/drawable-mdpi/ic_unarchive_white_24dp.png create mode 100644 res/drawable-mdpi/ic_unarchive_white_36dp.png create mode 100644 res/drawable-v21/conversation_list_item_read_background.xml rename res/drawable-v21/{conversation_list_item_background.xml => conversation_list_item_read_background_dark.xml} (82%) create mode 100644 res/drawable-xhdpi/ic_archive_white_24dp.png create mode 100644 res/drawable-xhdpi/ic_unarchive_white_24dp.png create mode 100644 res/drawable-xhdpi/ic_unarchive_white_36dp.png create mode 100644 res/drawable-xxhdpi/ic_archive_white_24dp.png create mode 100644 res/drawable-xxhdpi/ic_unarchive_white_24dp.png create mode 100644 res/drawable-xxhdpi/ic_unarchive_white_36dp.png create mode 100644 res/drawable-xxxhdpi/ic_archive_white_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_archive_white_36dp.png create mode 100644 res/drawable-xxxhdpi/ic_archive_white_48dp.png create mode 100644 res/drawable-xxxhdpi/ic_unarchive_white_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_unarchive_white_36dp.png create mode 100644 res/drawable/conversation_list_item_read_background.xml rename res/drawable/{conversation_list_item_background.xml => conversation_list_item_read_background_dark.xml} (78%) create mode 100644 res/drawable/rounded_rectangle.xml create mode 100644 res/layout/conversation_list_item_action.xml create mode 100644 res/menu/conversation_list_batch_archive.xml create mode 100644 res/menu/conversation_list_batch_unarchive.xml create mode 100644 src/org/thoughtcrime/securesms/BindableConversationListItem.java create mode 100644 src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java create mode 100644 src/org/thoughtcrime/securesms/ConversationListItemAction.java rename src/org/thoughtcrime/securesms/util/{ => task}/ProgressDialogAsyncTask.java (96%) create mode 100644 src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 257b2d8752..32f780434a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -173,6 +173,16 @@ + + + + }7JFOZ05pW>&>pxFUDe!x8iknAeRrk`ZOGYLp-9f+ z=b#?4q#uAt&BS{v1SGA0HH%08ZD+7k9Ly?zmAt{ zLh;U7z-PJK_D6>S$?<>=wti#XZ62ubR&h_eId;dU-8RK;0GP3hIX>bIfa@mM2_^8( zA?Ey?5-2(T4ePQ1;Dzx9zXGtJ`!n`LWRGZ!J!1Qyqp>rhJJnbsx|Feu{WCUU5|1fr z=j%y0CMd%+=f@PKIX5OK!3AJ~5?lZ#D8V@}K?yDZ6O`ZrFhL2bd0p&(P6+jH(ZvX%Q07*qoM6N<$f~}yA AK>z>% literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_archive_white_24dp.png b/res/drawable-mdpi/ic_archive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f6aa3f966501c78876e3a9b6d7d4856382cabb4d GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+i8c!F;5R22v2@-oA?0@(F`2WJj z_W!0Args0U|FEBRdE(~(_y6g)Xl;3GuegCT^l*HW)V~E;3QRmJ*#0aM$*lMgN z(y*u^$Ki5QK;LaokEjV#AAe>J;a*kr=OEXWrgd`yY-}9D{R)rlRa)_nbrswH|N0-b g|G&4+_-w}@zgnSR$K>N-pgR~mUHx3vIVCg!0B(Cqv;Y7A literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_unarchive_white_24dp.png b/res/drawable-mdpi/ic_unarchive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec62cd34b2c63234ff5593637d5105bdb253343 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+i8c!F;5R22v2@-oA?Em)v`2WI2 z_y46Ar*{9@|9wC2@ECHuegCV^l*HW)V~E;3QPf!hnW>r91l1D=W_Vb z5W(y4G{MkBcZZ3?)a0y#>li&PcZualU6`^s>)_+eKZ~whaF{%!&NbjKcL*=z|M*AY f|L&Jxcvj00UZF6vf&ID(&>ak(u6{1-oD!M + + + + + + + + + diff --git a/res/drawable-v21/conversation_list_item_background.xml b/res/drawable-v21/conversation_list_item_read_background_dark.xml similarity index 82% rename from res/drawable-v21/conversation_list_item_background.xml rename to res/drawable-v21/conversation_list_item_read_background_dark.xml index 642879178e..19f004e729 100644 --- a/res/drawable-v21/conversation_list_item_background.xml +++ b/res/drawable-v21/conversation_list_item_read_background_dark.xml @@ -5,6 +5,7 @@ + diff --git a/res/drawable-xhdpi/ic_archive_white_24dp.png b/res/drawable-xhdpi/ic_archive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3513bd9fef620b059640829c1889f0df4fc2bc77 GIT binary patch literal 267 zcmV+m0rdWfP)kz8(! z_+-N#ZwJ2IIp%4dbLLm;=ghCv! z357Ud6AE#_CNw{sQGu2eH8$`=iYgnJup`<(g$-PYnkI0ZiUP5LBzrj^181M3?d61& Rg3+fBM5T$P+WqDqf*6fiTYQEJx&}*G=J0NWgzCp99W#r?rXGJn%$B z>pbv5M4LPi)c^)CmvK+P2;p9U(bwDqF#0w}14hXl2^jq{M*&8eIRY@6nOgy)9dip{ zR4~^9Mul?~kR&SJ4&V>og8^)aXvzZ*M6}8SC%n+wGm8XBh&2&JQp zm*xkIOkLACniS#=UAm>$D(CP);GQF^cC>4buxhJZg0$ntc##tO*11-DT24+#+IO9Q zzh?2&)W~TY&&!&|UA_h3|*nY@Hy z!WqLL29IYFnhYw%JtC||eNJ5rlJ2TI8%z59KCw$4?_AOhW%Vv;zGLZD#LD^KGR7mI zN$5#PlhBj}`ioYXUN8>qHNBu2*lTjZYQ2le(&dd@(R)}5*hEA_A|AJJnx0;fcBVtl imEBd(5#+G2och=Q?sSz#RHy+1n8DN4&t;ucLK6T>dYn`M literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_archive_white_24dp.png b/res/drawable-xxhdpi/ic_archive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..00e04e42bfd86f7a5291d885f43538de3bf4e870 GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXoKN!07Ag;uuoF`1YouS8<|1>%;Xw zJTDvzGVq-?!A*eW`-kmec?udgcJP)bEPlYpa?~wH$hCxZBiD^;&Wsw}W&F>N>9>n~ zo>y1#OEYryCY{q35Bl1oOz&*+kKA%-k*v}3viLvoVq1>)gnl`hAQ{lFc~NlbDXTjI zdpF+|+j4Nmi#b8Lvuy-yFJ-dtQrh)ge(9ra6>7UOn5_<9T*-ASwQq4go74-b@aa-7 zn8Sm4pS(`L>!70H$Qf10=@rl!x?{ou7S#-=`JdXq&b-3$MBj4OgER=d*Px(tK53Fb9}C>qPt{_mnDjt_DH}^q-?-K8-I3bf Say}mzz6_qOelF{r5}E*+Fr6s? literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_unarchive_white_24dp.png b/res/drawable-xxhdpi/ic_unarchive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..20d015751f5e7a7654fecd4d1beb6ccdb1033f8b GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXoKN!06}c;uuoF`1ZV^7jvON>&JQp zm*xkIOkLACniS#=UAm>$D(CP);GQF^cC>4buxhJZg0$ntc##tO*11-DT24+#+IO9Q zzh?2&)W~TY&&!&|UA_h3|*nY@Hy z!WqLL29IYFnhYw%JtC||eNJ5rlJ2TI8%z59KCw$4?_AOhW%Vv;zGLZD#LD^KGR7mI zN$5#PlhBj}`ioYXUN8>qHNBu2*lTjZYQ2le(&dd@(R)}5*hEA_A|AJJnx0;fcBVtl imEBd(5#+G2och=Q?sSz#RHy+1n8DN4&t;ucLK6T>dYn`M literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_unarchive_white_36dp.png b/res/drawable-xxhdpi/ic_unarchive_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..fefcb36e846bfd62024e43ab3730713f5e4c92ac GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz0wh)Q=esd5Fv)wmIEGX(zP)0YJvmV1*vHwb zx0KjiA3b9iaB)Al^3j8+werekLJJSy=6)1(B>%$EgN`el?Hya*@cH#kU6kG8wkYG@ zt`j$VY%8np8Qc6%-~XZcw5FHl(kVe+D^(2>x@EH-lvSK?J@qoo;_NS*bT{$2AA0^p z9=ulJKD{^6o;#x7yyPz9-fxM#lB;qL%WZg&&uyle7@gjM)i}ifB+1G?$>H8+N;ahlX?&Ph< zS0Bjx(7dMh`7Ze~k7s;)Hmv@mRBXTVQM0X5^=!Kv!JEDxVZW_fz1uEAwK~+`Ad`@f z!9gVu!7|(UJpbnn@BZJku|0E8OJuQXn`MOCY2M-uJ=3J0B}nR;pE+n0l{cd~b;}(C z_R|?V6ZxiHEIKTsv-l}UVRtL*h8{6)ZlFRju>(d?Iyw!hTOuMDPG@Y~z%cD%QX-?y z;-iO{a44*pv9T$Waf9L4`P!$qKWQywICJs;ff!D;83|3CY7q=;^y0N5YJkic34JWW zYZR+{`M3FgbF;f)yJ`Ew=CvQEp7FJO-Ygn7X{T+Mc-c(-l(#b)<->LyHa9YRY<860 z_*VL+(i|Q8H3gmGJ|{0tvHjf=H{-2Mx#YzF>zKnGyt`ZKBK|=w1KT(wc|W7X;tj!7 TT1nS{DTcw*)z4*}Q$iB}C@uul literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_archive_white_24dp.png b/res/drawable-xxxhdpi/ic_archive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..34cd3fd80592159be9a91ff48bcd8a3bc11bdf11 GIT binary patch literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z_`KF#WAFU@$GHHRlJrWY@m-)?S^Z21|@8$7GpIK4=& z=6^opboo2qRF+N&^3weJf5yi-%)IwZYaU7lU&{2Jvu*dEgfsg#$_Q;(kn~0?kDqnM zhUY?WXYw#txZY2m^g69($@`}Q4a>jmRoMAnZ2rsYBE1KKys__RPWW(N^#03Ll@)vm zCnv6p*YQw(%UA!xYLgD@nV()Qd}mHGyzjVlTj&7uqbo)B@*mnfaz8Wf@4QlK_dtJQ z)+fe`LJka!ECLXs;ZpmryEP3AOdJXg3~og;Dw+OpvehxvH#N#1n4j=~{e!y14@MLr zjskUw9ZYjL+0HS9H#J%xn3y2I5a46L!Jwfg!NMRS%)`Xc#mUCVaFnH)p&_Zsk-@?6 zpaRc=h6Dk&00)B|D;W%U9x?tA23eoc5i*y5hgwjmU4r+LYd;%Abpx|@PUrV{({ZUS zglWddPLA4yS==u;t)uiJPq)3S6qnJC`#gEp#?>n$zSr-U5l8{M8cgv0W>?vB-1F++ S6G^}*WbkzLb6Mw<&;$T~B*Q%b literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_archive_white_36dp.png b/res/drawable-xxxhdpi/ic_archive_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1e2af8235fb22a7d1c0b371a71890cfa5f7166 GIT binary patch literal 1102 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t)J zFfbqUba4!+xb^mKe1>14%<+%klYdUOcXjghNnWY@?wX!kqS&LhO)ZmL3!Rtz(YzTW z(6vKJJbL1q9R&+7=4@@#5nnP*Kzy_7?ulYqXC5D7J%9MwoacY)fav)1>hFK_=2gZ0 zJ@@n9zWbkl+p14?HH=DOQBh#z^l)Gjn$W2sBt)IThQ4Y zqdvyem(T6OGkV!Xc~{Iy(~{lqw(I7(&nXRI1>Gm*?zpi<`d9yP=&pRg%6~w8+6R-k z1(G()jx0{O;;qX3k+FB_+@{;RUa*;*;#~ppvWsRp^q(zpRJWh~ub)9Ubjg(>Tod6AkdRnx;eTo`^mi}Aka zQj@$!bHNrn=~Eoga77AY6sD8?%ClS;YTP>9q=fVwq%EF(7$V7p~)4q`ukTH$gy8@{3sxL>hTe$i*|tzZTi+Mc`M}l zuyEOx4elbn#}`JO;R_6rTizA=+2vxT%<5-ng=Rh|Tz5CI?*vbO^IrvHmt_@ma%Q-y z8y$a~;ch53(NiuxdR?pV%%_Dr)MF(kX38y}{q14rna>vgLX>4D^2$Bm{(x0|)8WSl z<&Gw}Y^&IF1#Izun-jw(0cai*_&f3*UV9F-!U9e3x&PHn%h9 zdyBC5m%9tr2_8NC_+e!4e3!zH6_qoV3MM_im?;lWiokHX$tK11Y5nqhM+F}HGJa+M zc|Yd;qJGO|4Ywj@IxX0}#*DROhOj1!)rrhthtCyfXH5CX6YRBKQz2uaUaG_O8$ucv zSy{9ewtH$ml3X&!ZnDFM*43I!w_LmrEDh=~eZY8bNsIcC?i+{p+GQJ0-&1M)?1cFB za|<@B+X$R_DpGncZA)U5#gW?qS3MY0!#HP0I9eJ@KKBhYbl{hb;h%J4G4tPWMMTC? kQD}u{9H9x1w*O$PIzL4~`gl1vFq<=Yy85}Sb4q9e05=ZRFaQ7m literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_archive_white_48dp.png b/res/drawable-xxxhdpi/ic_archive_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb64c404bed4143fad009ab141624a7ab0d13a2 GIT binary patch literal 1457 zcmZ`(cU04N5dQ*^Daz2&qR5J(V!=vSK^ikCgb-8^lo=G+mNGyZ7#WKKHqIDJL9ll@&D=0RYN) zJDd{~+dr8+4Dufb@1;RO$Jfq@2teFE01~ePAcjhb(*Q&r20-Wo05TT+l%K{6ZM$TKZ`m9$+mkOsWwbWDc3hqN_!(y@8Yp@s$ zEo2dZ4~SA@iPUH<-4v;V8Vg`F)bYZ-rV`J_A33~Ch!*mOITu~~X6ya@_+1Wo7IlP9 zEy}2MW?KzB%Cj;t4<9Qgv?ki`Y0n-`rY3r4&cYfz9edwX_5%Z9)`5+g`ks(7f zS`(c#^hXTP_78RH<3Fy0W=7IXp8RqKc>H;ROLgTNXAxJ1-kA4nK7W6@V>Sx0R`B(* zvJ0!EFJCLQk;Fz|mXu_x*ijEHAvR^-xU=>NLeeMmHI;_3lFc5OVcvkMsKgP*%A=jz z;A6$5Df&KE%K`p&S;XB^`eGs>(8}ZvqH2h1+rBg)Fefl{*H3eKA3OuBI;C8B5srJ5 zx7Smskq?iq<70{y+M4FJUXvZyjr=``kz#n($pg~z-%9?k#KSu`Z=@r4ZnDB*pFac; z_^#+uwX@L0bYd(j+P7^-0zYK!Vpc3Bl-c1eOTPHvadC-9khYi1y<^evO$w(zcsv6vhY;SlzRR!)$b|++qX6uxSojvp~>BI1Yv@yN5Syi|zIkg(TB!9aa z_1v#YBCl418jR|2gW>tKdCc6V8r+@SZ8)K!2KOKjXB=#_&kU0sINEGM8yAq(iIt+V z93q_A|71-u*yg`FKI4_lJ&!L0wswmgD&OOnIr#(C?#RqLUUCd(uQ4r73O-|IU9C|2qD`~^ zE}uNvH^ZnfEidadZox=pzIc5T`h%apz<+*}^jmG}L>!vp63zLv7rc!lt`6fv`3oqg Bf!Y87 literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png b/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a789520baa7f4b65fd73ae78a57ec50b9d645e3c GIT binary patch literal 503 zcmV9JZFUK03OixFK?s74p2SYCAYR}p1?>_^(85!gQmq9eh^_{-&k=SI_HVTg zh?@Ooznyspp8wN-!>=89()iz$2!_W(zjkExyoapP1JK?6aWn z7F@3dm~h4roiV8cct+cOVyBz&mbRxg01s(5V4Wa(uWz#a0m;&wUU1(`Z22Ye?}7v+GT zWU46#Bx{5K0}L?000Rs#zyP5!14IyLtN{IfOJf9xXotoI5b?(}CV+^)Ok)9v_{TH` zfQbK0*#RQ1m@)%IY%pa7h}dw-2oO;)Wdn#PoH7AKbeOULM0A`o07Pst4Gj>n7+Ap}a$4Sn!<>CMU002ovPDHLkV1mEb(=Px3 literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_unarchive_white_36dp.png b/res/drawable-xxxhdpi/ic_unarchive_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4dd3ddf162b288b3259117988cfe2c46bd1d2a GIT binary patch literal 753 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFzxVkaSW+oe0$f>dwQVA@sHd| z#e$|TnGt~^F1)>tB8^|zTBH`O)f6~Xz;RBS<5trG&CZT8?+gL1ThdA#+=fz0kJgL5 zy1(!2dC4f7^N)-5_P>A68)J7`)<5v>(kVe+no#1ARI$!R*Za4UI+yzdm2ErZS2pYW zm%Zh4Zs{&Ky=Kxv#_%%c7dop`GJA_-nPnev&-``jpkAX}q)K%D&C9YEl4s=>O}w=F z?Q`aR>amP>50=lqv|IL(@xGUO4eN}S+*J0`H$TXAw#{c7Z%Na9ljxKwmoCqJ$bPo2 zaFg$Xy)`|*WUX$ovYGrovTs|1+l=Km^UnoW?eqS#@MgB#EygMbUE3Vy7iy-Z+EOnh zUSGLlCA`r=f8|>7i4MnqdgtaE3$mBYmzv$nT;<%WdxWue`LTUR8F^Q3Tx+qwOL#&9 zi;4mxr-uWR5Q6D74aav?!)RV%^Jr24+Tg(=GtNf$G9$@2d6KE5_B0b@X#wem=`~(K_xG!nQjwg-{RD(dz?`>Ao+8l!{#eDd}J;JZd9-BV(4eyMs&kVOE&fieWc7A4Y>0j^R z!gn9LQq4|WVmp1t_;AFNUT0s2I;4P|g%sG|_)l!;ZRyh5><3KB44$rjF6*2UngDh= BJ(>Uj literal 0 HcmV?d00001 diff --git a/res/drawable/conversation_list_item_read_background.xml b/res/drawable/conversation_list_item_read_background.xml new file mode 100644 index 0000000000..45108a5b76 --- /dev/null +++ b/res/drawable/conversation_list_item_read_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/conversation_list_item_background.xml b/res/drawable/conversation_list_item_read_background_dark.xml similarity index 78% rename from res/drawable/conversation_list_item_background.xml rename to res/drawable/conversation_list_item_read_background_dark.xml index 8a28201a1a..c5153ed188 100644 --- a/res/drawable/conversation_list_item_background.xml +++ b/res/drawable/conversation_list_item_read_background_dark.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/res/drawable/rounded_rectangle.xml b/res/drawable/rounded_rectangle.xml new file mode 100644 index 0000000000..4af7432fd4 --- /dev/null +++ b/res/drawable/rounded_rectangle.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/contact_selection_list_item.xml b/res/layout/contact_selection_list_item.xml index 4f9d121420..2f842b03e5 100644 --- a/res/layout/contact_selection_list_item.xml +++ b/res/layout/contact_selection_list_item.xml @@ -8,7 +8,6 @@ android:layout_height="?android:attr/listPreferredItemHeight" android:orientation="horizontal" android:gravity="center_vertical" - android:background="@drawable/conversation_list_item_background" android:paddingLeft="48dp" android:paddingRight="20dp"> @@ -18,7 +17,7 @@ android:layout_height="40dp" android:foreground="@drawable/contact_photo_background" android:cropToPadding="true" - tools:src="@color/md_material_blue_600" + tools:src="@color/blue_600" android:layout_marginRight="10dp" android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" /> diff --git a/res/layout/conversation_list_fragment.xml b/res/layout/conversation_list_fragment.xml index 9dcc31fa54..961349a88b 100644 --- a/res/layout/conversation_list_fragment.xml +++ b/res/layout/conversation_list_fragment.xml @@ -1,6 +1,7 @@ - - + android:contentDescription="@string/conversation_list_fragment__fab_content_description"/> - + diff --git a/res/layout/conversation_list_item_action.xml b/res/layout/conversation_list_item_action.xml new file mode 100644 index 0000000000..77d2bc8375 --- /dev/null +++ b/res/layout/conversation_list_item_action.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml index 1965dfeda4..94624a9e04 100644 --- a/res/layout/conversation_list_item_view.xml +++ b/res/layout/conversation_list_item_view.xml @@ -4,7 +4,6 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:background="@drawable/conversation_list_item_background" android:layout_height="70dp"> + + + diff --git a/res/menu/conversation.xml b/res/menu/conversation.xml index d57a0beaf5..81587e84c1 100644 --- a/res/menu/conversation.xml +++ b/res/menu/conversation.xml @@ -7,9 +7,6 @@ - - diff --git a/res/menu/conversation_list_batch.xml b/res/menu/conversation_list_batch.xml index a61042f5d5..3df65d81de 100644 --- a/res/menu/conversation_list_batch.xml +++ b/res/menu/conversation_list_batch.xml @@ -5,10 +5,11 @@ + app:showAsAction="always" /> + android:icon="?menu_selectall_icon" + app:showAsAction="always"/> \ No newline at end of file diff --git a/res/menu/conversation_list_batch_archive.xml b/res/menu/conversation_list_batch_archive.xml new file mode 100644 index 0000000000..1c91c1b358 --- /dev/null +++ b/res/menu/conversation_list_batch_archive.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/res/menu/conversation_list_batch_unarchive.xml b/res/menu/conversation_list_batch_unarchive.xml new file mode 100644 index 0000000000..ecb7bb310b --- /dev/null +++ b/res/menu/conversation_list_batch_unarchive.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index bcf175759f..e2de5006b3 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -24,7 +24,7 @@ #7F111111 - #ffffffff + @color/gray5 #ffffffff #ff000000 #ff333333 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 5a99e42358..c1937aea22 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -37,6 +37,7 @@ 150dp 70dp + 16dp 135dip diff --git a/res/values/strings.xml b/res/values/strings.xml index 59f88cd492..4933f80e9a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -174,10 +174,18 @@ Deleting Deleting selected threads... + Archived conversations + UNDO + Moved conversation to inbox + Archived conversation + Moved conversations to inbox Key exchange message... + + Archived conversations (%d) + Using custom: %s Using default: %s @@ -867,6 +875,7 @@ Message details Manage linked devices Invite friends + Conversations archive Import / export @@ -1048,6 +1057,7 @@ Delete selected Select all + Archive selected Search @@ -1055,6 +1065,7 @@ Contact Photo Image Error alert + Archived New conversation @@ -1151,6 +1162,7 @@ Transport icon + diff --git a/res/values/themes.xml b/res/values/themes.xml index c8b7f4f380..4e746c6e04 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -95,7 +95,7 @@ @color/white @drawable/list_selected_holo_light @drawable/conversation_list_item_unread_background - @drawable/conversation_list_item_background + @drawable/conversation_list_item_read_background #66333333 #FF333333 #FF444444 @@ -205,12 +205,13 @@ @style/ThemeOverlay.AppCompat.Dark @color/text_color_dark_theme @color/text_color_secondary_dark_theme + @color/textsecure_primary_dark @color/signal_primary_dark @color/signal_primary_dark @color/black @drawable/list_selected_holo_dark @drawable/conversation_list_item_unread_background_dark - @drawable/conversation_list_item_background + @drawable/conversation_list_item_read_background_dark #66dddddd #ffdddddd #ffdddddd diff --git a/src/org/thoughtcrime/securesms/BindableConversationListItem.java b/src/org/thoughtcrime/securesms/BindableConversationListItem.java new file mode 100644 index 0000000000..1da1ac2864 --- /dev/null +++ b/src/org/thoughtcrime/securesms/BindableConversationListItem.java @@ -0,0 +1,15 @@ +package org.thoughtcrime.securesms; + +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.model.ThreadRecord; + +import java.util.Locale; +import java.util.Set; + +public interface BindableConversationListItem extends Unbindable { + + public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, + @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode); +} diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index d07eace41c..12bb463ad9 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -392,7 +392,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity switch (item.getItemId()) { case R.id.menu_call_secure: case R.id.menu_call_insecure: handleDial(getRecipients().getPrimaryRecipient()); return true; - case R.id.menu_delete_thread: handleDeleteThread(); return true; case R.id.menu_add_attachment: handleAddAttachment(); return true; case R.id.menu_view_media: handleViewMedia(); return true; case R.id.menu_add_to_contacts: handleAddToContacts(); return true; @@ -650,28 +649,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity new GroupMembersDialog(this, getRecipients()).display(); } - private void handleDeleteThread() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.ConversationActivity_delete_thread_question); - builder.setIconAttribute(R.attr.dialog_alert_icon); - builder.setCancelable(true); - builder.setMessage(R.string.ConversationActivity_this_will_permanently_delete_all_messages_in_this_conversation); - builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (threadId > 0) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this).deleteConversation(threadId); - } - composeText.getText().clear(); - threadId = -1; - finish(); - } - }); - - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - private void handleAddToContacts() { final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipients.getPrimaryRecipient().getNumber()); @@ -1089,9 +1066,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity draftDatabase.insertDrafts(new MasterCipher(thisMasterSecret), threadId, drafts); threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), drafts.getUriSnippet(ConversationActivity.this), - System.currentTimeMillis(), Types.BASE_DRAFT_TYPE); + System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); } else if (threadId > 0) { - threadDatabase.update(threadId); + threadDatabase.update(threadId, false); } return threadId; diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 56fcadc332..a2297fc270 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -57,7 +57,7 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.ViewUtil; diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 0428eb4ff1..cff90af531 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -161,15 +161,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit @Override public void onCreateConversation(long threadId, Recipients recipients, int distributionType) { - 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.getIds()); intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); @@ -179,6 +170,17 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); } + @Override + public void onSwitchToArchive() { + Intent intent = new Intent(this, ConversationListArchiveActivity.class); + startActivity(intent); + } + + private void createGroup() { + Intent intent = new Intent(this, GroupCreateActivity.class); + startActivity(intent); + } + private void handleDisplaySettings() { Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class); startActivity(preferencesIntent); diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index 3325b699db..e29737ce82 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -49,6 +49,9 @@ import java.util.Set; */ public class ConversationListAdapter extends CursorRecyclerViewAdapter { + private static final int MESSAGE_TYPE_SWITCH_ARCHIVE = 1; + private static final int MESSAGE_TYPE_THREAD = 2; + private final ThreadDatabase threadDatabase; private final MasterSecret masterSecret; private final MasterCipher masterCipher; @@ -61,37 +64,25 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter ViewHolder(final @NonNull V itemView) { super(itemView); - itemView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - if (clickListener != null) clickListener.onItemClick(itemView); - } - }); - itemView.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - if (clickListener != null) clickListener.onItemLongClick(itemView); - return true; - } - }); } - public ConversationListItem getItem() { - return (ConversationListItem)itemView; + public BindableConversationListItem getItem() { + return (BindableConversationListItem)itemView; } } @Override public long getItemId(@NonNull Cursor cursor) { - ThreadRecord record = getThreadRecord(cursor); - StringBuilder builder = new StringBuilder(""+record.getThreadId()); + ThreadRecord record = getThreadRecord(cursor); + StringBuilder builder = new StringBuilder("" + record.getThreadId()); + for (long recipientId : record.getRecipients().getIds()) { builder.append("::").append(recipientId); } + return Conversions.byteArrayToLong(digest.digest(builder.toString().getBytes())); } @@ -116,10 +107,51 @@ public class ConversationListAdapter extends CursorRecyclerViewAdapter, ActionMode.Callback, ItemClickListener { + + public static final String ARCHIVE = "archive"; + private MasterSecret masterSecret; private ActionMode actionMode; private RecyclerView list; @@ -76,27 +91,39 @@ public class ConversationListFragment extends Fragment private FloatingActionButton fab; private Locale locale; private String queryFilter = ""; + private boolean archive; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); masterSecret = getArguments().getParcelable("master_secret"); locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); + archive = getArguments().getBoolean(ARCHIVE, false); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { final View view = inflater.inflate(R.layout.conversation_list_fragment, container, false); - reminderView = (ReminderView) view.findViewById(R.id.reminder); - list = (RecyclerView) view.findViewById(R.id.list); - fab = (FloatingActionButton) view.findViewById(R.id.fab); + + reminderView = ViewUtil.findById(view, R.id.reminder); + list = ViewUtil.findById(view, R.id.list); + fab = ViewUtil.findById(view, R.id.fab); + + if (archive) fab.setVisibility(View.GONE); + else fab.setVisibility(View.VISIBLE); + reminderView.setOnDismissListener(new OnDismissListener() { - @Override public void onDismiss() { + @Override + public void onDismiss() { updateReminders(); } }); + list.setHasFixedSize(true); list.setLayoutManager(new LinearLayoutManager(getActivity())); + + new ItemTouchHelper(new ArchiveListenerCallback()).attachToRecyclerView(list); + return view; } @@ -170,6 +197,49 @@ public class ConversationListFragment extends Fragment getLoaderManager().restartLoader(0, null, this); } + private void handleArchiveAllSelected() { + final Set selectedConversations = new HashSet<>(getListAdapter().getBatchSelections()); + final boolean archive = this.archive; + + String snackBarTitle; + + if (archive) snackBarTitle = getString(R.string.ConversationListFragment_moved_conversations_to_inbox); + else snackBarTitle = getString(R.string.ConversationListFragment_archived_conversations); + + new SnackbarAsyncTask(getView(), snackBarTitle, + getString(R.string.ConversationListFragment_undo), + getResources().getColor(R.color.amber_500), + Snackbar.LENGTH_LONG, true) + { + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + + @Override + protected void executeAction(@Nullable Void parameter) { + for (long threadId : selectedConversations) { + if (!archive) DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId); + else DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId); + } + } + + @Override + protected void reverseAction(@Nullable Void parameter) { + for (long threadId : selectedConversations) { + if (!archive) DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId); + else DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId); + } + } + }.execute(); + } + private void handleDeleteAllSelected() { int conversationsCount = getListAdapter().getBatchSelections().size(); AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); @@ -225,7 +295,7 @@ public class ConversationListFragment extends Fragment private void handleSelectAllThreads() { getListAdapter().selectAllThreads(); actionMode.setSubtitle(getString(R.string.conversation_fragment_cab__batch_selection_amount, - getListAdapter().getBatchSelections().size())); + getListAdapter().getBatchSelections().size())); } private void handleCreateConversation(long threadId, Recipients recipients, int distributionType) { @@ -234,7 +304,7 @@ public class ConversationListFragment extends Fragment @Override public Loader onCreateLoader(int arg0, Bundle arg1) { - return new ConversationListLoader(getActivity(), queryFilter); + return new ConversationListLoader(getActivity(), queryFilter, archive); } @Override @@ -276,21 +346,30 @@ public class ConversationListFragment extends Fragment getListAdapter().notifyDataSetChanged(); } + @Override + public void onSwitchToArchive() { + ((ConversationSelectedListener)getActivity()).onSwitchToArchive(); + } + public interface ConversationSelectedListener { void onCreateConversation(long threadId, Recipients recipients, int distributionType); + void onSwitchToArchive(); } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = getActivity().getMenuInflater(); + + if (archive) inflater.inflate(R.menu.conversation_list_batch_unarchive, menu); + else inflater.inflate(R.menu.conversation_list_batch_archive, menu); + inflater.inflate(R.menu.conversation_list_batch, menu); mode.setTitle(R.string.conversation_fragment_cab__batch_selection_mode); mode.setSubtitle(getString(R.string.conversation_fragment_cab__batch_selection_amount, 1)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getActivity().getWindow() - .setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar)); + getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar)); } return true; @@ -304,8 +383,9 @@ public class ConversationListFragment extends Fragment @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { - case R.id.menu_select_all: handleSelectAllThreads(); return true; - case R.id.menu_delete_selected: handleDeleteAllSelected(); return true; + case R.id.menu_select_all: handleSelectAllThreads(); return true; + case R.id.menu_delete_selected: handleDeleteAllSelected(); return true; + case R.id.menu_archive_selected: handleArchiveAllSelected(); return true; } return false; @@ -316,8 +396,7 @@ public class ConversationListFragment extends Fragment getListAdapter().initializeBatchMode(false); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - TypedArray color = getActivity().getTheme() - .obtainStyledAttributes(new int[] { android.R.attr.statusBarColor }); + TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] {android.R.attr.statusBarColor}); getActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK)); color.recycle(); } @@ -325,6 +404,114 @@ public class ConversationListFragment extends Fragment actionMode = null; } + private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback { + + public ArchiveListenerCallback() { + super(0, ItemTouchHelper.RIGHT); + } + + @Override + public boolean onMove(RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) + { + return false; + } + + @Override + public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + if (viewHolder.itemView instanceof ConversationListItemAction) { + return 0; + } + + if (actionMode != null) { + return 0; + } + + return super.getSwipeDirs(recyclerView, viewHolder); + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId(); + + if (archive) { + new SnackbarAsyncTask(getView(), + getString(R.string.ConversationListFragment_moved_conversation_to_inbox), + getString(R.string.ConversationListFragment_undo), + getResources().getColor(R.color.amber_500), + Snackbar.LENGTH_SHORT, false) + { + @Override + protected void executeAction(@Nullable Long parameter) { + DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId); + } + + @Override + protected void reverseAction(@Nullable Long parameter) { + DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId); + } + }.execute(threadId); + } else { + new SnackbarAsyncTask(getView(), + getString(R.string.ConversationListFragment_archived_conversation), + getString(R.string.ConversationListFragment_undo), + getResources().getColor(R.color.amber_500), + Snackbar.LENGTH_SHORT, false) + { + @Override + protected void executeAction(@Nullable Long parameter) { + DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId); + } + + @Override + protected void reverseAction(@Nullable Long parameter) { + DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId); + } + }.execute(threadId); + } + } + + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder, + float dX, float dY, int actionState, + boolean isCurrentlyActive) + { + + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + View itemView = viewHolder.itemView; + Paint p = new Paint(); + + if (dX > 0) { + Bitmap icon; + + if (archive) icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_unarchive_white_36dp); + else icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_archive_white_36dp); + + p.setColor(getResources().getColor(R.color.green_500)); + + c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX, + (float) itemView.getBottom(), p); + + c.drawBitmap(icon, + (float) itemView.getLeft() + getResources().getDimension(R.dimen.conversation_list_fragment_archive_padding), + (float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2, + p); + } + + if (Build.VERSION.SDK_INT >= 11) { + float alpha = 1.0f - Math.abs(dX) / (float) viewHolder.itemView.getWidth(); + viewHolder.itemView.setAlpha(alpha); + viewHolder.itemView.setTranslationX(dX); + } + + } else { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + } + } + } diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 0f51dce854..366b09158c 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.ResUtil; +import org.thoughtcrime.securesms.util.ViewUtil; import java.util.Locale; import java.util.Set; @@ -53,7 +54,8 @@ import static org.thoughtcrime.securesms.util.SpanUtil.color; */ public class ConversationListItem extends RelativeLayout - implements Recipients.RecipientsModifiedListener, Unbindable + implements Recipients.RecipientsModifiedListener, + BindableConversationListItem, Unbindable { private final static String TAG = ConversationListItem.class.getSimpleName(); @@ -66,6 +68,7 @@ public class ConversationListItem extends RelativeLayout private TextView subjectView; private FromTextView fromView; private TextView dateView; + private TextView archivedView; private boolean read; private AvatarImageView contactPhotoImage; private ThumbnailView thumbnailView; @@ -94,11 +97,12 @@ public class ConversationListItem extends RelativeLayout this.dateView = (TextView) findViewById(R.id.date); this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image); this.thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail); + this.archivedView = ViewUtil.findById(this, R.id.archived); thumbnailView.setClickable(false); } - public void set(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, - @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) + public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, + @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { this.selectedThreads = selectedThreads; this.recipients = thread.getRecipients(); @@ -118,6 +122,12 @@ public class ConversationListItem extends RelativeLayout dateView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); } + if (thread.isArchived()) { + this.archivedView.setVisibility(View.VISIBLE); + } else { + this.archivedView.setVisibility(View.GONE); + } + setThumbnailSnippet(masterSecret, thread); setBatchState(batchMode); setBackground(thread); @@ -158,7 +168,7 @@ public class ConversationListItem extends RelativeLayout this.thumbnailView.setVisibility(View.GONE); LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams(); - subjectParams.addRule(RelativeLayout.LEFT_OF, 0); + subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.archived); this.subjectView.setLayoutParams(subjectParams); } } @@ -187,4 +197,5 @@ public class ConversationListItem extends RelativeLayout } }); } + } diff --git a/src/org/thoughtcrime/securesms/ConversationListItemAction.java b/src/org/thoughtcrime/securesms/ConversationListItemAction.java new file mode 100644 index 0000000000..6b49f7360e --- /dev/null +++ b/src/org/thoughtcrime/securesms/ConversationListItemAction.java @@ -0,0 +1,50 @@ +package org.thoughtcrime.securesms; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.util.ViewUtil; + +import java.util.Locale; +import java.util.Set; + +public class ConversationListItemAction extends LinearLayout implements BindableConversationListItem { + + private TextView description; + + public ConversationListItemAction(Context context) { + super(context); + } + + public ConversationListItemAction(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public ConversationListItemAction(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + this.description = ViewUtil.findById(this, R.id.description); + } + + @Override + public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { + this.description.setText(getContext().getString(R.string.ConversationListItemAction_archived_conversations_d, thread.getCount())); + } + + @Override + public void unbind() { + + } +} diff --git a/src/org/thoughtcrime/securesms/DeviceActivity.java b/src/org/thoughtcrime/securesms/DeviceActivity.java index d92e6a7d5e..0e2c8cf686 100644 --- a/src/org/thoughtcrime/securesms/DeviceActivity.java +++ b/src/org/thoughtcrime/securesms/DeviceActivity.java @@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libaxolotl.IdentityKeyPair; diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java index 23d8b19eff..762742066d 100644 --- a/src/org/thoughtcrime/securesms/DeviceListFragment.java +++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java @@ -22,7 +22,7 @@ import com.melnykov.fab.FloatingActionButton; import org.thoughtcrime.securesms.database.loaders.DeviceListLoader; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; import org.whispersystems.textsecure.api.TextSecureAccountManager; diff --git a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java index 167a8ca212..0cf49f65f0 100644 --- a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java +++ b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory; import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.InvalidKeyException; diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index c189ce46b8..511283aab6 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener; import org.thoughtcrime.securesms.util.TextSecurePreferences; diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java index 0b83cbe1a3..bf3b79754a 100644 --- a/src/org/thoughtcrime/securesms/InviteActivity.java +++ b/src/org/thoughtcrime/securesms/InviteActivity.java @@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener; diff --git a/src/org/thoughtcrime/securesms/ShareFragment.java b/src/org/thoughtcrime/securesms/ShareFragment.java index 0577077dfa..9a0a7b1472 100644 --- a/src/org/thoughtcrime/securesms/ShareFragment.java +++ b/src/org/thoughtcrime/securesms/ShareFragment.java @@ -90,7 +90,7 @@ public class ShareFragment extends ListFragment implements LoaderManager.LoaderC @Override public Loader onCreateLoader(int arg0, Bundle arg1) { - return new ConversationListLoader(getActivity(), null); + return new ConversationListLoader(getActivity(), null, false); } @Override diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 18cbd066cb..4d16f89676 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -68,7 +68,8 @@ public class DatabaseFactory { private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21; private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23; - private static final int DATABASE_VERSION = 23; + private static final int INTRODUCED_ARCHIVE_VERSION = 24; + private static final int DATABASE_VERSION = 24; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -778,6 +779,11 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL"); } + if (oldVersion < INTRODUCED_ARCHIVE_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN archived INTEGER DEFAULT 0"); + db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 037052accb..a942341746 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -449,7 +449,7 @@ public class MmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(messageId); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); notifyConversationListListeners(); @@ -604,7 +604,7 @@ public class MmsDatabase extends MessagingDatabase { contentValues); DatabaseFactory.getThreadDatabase(context).setUnread(threadId); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); jobManager.add(new TrimThreadJob(context, threadId)); @@ -692,7 +692,7 @@ public class MmsDatabase extends MessagingDatabase { public void markIncomingNotificationReceived(long threadId) { notifyConversationListeners(threadId); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) { DatabaseFactory.getThreadDatabase(context).setUnread(threadId); @@ -808,7 +808,7 @@ public class MmsDatabase extends MessagingDatabase { db.endTransaction(); notifyConversationListeners(contentValues.getAsLong(THREAD_ID)); - DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID)); + DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID), true); } } @@ -821,7 +821,7 @@ public class MmsDatabase extends MessagingDatabase { SQLiteDatabase database = databaseHelper.getWritableDatabase(); database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); - boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId); + boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); return threadDeleted; } diff --git a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java index ac56bc95d0..c1cf58c30d 100644 --- a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java +++ b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java @@ -83,7 +83,7 @@ public class PlaintextBackupImporter { } for (long threadId : modifiedThreads) { - threads.update(threadId); + threads.update(threadId, true); } Log.w("PlaintextBackupImporter", "Exited loop"); diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 0f17ebab66..365c471fae 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -118,7 +118,7 @@ public class SmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(id); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); notifyConversationListListeners(); } @@ -310,7 +310,7 @@ public class SmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(messageId); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); notifyConversationListListeners(); @@ -335,7 +335,7 @@ public class SmsDatabase extends MessagingDatabase { SQLiteDatabase db = databaseHelper.getWritableDatabase(); long newMessageId = db.insert(TABLE_NAME, null, contentValues); - DatabaseFactory.getThreadDatabase(context).update(record.getThreadId()); + DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true); notifyConversationListeners(record.getThreadId()); jobManager.add(new TrimThreadJob(context, record.getThreadId())); @@ -372,7 +372,7 @@ public class SmsDatabase extends MessagingDatabase { SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, null, values); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); jobManager.add(new TrimThreadJob(context, threadId)); @@ -450,7 +450,7 @@ public class SmsDatabase extends MessagingDatabase { DatabaseFactory.getThreadDatabase(context).setUnread(threadId); } - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); jobManager.add(new TrimThreadJob(context, threadId)); @@ -481,7 +481,7 @@ public class SmsDatabase extends MessagingDatabase { SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); - DatabaseFactory.getThreadDatabase(context).update(threadId); + DatabaseFactory.getThreadDatabase(context).update(threadId, true); notifyConversationListeners(threadId); jobManager.add(new TrimThreadJob(context, threadId)); @@ -526,7 +526,7 @@ public class SmsDatabase extends MessagingDatabase { SQLiteDatabase db = databaseHelper.getWritableDatabase(); long threadId = getThreadIdForMessage(messageId); db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); - boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId); + boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); return threadDeleted; } diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index 499219b9bf..f78318b7b0 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -197,7 +197,7 @@ public class SmsMigrator { } ourSmsDatabase.endTransaction(transaction); - DatabaseFactory.getThreadDatabase(context).update(ourThreadId); + DatabaseFactory.getThreadDatabase(context).update(ourThreadId, true); DatabaseFactory.getThreadDatabase(context).notifyConversationListeners(ourThreadId); } finally { diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index c16ab17b01..b468159661 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -59,19 +59,21 @@ public class ThreadDatabase extends Database { public static final String SNIPPET = "snippet"; private static final String SNIPPET_CHARSET = "snippet_cs"; public static final String READ = "read"; - private static final String TYPE = "type"; + public static final String TYPE = "type"; private static final String ERROR = "error"; public static final String SNIPPET_TYPE = "snippet_type"; - private static final String SNIPPET_URI = "snippet_uri"; + public static final String SNIPPET_URI = "snippet_uri"; + public static final String ARCHIVED = "archived"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + RECIPIENT_IDS + " TEXT, " + SNIPPET + " TEXT, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + - SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL);"; + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + ARCHIVED + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");", + "CREATE INDEX IF NOT EXISTS archived_index ON " + TABLE_NAME + " (" + ARCHIVED + ");", }; public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -124,27 +126,36 @@ public class ThreadDatabase extends Database { return db.insert(TABLE_NAME, null, contentValues); } - private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, long date, long type) + private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, long date, long type, boolean unarchive) { - ContentValues contentValues = new ContentValues(4); + ContentValues contentValues = new ContentValues(5); contentValues.put(DATE, date - date % 1000); contentValues.put(MESSAGE_COUNT, count); contentValues.put(SNIPPET, body); contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); contentValues.put(SNIPPET_TYPE, type); + if (unarchive) { + contentValues.put(ARCHIVED, 0); + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); notifyConversationListListeners(); } - public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type) { + public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { ContentValues contentValues = new ContentValues(3); contentValues.put(DATE, date - date % 1000); contentValues.put(SNIPPET, snippet); contentValues.put(SNIPPET_TYPE, type); contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); + + if (unarchive) { + contentValues.put(ARCHIVED, 0); + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); notifyConversationListListeners(); @@ -217,7 +228,7 @@ public class ThreadDatabase extends Database { DatabaseFactory.getSmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); DatabaseFactory.getMmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); - update(threadId); + update(threadId, false); notifyConversationListeners(threadId); } } finally { @@ -302,12 +313,60 @@ public class ThreadDatabase extends Database { } public Cursor getConversationList() { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, DATE + " DESC"); + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_NAME, null, ARCHIVED + " = ?", new String[] {"0"}, null, null, DATE + " DESC"); + setNotifyConverationListListeners(cursor); + return cursor; } + public Cursor getArchivedConversationList() { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_NAME, null, ARCHIVED + " = ?", new String[] {"1"}, null, null, DATE + " DESC"); + + setNotifyConverationListListeners(cursor); + + return cursor; + } + + public int getArchivedConversationListCount() { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + Cursor cursor = null; + + try { + cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, ARCHIVED + " = ?", + new String[] {"1"}, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0); + } + + } finally { + if (cursor != null) cursor.close(); + } + + return 0; + } + + public void archiveConversation(long threadId) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(1); + contentValues.put(ARCHIVED, 1); + + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""}); + notifyConversationListListeners(); + } + + public void unarchiveConversation(long threadId) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(1); + contentValues.put(ARCHIVED, 0); + + db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""}); + notifyConversationListListeners(); + } + public void deleteConversation(long threadId) { DatabaseFactory.getSmsDatabase(context).deleteThread(threadId); DatabaseFactory.getMmsDatabase(context).deleteThread(threadId); @@ -317,7 +376,6 @@ public class ThreadDatabase extends Database { notifyConversationListListeners(); } - public void deleteConversations(Set selectedConversations) { DatabaseFactory.getSmsDatabase(context).deleteThreads(selectedConversations); DatabaseFactory.getMmsDatabase(context).deleteThreads(selectedConversations); @@ -399,7 +457,7 @@ public class ThreadDatabase extends Database { return null; } - public boolean update(long threadId) { + public boolean update(long threadId, boolean unarchive) { MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context); long count = mmsSmsDatabase.getConversationCount(threadId); @@ -421,7 +479,10 @@ public class ThreadDatabase extends Database { if (record.isPush()) timestamp = record.getDateSent(); else timestamp = record.getDateReceived(); - updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record), timestamp, record.getType()); + updateThread(threadId, count, record.getBody().getBody(), + getAttachmentUriFor(record), timestamp, + record.getType(), unarchive); + notifyConversationListListeners(); return false; } else { @@ -456,6 +517,7 @@ public class ThreadDatabase extends Database { public static final int DEFAULT = 2; public static final int BROADCAST = 1; public static final int CONVERSATION = 2; + public static final int ARCHIVE = 3; } public class Reader { @@ -486,10 +548,11 @@ public class ThreadDatabase extends Database { long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE)); + boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; Uri snippetUri = getSnippetUri(cursor); return new ThreadRecord(context, body, snippetUri, recipients, date, count, - read == 1, threadId, type, distributionType); + read == 1, threadId, type, distributionType, archived); } private DisplayRecord.Body getPlaintextBody(Cursor cursor) { diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java index d4a34755b2..f0749c40f8 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java @@ -2,30 +2,64 @@ package org.thoughtcrime.securesms.database.loaders; import android.content.Context; import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MergeCursor; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.util.AbstractCursorLoader; +import java.util.LinkedList; import java.util.List; public class ConversationListLoader extends AbstractCursorLoader { private final String filter; + private final boolean archived; - public ConversationListLoader(Context context, String filter) { + public ConversationListLoader(Context context, String filter, boolean archived) { super(context); - this.filter = filter; + this.filter = filter; + this.archived = archived; } @Override public Cursor getCursor() { - if (filter != null && filter.trim().length() != 0) { - List numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter); + if (filter != null && filter.trim().length() != 0) return getFilteredConversationList(filter); + else if (!archived) return getUnarchivedConversationList(); + else return getArchivedConversationList(); + } - return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(numbers); - } else { - return DatabaseFactory.getThreadDatabase(context).getConversationList(); + private Cursor getUnarchivedConversationList() { + List cursorList = new LinkedList<>(); + cursorList.add(DatabaseFactory.getThreadDatabase(context).getConversationList()); + + int archivedCount = DatabaseFactory.getThreadDatabase(context) + .getArchivedConversationListCount(); + + if (archivedCount > 0) { + MatrixCursor switchToArchiveCursor = new MatrixCursor(new String[] { + ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT, + ThreadDatabase.RECIPIENT_IDS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, + ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI, + ThreadDatabase.ARCHIVED}, 1); + + switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount, + "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, 0, null, 0}); + + cursorList.add(switchToArchiveCursor); } + + return new MergeCursor(cursorList.toArray(new Cursor[0])); + } + + private Cursor getArchivedConversationList() { + return DatabaseFactory.getThreadDatabase(context).getArchivedConversationList(); + } + + private Cursor getFilteredConversationList(String filter) { + List numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter); + return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(numbers); } } diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 539167646b..471d26da6b 100644 --- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -44,10 +44,11 @@ public class ThreadRecord extends DisplayRecord { private final long count; private final boolean read; private final int distributionType; + private final boolean archived; public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, @NonNull Recipients recipients, long date, long count, boolean read, - long threadId, long snippetType, int distributionType) + long threadId, long snippetType, int distributionType, boolean archived) { super(context, body, recipients, date, date, threadId, snippetType); this.context = context.getApplicationContext(); @@ -55,6 +56,7 @@ public class ThreadRecord extends DisplayRecord { this.count = count; this.read = read; this.distributionType = distributionType; + this.archived = archived; } public @Nullable Uri getSnippetUri() { @@ -124,6 +126,10 @@ public class ThreadRecord extends DisplayRecord { return getDateReceived(); } + public boolean isArchived() { + return archived; + } + public int getDistributionType() { return distributionType; } diff --git a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 6e874bf8f3..87be7a2a27 100644 --- a/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactIdentityManager; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory; -import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.TextSecureAccountManager; diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java index 1216b4ff1e..6072414b1f 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java @@ -13,6 +13,7 @@ import android.widget.Toast; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import java.io.File; import java.io.FileOutputStream; diff --git a/src/org/thoughtcrime/securesms/util/ProgressDialogAsyncTask.java b/src/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java similarity index 96% rename from src/org/thoughtcrime/securesms/util/ProgressDialogAsyncTask.java rename to src/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java index 6ea6d02f1a..e862d5d4f5 100644 --- a/src/org/thoughtcrime/securesms/util/ProgressDialogAsyncTask.java +++ b/src/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.util; +package org.thoughtcrime.securesms.util.task; import android.app.ProgressDialog; import android.content.Context; @@ -7,6 +7,7 @@ import android.os.AsyncTask; import java.lang.ref.WeakReference; public abstract class ProgressDialogAsyncTask extends AsyncTask { + private final WeakReference contextReference; private ProgressDialog progress; private final String title; diff --git a/src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java b/src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java new file mode 100644 index 0000000000..db2fd0fc19 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.util.task; + +import android.app.ProgressDialog; +import android.os.AsyncTask; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.view.View; + +public abstract class SnackbarAsyncTask + extends AsyncTask + implements View.OnClickListener +{ + + private final View view; + private final String snackbarText; + private final String snackbarActionText; + private final int snackbarActionColor; + private final int snackbarDuration; + private final boolean showProgress; + + private @Nullable Params reversibleParameter; + private @Nullable ProgressDialog progressDialog; + + public SnackbarAsyncTask(View view, + String snackbarText, + String snackbarActionText, + int snackbarActionColor, + int snackbarDuration, + boolean showProgress) + { + this.view = view; + this.snackbarText = snackbarText; + this.snackbarActionText = snackbarActionText; + this.snackbarActionColor = snackbarActionColor; + this.snackbarDuration = snackbarDuration; + this.showProgress = showProgress; + } + + @Override + protected void onPreExecute() { + if (this.showProgress) this.progressDialog = ProgressDialog.show(view.getContext(), "", "", true); + else this.progressDialog = null; + } + + @SafeVarargs + @Override + protected final Void doInBackground(Params... params) { + this.reversibleParameter = params != null && params.length > 0 ?params[0] : null; + executeAction(reversibleParameter); + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (this.showProgress && this.progressDialog != null) { + this.progressDialog.dismiss(); + this.progressDialog = null; + } + + Snackbar.make(view, snackbarText, snackbarDuration) + .setAction(snackbarActionText, this) + .setActionTextColor(snackbarActionColor) + .show(); + } + + @Override + public void onClick(View v) { + new AsyncTask() { + @Override + protected void onPreExecute() { + if (showProgress) progressDialog = ProgressDialog.show(view.getContext(), "", "", true); + else progressDialog = null; + } + + @Override + protected Void doInBackground(Void... params) { + reverseAction(reversibleParameter); + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (showProgress && progressDialog != null) { + progressDialog.dismiss(); + progressDialog = null; + } + } + }.execute(); + } + + protected abstract void executeAction(@Nullable Params parameter); + protected abstract void reverseAction(@Nullable Params parameter); + +}