From 8d561ead2120db26daa5068d8cebc836bad25f82 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Fri, 30 Aug 2019 17:45:35 -0400 Subject: [PATCH] Stickers in image editor. --- AndroidManifest.xml | 4 + res/drawable-hdpi/ic_sticker_32.webp | Bin 0 -> 1362 bytes res/drawable-mdpi/sticker_32.webp | Bin 0 -> 804 bytes res/drawable-xhdpi/sticker_32.webp | Bin 0 -> 1912 bytes res/drawable-xxhdpi/sticker_32.webp | Bin 0 -> 3314 bytes res/drawable-xxxhdpi/sticker_32.webp | Bin 0 -> 4032 bytes res/layout/image_editor_hud.xml | 10 +- res/layout/media_keyboard.xml | 19 +++- res/layout/media_keyboard_bottom_tab_item.xml | 20 +++- .../scribble_select_new_sticker_activity.xml | 8 ++ res/values/attrs.xml | 7 ++ .../components/emoji/MediaKeyboard.java | 64 ++++++++---- .../emoji/MediaKeyboardBottomTabAdapter.java | 16 ++- .../conversation/ConversationActivity.java | 31 +++--- .../database/model/StickerRecord.java | 1 + .../mediasend/MediaSendActivity.java | 26 ++--- .../scribbles/ImageEditorFragment.java | 97 ++++++++++++------ .../ImageEditorFragmentViewModel.java | 65 ++++++++++++ .../securesms/scribbles/ImageEditorHud.java | 67 +++++++++--- .../scribbles/NewStickerSelectActivity.java | 82 +++++++++++++++ .../securesms/scribbles/UriGlideRenderer.java | 8 +- .../stickers/StickerKeyboardProvider.java | 11 +- 22 files changed, 419 insertions(+), 117 deletions(-) create mode 100644 res/drawable-hdpi/ic_sticker_32.webp create mode 100644 res/drawable-mdpi/sticker_32.webp create mode 100644 res/drawable-xhdpi/sticker_32.webp create mode 100644 res/drawable-xxhdpi/sticker_32.webp create mode 100644 res/drawable-xxxhdpi/sticker_32.webp create mode 100644 res/layout/scribble_select_new_sticker_activity.xml create mode 100644 src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java create mode 100644 src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 75cd3b706f..527fc7b339 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -429,6 +429,10 @@ android:theme="@style/TextSecure.DarkTheme" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> + + diff --git a/res/drawable-hdpi/ic_sticker_32.webp b/res/drawable-hdpi/ic_sticker_32.webp new file mode 100644 index 0000000000000000000000000000000000000000..09d338c31eae12c006105046862225977d31b56f GIT binary patch literal 1362 zcmV-Y1+Ds0Nk&FW1pok7MM6+kP&iCI1pojqFTe{B_vWaPBuC2SkL=)h+f}Ll-7%05 zNs=Q;e*BRg94-E*G9*NjRt7-q z7A*pX1SP0}S~n{Kwg-i7zmP10GSs`BEV8^?%w6BQSpP7x_JG`x<=teSN4kB31FUQn zfh`8MBDAb8K(_#So(H*0mctmzxm)MXbH~8leyl>be?T{d=I7tPYd5wSg2fiB*o~kJ z1?yqiJkP&nmRf^u8%r!euqL_NTL9@6Ay~z3uWpsxb>Dw~$K1u+=O?T`D_i*f|L@&k z6|GtBmhZp6lsmQltkU!6=kNXZm%ji1r+J>(t=bK_1BB1Nf9d@8X1V*ZICr_5b^8Y# zFE7*2zke%hIL~wV{{K&>*Vj4d@BgQ^ZSKA&T_m53k!=+e$xh-LQhCAgH_T(mc*!^& zXS_0xd1V}zY0T*uvW!>mKT}JQM?XjOe*(z=Sqi{pnR}OInE_CLD9DNk$Mks8Ya*`4 z!a|XO`T&v|Hmpp+sc&d#bZuy;cM2KH2&)oIg#r&6wG->+jt=#8w4!TkXW!7Ax$7os zjUWf=4S3i*w4@Jx?pt!m40G@VJYtUzBle6fU$u4n?p?0Cw{Kmwe5?m?@ zEyQFO-5J7F)gk5|3stYeAxC$M*Z1Mplm`BRmfDJa^>G)TZ0Nx4wkU_Nw_|5Rl6z>y z-Bx+5-Pr1>5pkClC`Z8Bh1mEvpcG0n&E8W!3QXpTDo)k(S#OsM7!QPXW4dYCwr!`g zEgvE_B>bZw3w>to>_0x+``VU`8#ir!W9JJv;OMenlqr#!`S<5v)rOw$&PF7kC{y+H zw|8O5?!Ny13obby5nnY0^9d9^^X5f}*FLrTi@%y%&OGPbFKx=-S9y)NlU5c_`)#tr<$9LK3u~1kyXMb`N;&mTq zlR~1Tv>dPR!>uW;1SrYF*W=|sRqH|#mDY*py0LB1?{=liC~?!BA$++y#7<$Fy&5|{ z$i!4dGcsS@j(Fv}`RO6fDDe<0jN-CvR3d~>Iaat4A1WFm5H+1OeTegZO~-`{#48JD z9>x2L2~G*2it%GR?x>B0lC|I8k9g@@bu+>hj1tcvthLzlv!yXgAYg98_TSQmS@+6C zh!6ZwlMpgcG6lt$=))%+LuHg{sh9AI)APAs9!Bh6@0d}AQ8HC3M`{;i`#U*9tKgB$ zV|dTmXB@a3@y@R^NlgYyrVgrcXc#Yk&#{vF^x{H%LU6wf+|_uh6;1AO}kP1s<`-hY{Q6JhNfngNWGq^ieyeN-Tg$T!V UU5{#t1my1tjLEXZfRg{w07`nMW&i*H literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/sticker_32.webp b/res/drawable-mdpi/sticker_32.webp new file mode 100644 index 0000000000000000000000000000000000000000..020d81c54c2a79f56f6baada85d7048d908eef9a GIT binary patch literal 804 zcmV+<1Ka#kNk&E-0{{S5MM6+kP&iBv0{{RoAHW9?|KuQ&BtXMDy?eu zQq^jyUMd5!nrCMBgjAt&)mKj-006djy;@ZlAZF$T0En3(Kq{#J%7LN!ssR3#0?X>E zF4bP$RVNj|M`ge(zJ5NgR_f*Qe#hZ42i&foPq}vU z_;@7nUc0h(>Gu8F;eNO4=Tod*%*^Zh`QY*Y`~COxid9$j@&DuedI4B(j$=DB->QT} z0RT+5Ipp3Y&Bb`!vN+|{Hr~v7M%~WH?94{y?qeT^4~Xdh1c3h~!UFaBEWK2~$Q~pt zu4QtCqG=(Q(c+9i8;C}$wKb;q|l5+ zD;>l4N-0&5B}tNGMNO3+jC53@#00VG`0#s?QDT!a@%`{bl_3L;m-z#cB48U)@g6V7 zu|d!#^%6tCAz~$cQWFd`qtB-(a7!7h+tbtu6r!I` zcXH0#J9~SZ`6-b&fF$;8N9&2(Z-3P`{=Cqd8uRI*0vBqz!Jx=@rNQsL^;`4pLxBXPrnmuFa*|!RmVp@h>WFY z_B7q-?zx@`3kcXI(MreI-QT%}*}cd3fj5#LLtr1$%aV)7_1YT+B&XKa@s5-#ZnDzYv-(+$18U{U6CsNN0J4pY zbhT|fyUDG_Y$QF~wr$(`vTfVeY}=Ltw{0BBj_srprA6BLLvx{=u8)Hkk^BR>C$>&@>)DDJ^@TzEJLKP7W%e-wqtl2o^Q4^wO2up*H zSX$cLY(^9DZ2isihW#g^Oe6}b=#^{ z7lsFtc|Kf6^xZ`Z63~G05G3s7b{s?TE3D$508MtV^I?c1p252;ff--G_E^!G2TigK zQHfzo#k@N#lW0L)miQJOI|hCWd*5!I2W`O;r2w-E&^8gxYstj;7LAHXw37WQtfuOT z6K9@k*8ffP$lPe8!r(y`G^kpgA!>UAM$O_bMx@!6mi?b7S6c+Y1Gc87?^~i5wm=O! zP|7UPROaR4GEuCCt>8HqpfwmrWfR>zBa!wD@i4q}qDVA1tGPVzEpW>wRui4#i>kUK z9*(X?XA_xa<(AKbWCma((R_$O2oLJb`9zIwCdmUknW~=AL$t0FBH-;=VW8dAJden# zP9<%AurjrrwHdhZAl9rB)rLSUHHV1il!gnX0VkP?2|y}bNd0thIQ|45fsHJvc<4F+ zkfX4L6I?ISJ+6^)wTojzIW+JP5!)mT;51TigZ;H9|Q$uSQt97rZ2K%YHkWBYi-u$PUJSGh6xnlq zAMES-bX4}F`Z@~15t&@xiUg}sK&e_{orpKKjwvKT{QO8jj*CEmq;v|}hv$ti4?fen zX>C)Rr!-D(oYdq`Xdd4(qnC&#H|*gBKZX#I;MBadsr?mh-|3zGK7Dxj?zzV!8@OSu zhaGOjAp>KVn*Guv)*9;`Uko{De05h?-Z%;XkV{vA`oATjY3-k!-Yy;>v{2+Bi6)?N zb4WRI@t{KPR32R%2MbufZ=Ss6gJU2Rc<`X_cAtnwdS5>= zy_<*z|2`gp3Y;eXz9pn?7&g#<)xN)rh}zv*(^Z>E$6P51q6`TPMeT{6y6Fcq;46WY91Ott&OPP=S8iM%S}Leo>X7Rm%XyZVH%Zt>1b$6K?SCQU%_?r zB!s7KIzG@cG@Wh}O|0892?{ys>7ENpCcaO6-#5%F>HF@?>Xw8iU~mbl+c!<;d6x{m z9>V=&W^@tVw-h=p=IH>ML6)*WHRWBE!>Cz)z!fP{0lNP0h~7C+ zsR8dcfetYYch8_wQ?206%CVE+Ee5iObn~Q^L7&j{pEZ~_8Z$+aw3%IY>3{RtS*=M{xp_q&KzgpSAn literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/sticker_32.webp b/res/drawable-xxhdpi/sticker_32.webp new file mode 100644 index 0000000000000000000000000000000000000000..f97e9d2d661902fac85c548680b4711789ebc512 GIT binary patch literal 3314 zcmVGfHJk0tno2%lLI6ILp5e!M1p-ynQ} zuLbxPhwt!%75(veq0k!SeAiM5ZHw38C;Wy#@E8680uP84Kw`*Bh3BPA&7;-hXj7@g zrDgMYGCUvyDxd=5o1wB)V8;(FMK@EMQIfeg+pzgDXly5$2RAO&)u z1S+5g8lY8yjuBOLJ*T*$D=qshz%VK`&CiWdN+eq2S|$~?fDp*U5HJA?umdM>0XF~u zXd{5^5@~6v3Q;o$j3IHJtQAgWw0LVG!L}i9y_O2+fkXrbU==PZ_c)J%(?@HZaHP#j zhyAV>Jsx^K_j>L9p8-F$T95mmcih{1!r^rXPJ68TUxfvW;+FYvs#Zr%wmq)FKO-QE zO7u=fBUg7mA)K-E@yJ!TH;O8v955s;jvBiTzW^cZa~1c)`8d}p<_O1}V)n5v3l?3z zFyD+CyIyHzow=*3Og>EV(5%w2xFfOv#Xi zgXayB&453ibB2HRlYZNWM&fGyCzz_NGj9k5jc>@I!vaRHrwDDcIY{2!?M@*9U;tKy zC~%kzxq4KnerA)CiQmn3klZ%w6rrQl9|91P8@yIvh*F}p4*$Yg)j!b?E^>m=J6Qvy zY65dnb&4WT*yyrLq(u~+YH?>^t1fdp%khSBtrHD;)NEL)w=!!sU(N72&5vUWJ09Q9 zZ5iNDoGu{F#;ia<$Jn4Ht|Sn!VnE<_IhECyxu!2 zYRkcROx0~AQxg2vP;6#Ff4#1Gq*T&!1C4ngo>7{ygRN7P!x?{&0nnHi7yJQcZ|oJf zYgB3)ipRkOD+W<~*J_o9PxNj)!3fek+0=q!9t{k}crCo8@@6Eh4P8AkGs&|Wu zR;Au*cSsaGMz(k-BP48sk!2Kj1An+VBlb!)+{!YT>zl=_;VIZdDG|@;^8o8!NY@># zfn!ChqH)@rP}3^bimQJH>E3I>dGt)&p6pVv&Re3eJRzjb#xjbWti`$QP3XGRlCrYQ z9)ytP1<~T`L3?P`BEHaH(P__Fb>>rAFww*@6G1JNcUJdA%KsYjMYL8sKwD(x@$N{~ z|Gh3<;(TmEgQUB>|3}Jmifng-4YhC@Sbl_5okC(~cM_@Mn&`?T-8;V%DgRL}9j@6$ zJfpjSeO*FGpG}I}VON!NqC2}?aTD~JSl4vvnYdj@i?0}+aD))@TL7xDR6W?_00!%+ znEILD5<>Xsii=Bj;Rl3(DC|uLnQ*x3w0K8>x13dp5VE%*26%shiKq_ygphs|Q4Sz} zY5EaD_GEBrY^dbd1HjJ-AtTjd7u7MqZtXt_AwO?fFJG|_bkk-cgv_vdS$7=t^6JBs z5<(V3j}9}SZ_bc~rek5wNeEeuDMFt?v%??TkZ}|;HvE_n@~O{+m;$|&zcXEqgv%T> z2We*rUuaqi8jeH5W&RFnpEl%sEdZrb$?g=R&M#hG{gnWe-v&|AS%}lJUIUcadewn* z91d(-Ya#BhYaUOnmdQv|@>2)4R5lqf&#^zFB(#3bli^blBN=Y!y1qDt0be>K0N=|C zF{q9KC85P8ih3ZnoxEiln_jPoQ4~Wv3&0EFFDXIVTh_C9M*!^+M+xoXG1(=mMP=s& zy{5VP>QRhBby|RfAV`7IplvqkT`#fF&pAqzlrw3`E>-aghlX55_5QX`4}3+^U^&B; zjZ!;W!x+tDwDrR{ddFy;qjZndJ3{}kgYD03(x+N7v6%zM!1Cp0-mqk`=siB)lf6c* zZMT79Bx8=zlmn7vt-vuTUL6R^#nrE`IcD=}geC811@zlRnmGp6GgDhzs@@yxOgutZ zv(w=LxK<3Hc^#)fT{2=Woen6+>-{$6%J9|Cjl75LmDEovvTyJvn=AuRfP(;t zfHWw9HW-1`11q{eVUpfpmID=(cyNePqSaqBVk_nzXxrlsx7zPMH|F%xn*g`DATR@C z(e|&r!ZN_$7ZgDAqz`ghT%pwgb}zTiXLuj8<0PV9XSTsJDXy@a7nKrm&(tyJemEze zLCQOwTuM_Y&J#bBcjDEOOswA<>}(x#MDCZD~ek{L={hQz!yN0%2&wQBdH zlxWo=F||J!x@FyNPAlbg1yarNLL7y_thn<5fUii#3rK(NKXsO!#jMV%E=QNweBEwd zgA&FqcYaPVl0R5}3()$ET0QP>mJG}G-!7r}f80CAwRKhxrSnNZXl7Gaq|M7tCT=G$ zLw^U#<|Lzpkk!sAv*0{7UtO!E8rzj;Zh2PtVwm;*p^HA56M;yNuq-Ol=9PD~q2!fR zXsMWbR37xkk2f%FC~HW~9?3xQ`|MH^4&mO8`*$>p7G1FQeH0R0*AG z%}!pA->`v_(J5S5)$6j1h!JqF=RJ{dKiuki_8><=arwf4=<#zv66~afgE=3Y%h#QrBul0e9cP zUN3}@fy&;!j9?0fh7F#Jf0%ndydY-ZfSFg^u0iuEb3jU6?t_?X|8TG9!|QT>bi0lt zgv2~68zeypc$d7Q&QvS7cZ!9}sp4^n@+xx!ysmh4RV>ns(6E2F2{djCg@0QBO6Sfk zkF-221(ZN$Uy{#D>-mIPs|=KJU@+@A#DFw)(E$?)X0C?SMH|Vao^=YB)~0U3 zqW=z8+8;dUloyY1>qVrgOHVY6U!C!W?&+{^h~Nb#W#=eBzjh^Oq+{}9Tu!IMyIN7= z5PWatxHr+&)p3)M_B(?W-pTq{&PSN0|}9JT4Vf|NK!c^kL|&APyVi8Sj1 zJs;a_jNLy}%^=nN*A72SmC^O$VQw76AvUC4*SLnt2MIHd(|>viQ{^BIu_E2N%zii? zluB|a#QM*lgYo?(}&LYV{9O8hK{Gt-hyTyMmC|pqH zSpmZoj^EdQ>tjFfe&c-Cgd+napGL&|x68*t9O8hq>v}*chGt&Sj8*~AQjG%J%&Kd< z#iwR5FX57umBYj_C#

$(v(yyyF^nhhqfyEm}O7hg>LF9cU wAZ7&dbzw>3d7hVQ9?f6iK2We@DS_7?;(mYKk5;c+Ia%az0BLG7Xy4b@5npd%x&QzG literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/sticker_32.webp b/res/drawable-xxxhdpi/sticker_32.webp new file mode 100644 index 0000000000000000000000000000000000000000..7bda7018b7c511b41b99af602eb6dd1ea8dcabcc GIT binary patch literal 4032 zcmV;x4?plyNk&Gv4*&pHMM6+kP&iDi4*&o!f50CQO)zL1$&s?N-@~8q6+MKA{!aiu zMjiA#fkc^iU+@?mwEG4%6WV$U%P_a9s4AAVqk^uY3ZFBVdBCz(W^yeO;OCL~Rv$U3tp+qF)Pl#xBwcvc&01<8@Nm8V#KJA%h*3f@j-VvOd z0Q=jv9gig4rBPdBuWj2l$F^}^T~o8un66av!}or@HTVOrJWuXQ1U+oq z4L9ERoij5*9n`7qHbu0yZQJ{^{iki)w$XNN)%GIE7L%l%Iddl9;D1I8z0@Vgmxg_b zh=rc?@QZsgEcS6}TD2OI-=3kZZB6=|phSewFD-^KW~9#uPAE~k7xLI8doZliZOm4r z_X}=Hxuu2lKz}q(*RW>(vWZCV)x*uLryldXCs)t&dEB-{q<5mybCOrz<8j8rynTf2 zBr$e)71$*HZE4T5O|woiO*9RJ4H*0N{e~WzJtT!=B;H~zt=&=^p+;~d_>{cF_dnmz z_4|5%-gVQ!GA30FDM+*gQIQl1qW8g_II)d6ApLW$4GJdZ@GKma7c@S2;r6;S+9D*S zgsx~?B=1vi9nQoF&yy~m`<6w5XYt~mUw*UmkCIYiMM&?Hx{cG~?}VB6sOc200C1nX~e0p8W$+uUxwCBnju<(J;8=q1H{Kf*bh_a zWV$MyCa!B-+b|<+?Wi@(Gt4s+r=?6u?bFr4^t{(4i(-bvh9pK|sJ5fx)DmnNx(laz9x0vdL4^Yv|Lj7thmq#}wL$XIOeDwX@IM`1$c( zxm2#K6OV@tb9K->4)b%`zH{#HM-PhLeD%6jF3>|~Ss)^$(fOT1gtYB%&ZGA6&U2o- zRFX#}i1Xrpsrk|2LoQ$c?Pl^px0GP;2GTmwi4Jugg_Gpsik73)RQ~+$Rwv|9uDAm~ zwBCTVhj8Fi+RlIdkN-9&N-g%#khJJ@I-PQTLBu3GBs8~E91Z=lVOWtfLl8o^Y4>2R z4?5lGQ1xPMvGU7?MySAO==XhMre-lSgzwXlxS8@6Td_h#=?$tYj?ktKX#p zhfb+$$cUJCMXa`_(5HW)$smrgjY+Uy3T6m`hzQ^X<~~DVWcbWFsZuy~bZ~K=nlqLQ zs=eZy3eo~FiLo_rI~oy01c37``MqL=zkK&&pC&pUyQJxn=SdT06&~-{=A@hgu+tm! zf%7B+{E6?btD2fodF9gQg-P@QM;D(+_~N;O;u+7$lVI>k2m%&sm%hHwt(AeOix)|h za5$B#lbdrG)>#xUK;MI@L7*2Gj@#=ykTMCAGLA`0GrPb zA^`qTTiCEv0^2nqtb&tr3g0=cGQh^4M-Tyg_jF-a{wnkz+>>h$(k5|Ko1t^Mlr1jA z*nL_=5QNWZ8Xd3ng>91fckk`=wo4Ae+tLb;_YSLZvV6yNnnlWRPldOoWoFupk$9f1 zFx1n51AT!bL#JDKo-H$lW0Tb~&01L4Ur_tJwRu?BUzldibXs8lnOICV6$ZVPL%!uk zXV6=iWD?o}+guwamhmAsy)r%Q+l8L=@N#DK#UcwKkG(Qa_tW-W(zFxYqldg6 zv-!?JY1NWvy)EiAN~*d(L0 zA&fV~@i!zFp1gK89OMgzZA6UI81OpcPU){m*}yg}ZbHnerd1;Q4T7h4@zB}fK%W)a zSOll39G(3M_Xb6+6rPz6H=PUj@$hMnwv#kAFEd^+?+uFt&see3e>=}|qJ7fPBWco~ zFde(3X9~~Yxo7SkI-7%}PkL?1ERRq={(pxth_X%gD2hE0yFx%c-Sj%woIBz6281Gb$utCC;sP>x_l8-c-=`Q3AS z9~^kt_ZQ#qhUakUq?ah#me3XHcDuHuu1_rAd8VMePWfx2+=W9vH*su4oHozXed}IU zaJcgywRax7ct9DlDM|5)QM95Y2nEKr2)51h4Yw?#womu*RMBpGgRa^OzKpRGoBVjp zQ4dfXkDa>Z%VYg=sY+==0Ee+{L`a)Byx?<7scnSj1w~S{)Kx)g`!?tSg@VAm!!$ zyI$YdE4Pw_;$R0h*iKjBfb4CzQQK1gd-8|A2mwNG)D=Isw88{pbU_%4*!1I%?xH;J z(iflCWYvTqG7M>q)8&{(zrMDWOYLy?$xlA1h*j|X?$%<3i2|3O;Ns8MQU34WpZZm9 zg#$utV?;=o@$RYn9;8;`m-9q|yrcG=7*O%vg8L9o+!FI&r9`r}t$YLQb^ z078@4r0pb)y$XD5S<_AJlA-gTyeUOhk_rLiaFMlW1PkjKgiLF%al(n+4+kBlc;Ek< zG{|{s2e&eIVkc>vEn-i&T~L4TZ(DpWhl&xpt8Tx1q!B8tWkk#$hN|2%kK)3HPyZ-a zpz4HlMy8z@zA|TYA+^i>H$8p4K~*vc5atZ-^czQyBZ0znW7@Jmpd@Ak-3!(g8i5_2 zl1HMrlSA&Frk&XFVbkS@sehz?hqvTZg}4Rw$t~qq_b?(C){Hi${Xv|Nj!R}!{NB6E zCMzcLpze|dY`D%dWv!sLA%FFyy@pk-NO2K_N$zUDV_57ftZk}1?xohv&`;jHq%hdD zeWM)WLeyQ|uI2T{oAxEO^Uv+`;!>CFh$lijOMZ7Wp70f>SwwK0*V|3lSGp)(^^QD% z@9M6|G`9G;Wv|{PsBMn_`0dSGR4uE3BuUaCzkAfyJbt4vE1}TVD_WJo`{xyg+D^Qw z60p0g?$TgyBz|SP5~y9&eALr_cgrpmU=fox`TZl~-k!qr6o;+7q(r_--^wa{`%8HQ zkapkw%+cAOJw$Db{q~bhj;cnn2i-z-t3DA+!I+?cnVaYO&8TASUf&?$6;m}Ti96PgO%So9|8xlB8_6! z$uIo*jLN_kNvBCeKYnXTb1Jn zLBveYTH>uKg`sYxSOlljw)lG@uepW#$BymvhMcMpAT&;s;>kPDoF*lH{dHm8noWjJ z>##t);#st?R%~I>fRYf3q;131jx{$^I~w@LE4y4*wPHeOL~PPzJfoa8lPYDGpGHP1 zKIoQnUR%aN9P`-V9;TTKgEtgw+nM;4`rG!(K<$Ey2R=XOk{xjogh|?%XLwH~O3E8I z4@6S^!#~SZF=-))BG`e2Nv6VVpR6Kwg163Hw_H%$RDSl(=2uj$IN|Qfo$~%cqNIF} z`ISox&GDZ#NQ)2z5kUmw023k$b7e&fErQn)zqxfaE49OIM?d)ox9lQtAJnb9e^C;; z61)3O8i=gW2>$0L2#E+H7z_psh1nrR354c=%= z+kMmQ0?XwJ;zST;7z0w_k#R+cvMTw;@|ImtyLj+hNAAi~rKnI~`$Uz`EVSEg3GJOz zrsozI2zz048pLau(JvL|aTx@ki#ua4wc-z*y}C_Ic_6GhQQAjPGDX|%_Un>*^#ukD zZ_WSo&&#+FKzwW_`e|VZBwassV5*>YnfJ%__XftL3WP?)MW34^XK}rR9$KJRcSYeD zubtH?3FNft@$qpX7O`>Z_%Tn@=`=IVC1&IvcFULKul=;! znFa_7w$stW3OCVGpLm5Oaa+8upzi3kh5Emo*59{l@1-#c~UmBorULQ#p* zN#E&J3oGes=A7L9H91uw6xeiMxn`?&Z@9$D}#&yDzu+Uk+u8ryz0K*uH57A*~V;En$;{t(-5f zS+k4=SwOAUz<+-h{TbS?YQzGi*~6 z`wcx}uh1t5m&;H2=ePgk!pUcb%Vk%bh#*sFlh|1NyF4x7bbry`|Nizm!%26sGdlCL zKiBg-vpU8+7UK?jk@oCDCtM`y(04$f7xA6R+Kxp^3@Y# mW0ka~a!dL8(O<@%JWVUPY2WVY+K`?giuqS@j6D#O|5*}rt0)5i literal 0 HcmV?d00001 diff --git a/res/layout/image_editor_hud.xml b/res/layout/image_editor_hud.xml index 4ebafacaca..8601d9919a 100644 --- a/res/layout/image_editor_hud.xml +++ b/res/layout/image_editor_hud.xml @@ -75,13 +75,21 @@ android:src="@drawable/ic_brush_highlight_32" /> + + + app:layout_constraintTop_toBottomOf="@id/media_keyboard_tabs_top" /> + + + app:layout_constraintStart_toStartOf="parent" + tools:layout_height="40dp" + tools:visibility="visible" /> - + android:background="?emoji_tab_indicator" + android:visibility="gone" + tools:visibility="visible" /> + android:padding="6dp" /> + + diff --git a/res/layout/scribble_select_new_sticker_activity.xml b/res/layout/scribble_select_new_sticker_activity.xml new file mode 100644 index 0000000000..a99e01fd22 --- /dev/null +++ b/res/layout/scribble_select_new_sticker_activity.xml @@ -0,0 +1,8 @@ + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 92d2670708..c2ed44653b 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -355,4 +355,11 @@ + + + + + + + diff --git a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java index 29e8b47671..3443725dd3 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java +++ b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboard.java @@ -1,18 +1,20 @@ package org.thoughtcrime.securesms.components.emoji; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.viewpager.widget.PagerAdapter; -import androidx.viewpager.widget.ViewPager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; +import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.InputAwareLayout.InputView; import org.thoughtcrime.securesms.components.RepeatableImageKey; @@ -29,16 +31,18 @@ public class MediaKeyboard extends FrameLayout implements InputView, private static final String TAG = Log.tag(MediaKeyboard.class); - private RecyclerView categoryTabs; - private ViewPager categoryPager; - private ViewGroup providerTabs; - private RepeatableImageKey backspaceButton; - private RepeatableImageKey backspaceButtonBackup; - private View searchButton; - private View addButton; - private MediaKeyboardListener keyboardListener; - private MediaKeyboardProvider[] providers; - private int providerIndex; + private RecyclerView categoryTabs; + private ViewPager categoryPager; + private ViewGroup providerTabs; + private RepeatableImageKey backspaceButton; + private RepeatableImageKey backspaceButtonBackup; + private View searchButton; + private View addButton; + @Nullable private MediaKeyboardListener keyboardListener; + private MediaKeyboardProvider[] providers; + private int providerIndex; + + private final boolean tabsAtBottom; private MediaKeyboardBottomTabAdapter categoryTabAdapter; @@ -48,6 +52,14 @@ public class MediaKeyboard extends FrameLayout implements InputView, public MediaKeyboard(Context context, AttributeSet attrs) { super(context, attrs); + + TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaKeyboard, 0, 0); + + try { + tabsAtBottom = typedArray.getInt(R.styleable.MediaKeyboard_tabs_gravity, 0) == 0; + } finally { + typedArray.recycle(); + } } public void setProviders(int startIndex, MediaKeyboardProvider... providers) { @@ -59,7 +71,7 @@ public class MediaKeyboard extends FrameLayout implements InputView, } } - public void setKeyboardListener(MediaKeyboardListener listener) { + public void setKeyboardListener(@Nullable MediaKeyboardListener listener) { this.keyboardListener = listener; } @@ -76,8 +88,14 @@ public class MediaKeyboard extends FrameLayout implements InputView, params.height = height; Log.i(TAG, "showing emoji drawer with height " + params.height); setLayoutParams(params); - setVisibility(VISIBLE); + show(); + } + + public void show() { + if (this.categoryPager == null) initView(); + + setVisibility(VISIBLE); if (keyboardListener != null) keyboardListener.onShown(); requestPresent(providers, providerIndex); @@ -122,7 +140,7 @@ public class MediaKeyboard extends FrameLayout implements InputView, public void requestDismissal() { hide(true); providerIndex = 0; - keyboardListener.onKeyboardProviderChanged(providers[providerIndex]); + if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(providers[providerIndex]); } @Override @@ -148,7 +166,10 @@ public class MediaKeyboard extends FrameLayout implements InputView, private void initView() { final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true); - this.categoryTabs = view.findViewById(R.id.media_keyboard_tabs); + RecyclerView categoryTabsTop = view.findViewById(R.id.media_keyboard_tabs_top); + RecyclerView categoryTabsBottom = view.findViewById(R.id.media_keyboard_tabs); + + this.categoryTabs = tabsAtBottom ? categoryTabsBottom : categoryTabsTop; this.categoryPager = view.findViewById(R.id.media_keyboard_pager); this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs); this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace); @@ -156,10 +177,11 @@ public class MediaKeyboard extends FrameLayout implements InputView, this.searchButton = view.findViewById(R.id.media_keyboard_search); this.addButton = view.findViewById(R.id.media_keyboard_add); - this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this); + this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this, tabsAtBottom); categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); categoryTabs.setAdapter(categoryTabAdapter); + categoryTabs.setVisibility(VISIBLE); } private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) { diff --git a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java index 440ce88e1e..80fd336813 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java +++ b/src/org/thoughtcrime/securesms/components/emoji/MediaKeyboardBottomTabAdapter.java @@ -15,19 +15,22 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter { - DatabaseFactory.getStickerDatabase(this).updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis()); - }); + SignalExecutors.BOUNDED.execute(() -> + DatabaseFactory.getStickerDatabase(getApplicationContext()) + .updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis()) + ); } private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) { diff --git a/src/org/thoughtcrime/securesms/database/model/StickerRecord.java b/src/org/thoughtcrime/securesms/database/model/StickerRecord.java index 76ad74fbdf..8db2525d3d 100644 --- a/src/org/thoughtcrime/securesms/database/model/StickerRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/StickerRecord.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.database.model; import android.net.Uri; + import androidx.annotation.NonNull; import org.thoughtcrime.securesms.mms.PartAuthority; diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index 1329e5330c..481c01ac80 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -1,10 +1,5 @@ package org.thoughtcrime.securesms.mediasend; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.lifecycle.ViewModelProviders; - import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; @@ -15,12 +10,6 @@ import android.graphics.Rect; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; @@ -32,6 +21,16 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.TransportOption; @@ -438,10 +437,13 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple } @Override - public void onRequestFullScreen(boolean fullScreen) { + public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) { if (captionAndRail != null) { captionAndRail.setVisibility(fullScreen ? View.GONE : View.VISIBLE); } + if (hideKeyboard && hud.isKeyboardOpen()) { + hud.hideSoftkey(composeText, null); + } } @Override diff --git a/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java index 50ab3041ad..93703a83fe 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java +++ b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java @@ -6,14 +6,16 @@ import android.graphics.Bitmap; import android.graphics.Paint; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.imageeditor.ColorableRenderer; import org.thoughtcrime.securesms.imageeditor.ImageEditorView; @@ -28,6 +30,7 @@ import org.thoughtcrime.securesms.mms.PushMediaConstraints; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker; +import org.thoughtcrime.securesms.stickers.StickerSearchRepository; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ParcelUtil; import org.thoughtcrime.securesms.util.SaveAttachmentTask; @@ -36,7 +39,6 @@ import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import java.io.ByteArrayOutputStream; -import java.io.IOException; import static android.app.Activity.RESULT_OK; @@ -48,14 +50,15 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu private static final String KEY_IMAGE_URI = "image_uri"; - public static final int SELECT_STICKER_REQUEST_CODE = 123; + private static final int SELECT_OLD_STICKER_REQUEST_CODE = 123; + private static final int SELECT_NEW_STICKER_REQUEST_CODE = 124; private EditorModel restoredModel; - @Nullable - private EditorElement currentSelection; - private int imageMaxHeight; - private int imageMaxWidth; + @Nullable private EditorElement currentSelection; + private int imageMaxHeight; + private int imageMaxWidth; + private ImageEditorFragmentViewModel viewModel; public static class Data { private final Bundle bundle; @@ -118,6 +121,13 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu imageMaxWidth = mediaConstraints.getImageMaxWidth(requireContext()); imageMaxHeight = mediaConstraints.getImageMaxHeight(requireContext()); + + StickerSearchRepository repository = new StickerSearchRepository(requireContext()); + + viewModel = ViewModelProviders.of(this, new ImageEditorFragmentViewModel.Factory(requireActivity().getApplication(), repository)) + .get(ImageEditorFragmentViewModel.class); + + viewModel.getStickersAvailability().observe(this, isAvailable -> imageEditorHud.setStickersAvailable(isAvailable)); } @Nullable @@ -233,15 +243,26 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK && requestCode == SELECT_STICKER_REQUEST_CODE && data != null) { - final String stickerFile = data.getStringExtra(StickerSelectActivity.EXTRA_STICKER_FILE); - - UriGlideRenderer renderer = new UriGlideRenderer(Uri.parse("file:///android_asset/" + stickerFile), false, imageMaxWidth, imageMaxHeight); - EditorElement element = new EditorElement(renderer); - imageEditorView.getModel().addElementCentered(element, 0.2f); - currentSelection = element; + if (resultCode == RESULT_OK && requestCode == SELECT_NEW_STICKER_REQUEST_CODE && data != null) { + final Uri uri = data.getData(); + if (uri != null) { + UriGlideRenderer renderer = new UriGlideRenderer(uri, true, imageMaxWidth, imageMaxHeight); + EditorElement element = new EditorElement(renderer); + imageEditorView.getModel().addElementCentered(element, 0.2f); + currentSelection = element; + imageEditorHud.setMode(ImageEditorHud.Mode.MOVE_DELETE); + } + } else if (resultCode == RESULT_OK && requestCode == SELECT_OLD_STICKER_REQUEST_CODE && data != null) { + final Uri uri = data.getData(); + if (uri != null) { + UriGlideRenderer renderer = new UriGlideRenderer(uri, false, imageMaxWidth, imageMaxHeight); + EditorElement element = new EditorElement(renderer); + imageEditorView.getModel().addElementCentered(element, 0.2f); + currentSelection = element; + imageEditorHud.setMode(ImageEditorHud.Mode.MOVE_DELETE); + } } else { - imageEditorHud.enterMode(ImageEditorHud.Mode.NONE); + imageEditorHud.setMode(ImageEditorHud.Mode.NONE); } } @@ -253,31 +274,46 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu controller.onTouchEventsNeeded(mode != ImageEditorHud.Mode.NONE); switch (mode) { - case CROP: + case CROP: { imageEditorView.getModel().startCrop(); - break; + break; + } - case DRAW: + case DRAW: { imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND); break; + } - case HIGHLIGHT: + case HIGHLIGHT: { imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE); break; + } - case TEXT: + case TEXT: { addText(); break; + } + + case INSERT_ASSET_STICKER: { + Intent intent = new Intent(getContext(), StickerSelectActivity.class); + startActivityForResult(intent, SELECT_OLD_STICKER_REQUEST_CODE); + break; + } + + case INSERT_STICKER: { + Intent intent = new Intent(getContext(), NewStickerSelectActivity.class); + startActivityForResult(intent, SELECT_NEW_STICKER_REQUEST_CODE); + break; + } case MOVE_DELETE: - Intent intent = new Intent(getContext(), StickerSelectActivity.class); - startActivityForResult(intent, SELECT_STICKER_REQUEST_CODE); break; - case NONE: + case NONE: { imageEditorView.getModel().doneCrop(); currentSelection = null; break; + } } } @@ -350,8 +386,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu } @Override - public void onRequestFullScreen(boolean fullScreen) { - controller.onRequestFullScreen(fullScreen); + public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) { + controller.onRequestFullScreen(fullScreen, hideKeyboard); } private void refreshUniqueColors() { @@ -371,8 +407,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu } else { currentSelection = null; controller.onTouchEventsNeeded(false); - imageEditorHud.enterMode(ImageEditorHud.Mode.NONE); - imageEditorView.doneTextEditing(); + imageEditorHud.setMode(ImageEditorHud.Mode.NONE); } } @@ -383,7 +418,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu if (editorElement.getRenderer() instanceof MultiLineTextRenderer) { setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), imageEditorView.isTextEditing()); } else { - imageEditorHud.enterMode(ImageEditorHud.Mode.MOVE_DELETE); + imageEditorHud.setMode(ImageEditorHud.Mode.MOVE_DELETE); } } } @@ -412,6 +447,6 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu public interface Controller { void onTouchEventsNeeded(boolean needed); - void onRequestFullScreen(boolean fullScreen); + void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard); } } diff --git a/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java new file mode 100644 index 0000000000..8f843d8d44 --- /dev/null +++ b/src/org/thoughtcrime/securesms/scribbles/ImageEditorFragmentViewModel.java @@ -0,0 +1,65 @@ +package org.thoughtcrime.securesms.scribbles; + +import android.app.Application; +import android.database.ContentObserver; +import android.os.Handler; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import org.thoughtcrime.securesms.database.DatabaseContentProviders; +import org.thoughtcrime.securesms.stickers.StickerSearchRepository; +import org.thoughtcrime.securesms.util.Throttler; + +public final class ImageEditorFragmentViewModel extends ViewModel { + + private final Application application; + private final StickerSearchRepository repository; + private final MutableLiveData stickersAvailable; + private final Throttler availabilityThrottler; + private final ContentObserver packObserver; + + private ImageEditorFragmentViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) { + this.application = application; + this.repository = repository; + this.stickersAvailable = new MutableLiveData<>(); + this.availabilityThrottler = new Throttler(500); + this.packObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue)); + } + }; + + application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver); + } + + @NonNull LiveData getStickersAvailability() { + repository.getStickerFeatureAvailability(stickersAvailable::postValue); + return stickersAvailable; + } + + @Override + protected void onCleared() { + application.getContentResolver().unregisterContentObserver(packObserver); + } + + static class Factory extends ViewModelProvider.NewInstanceFactory { + private final Application application; + private final StickerSearchRepository repository; + + public Factory(@NonNull Application application, @NonNull StickerSearchRepository repository) { + this.application = application; + this.repository = repository; + } + + @Override + public @NonNull T create(@NonNull Class modelClass) { + //noinspection ConstantConditions + return modelClass.cast(new ImageEditorFragmentViewModel(application, repository)); + } + } +} diff --git a/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java b/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java index efc062b14d..81f37d5f9d 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java +++ b/src/org/thoughtcrime/securesms/scribbles/ImageEditorHud.java @@ -2,15 +2,17 @@ package org.thoughtcrime.securesms.scribbles; import android.content.Context; import android.graphics.Color; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter; import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker; @@ -34,7 +36,8 @@ public final class ImageEditorHud extends LinearLayout { private View drawButton; private View highlightButton; private View textButton; - private View stickerButton; + private View oldStickerButton; + private View newStickerButton; private View undoButton; private View saveButton; private View deleteButton; @@ -80,7 +83,8 @@ public final class ImageEditorHud extends LinearLayout { drawButton = findViewById(R.id.scribble_draw_button); highlightButton = findViewById(R.id.scribble_highlight_button); textButton = findViewById(R.id.scribble_text_button); - stickerButton = findViewById(R.id.scribble_sticker_button); + oldStickerButton = findViewById(R.id.old_scribble_sticker_button); + newStickerButton = findViewById(R.id.scribble_sticker_button); undoButton = findViewById(R.id.scribble_undo_button); saveButton = findViewById(R.id.scribble_save_button); deleteButton = findViewById(R.id.scribble_delete_button); @@ -102,7 +106,7 @@ public final class ImageEditorHud extends LinearLayout { } private void initializeVisibilityMap() { - setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, stickerButton, cropButton, undoButton, saveButton); + setStickersAvailable(false); setVisibleViewsWhenInMode(Mode.DRAW, confirmButton, undoButton, colorPicker, colorPalette); @@ -112,17 +116,34 @@ public final class ImageEditorHud extends LinearLayout { setVisibleViewsWhenInMode(Mode.MOVE_DELETE, confirmButton, deleteButton); + setVisibleViewsWhenInMode(Mode.INSERT_STICKER, confirmButton); + + setVisibleViewsWhenInMode(Mode.INSERT_ASSET_STICKER, confirmButton); + setVisibleViewsWhenInMode(Mode.CROP, confirmButton, cropFlipButton, cropRotateButton, cropAspectLock, undoButton); for (Set views : visibilityModeMap.values()) { allViews.addAll(views); } + + allViews.add(newStickerButton); + allViews.add(oldStickerButton); } private void setVisibleViewsWhenInMode(Mode mode, View... views) { visibilityModeMap.put(mode, new HashSet<>(Arrays.asList(views))); } + @MainThread + public void setStickersAvailable(boolean stickersAvailable) { + if (stickersAvailable) { + setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, newStickerButton, cropButton, undoButton, saveButton); + } else { + setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, oldStickerButton, cropButton, undoButton, saveButton); + } + updateButtonVisibility(currentMode); + } + private void initializeViews() { undoButton.setOnClickListener(v -> eventListener.onUndo()); @@ -146,7 +167,8 @@ public final class ImageEditorHud extends LinearLayout { drawButton.setOnClickListener(v -> setMode(Mode.DRAW)); highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT)); textButton.setOnClickListener(v -> setMode(Mode.TEXT)); - stickerButton.setOnClickListener(v -> setMode(Mode.MOVE_DELETE)); + oldStickerButton.setOnClickListener(v -> setMode(Mode.INSERT_ASSET_STICKER)); + newStickerButton.setOnClickListener(v -> setMode(Mode.INSERT_STICKER)); saveButton.setOnClickListener(v -> eventListener.onSave()); } @@ -172,16 +194,13 @@ public final class ImageEditorHud extends LinearLayout { setMode(mode, false); } - private void setMode(@NonNull Mode mode) { + public void setMode(@NonNull Mode mode) { setMode(mode, true); } private void setMode(@NonNull Mode mode, boolean notify) { this.currentMode = mode; - Set visibleButtons = visibilityModeMap.get(mode); - for (View button : allViews) { - button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE); - } + updateButtonVisibility(mode); switch (mode) { case CROP: presentModeCrop(); break; @@ -193,7 +212,14 @@ public final class ImageEditorHud extends LinearLayout { if (notify) { eventListener.onModeStarted(mode); } - eventListener.onRequestFullScreen(mode != Mode.NONE); + eventListener.onRequestFullScreen(mode != Mode.NONE, mode != Mode.TEXT); + } + + private void updateButtonVisibility(@NonNull Mode mode) { + Set visibleButtons = visibilityModeMap.get(mode); + for (View button : allViews) { + button.setVisibility(buttonIsVisible(visibleButtons, button) ? VISIBLE : GONE); + } } private boolean buttonIsVisible(@Nullable Set visibleButtons, @NonNull View button) { @@ -236,7 +262,14 @@ public final class ImageEditorHud extends LinearLayout { } public enum Mode { - NONE, DRAW, HIGHLIGHT, TEXT, MOVE_DELETE, CROP + NONE, + CROP, + TEXT, + DRAW, + HIGHLIGHT, + MOVE_DELETE, + INSERT_STICKER, + INSERT_ASSET_STICKER } public interface EventListener { @@ -249,7 +282,7 @@ public final class ImageEditorHud extends LinearLayout { void onRotate90AntiClockwise(); void onCropAspectLock(boolean locked); boolean isCropAspectLocked(); - void onRequestFullScreen(boolean fullScreen); + void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard); } private static final EventListener NULL_EVENT_LISTENER = new EventListener() { @@ -292,7 +325,7 @@ public final class ImageEditorHud extends LinearLayout { } @Override - public void onRequestFullScreen(boolean fullScreen) { + public void onRequestFullScreen(boolean fullScreen, boolean hideKeyboard) { } }; } diff --git a/src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java b/src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java new file mode 100644 index 0000000000..ae6c81d773 --- /dev/null +++ b/src/org/thoughtcrime/securesms/scribbles/NewStickerSelectActivity.java @@ -0,0 +1,82 @@ +package org.thoughtcrime.securesms.scribbles; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; +import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.model.StickerRecord; +import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider; +import org.thoughtcrime.securesms.stickers.StickerManagementActivity; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; + +public final class NewStickerSelectActivity extends FragmentActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + setContentView(R.layout.scribble_select_new_sticker_activity); + + MediaKeyboard mediaKeyboard = findViewById(R.id.emoji_drawer); + + mediaKeyboard.setProviders(0, new StickerKeyboardProvider(this, new StickerKeyboardProvider.StickerEventListener() { + @Override + public void onStickerSelected(@NonNull StickerRecord sticker) { + Intent intent = new Intent(); + intent.setData(sticker.getUri()); + setResult(RESULT_OK, intent); + + SignalExecutors.BOUNDED.execute(() -> + DatabaseFactory.getStickerDatabase(getApplicationContext()) + .updateStickerLastUsedTime(sticker.getRowId(), System.currentTimeMillis()) + ); + + finish(); + } + + @Override + public void onStickerManagementClicked() { + startActivity(StickerManagementActivity.getIntent(NewStickerSelectActivity.this)); + } + } + )); + + mediaKeyboard.setKeyboardListener(new MediaKeyboard.MediaKeyboardListener() { + @Override + public void onShown() { + } + + @Override + public void onHidden() { + finish(); + } + + @Override + public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) { + } + }); + + mediaKeyboard.show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java b/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java index 429136917f..37f1a206c7 100644 --- a/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java +++ b/src/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java @@ -63,9 +63,7 @@ final class UriGlideRenderer implements Renderer { try { Bitmap bitmap = getBitmapGlideRequest(rendererContext.context, false).submit().get(); setBitmap(rendererContext, bitmap); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { + } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } } else { @@ -73,6 +71,8 @@ final class UriGlideRenderer implements Renderer { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { setBitmap(rendererContext, resource); + + rendererContext.invalidate.onInvalidate(UriGlideRenderer.this); } @Override @@ -99,7 +99,7 @@ final class UriGlideRenderer implements Renderer { paint.setAlpha(alpha); rendererContext.restore(); - } else { + } else if (rendererContext.isBlockingLoad()) { // If failed to load, we draw a black out, in case image was sticker positioned to cover private info. rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint); } diff --git a/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java b/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java index df9ab953bc..5b47fc8ade 100644 --- a/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java +++ b/src/org/thoughtcrime/securesms/stickers/StickerKeyboardProvider.java @@ -1,16 +1,17 @@ package org.thoughtcrime.securesms.stickers; -import androidx.lifecycle.ViewModelProviders; import android.content.Context; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.widget.ImageView; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentStatePagerAdapter; -import androidx.appcompat.app.AppCompatActivity; -import android.widget.ImageView; +import androidx.lifecycle.ViewModelProviders; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider; @@ -48,7 +49,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider, private boolean isSoloProvider; private StickerKeyboardViewModel viewModel; - public StickerKeyboardProvider(@NonNull AppCompatActivity activity, + public StickerKeyboardProvider(@NonNull FragmentActivity activity, @NonNull StickerEventListener eventListener) { this.context = activity; @@ -109,7 +110,7 @@ public final class StickerKeyboardProvider implements MediaKeyboardProvider, } } - private void initViewModel(@NonNull AppCompatActivity activity) { + private void initViewModel(@NonNull FragmentActivity activity) { StickerKeyboardRepository repository = new StickerKeyboardRepository(DatabaseFactory.getStickerDatabase(activity)); viewModel = ViewModelProviders.of(activity, new StickerKeyboardViewModel.Factory(activity.getApplication(), repository)).get(StickerKeyboardViewModel.class);