From 40f8bc87a22889632bfd3500449c68f4034faf96 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 5 Mar 2018 09:43:40 +0100 Subject: [PATCH 01/28] Bump dependencies --- app/build.gradle | 26 +++++++++--------- .../dissem/apps/abit/AddressListFragment.kt | 14 +++++++--- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54708 -> 53837 bytes gradle/wrapper/gradle-wrapper.properties | 4 +-- gradlew | 6 ++-- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1d45f11..41b9321 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,7 +51,7 @@ android { //ext.jabitVersion = '2.0.4' ext.jabitVersion = 'feature-refactoring-SNAPSHOT' -ext.supportVersion = '27.0.2' +ext.supportVersion = '27.1.0' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" @@ -65,7 +65,7 @@ dependencies { implementation "com.android.support:support-v13:$supportVersion" implementation "com.android.support:preference-v14:$supportVersion" implementation "com.android.support:design:$supportVersion" - implementation "com.android.support:multidex:1.0.2" + implementation "com.android.support:multidex:1.0.3" implementation "ch.dissem.jabit:jabit-core:$jabitVersion" implementation "ch.dissem.jabit:jabit-networking:$jabitVersion" @@ -77,21 +77,21 @@ dependencies { implementation 'org.slf4j:slf4j-android:1.7.25' implementation 'com.mikepenz:materialize:1.1.2@aar' - implementation('com.mikepenz:materialdrawer:6.0.2@aar') { + implementation('com.mikepenz:materialdrawer:6.0.6@aar') { transitive = true } - implementation('com.mikepenz:aboutlibraries:6.0.2@aar') { + implementation('com.mikepenz:aboutlibraries:6.0.6@aar') { transitive = true } - implementation "com.mikepenz:iconics-core:3.0.0@aar" - implementation "com.mikepenz:iconics-views:3.0.0@aar" + implementation "com.mikepenz:iconics-core:3.0.3@aar" + implementation "com.mikepenz:iconics-views:3.0.3@aar" implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar' implementation 'com.mikepenz:community-material-typeface:2.0.46.1@aar' - implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar' - implementation 'com.google.zxing:core:3.3.1' + implementation 'com.journeyapps:zxing-android-embedded:3.6.0@aar' + implementation 'com.google.zxing:core:3.3.2' - implementation 'com.github.kobakei:MaterialFabSpeedDial:1.1.8' + implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.0' implementation 'com.github.amlcurran.showcaseview:library:5.4.3' implementation('com.github.h6ah4i:android-advancedrecyclerview:0.11.0@aar') { transitive = true @@ -100,14 +100,14 @@ dependencies { implementation 'com.android.support.constraint:constraint-layout:1.0.2' testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.13.0' + testImplementation 'org.mockito:mockito-core:2.15.0' testImplementation 'org.hamcrest:hamcrest-library:1.3' testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0' testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - testImplementation 'org.robolectric:robolectric:3.6.1' - testImplementation "org.robolectric:shadows-multidex:3.6.1" + testImplementation 'org.robolectric:robolectric:3.7.1' + testImplementation "org.robolectric:shadows-multidex:3.7.1" - androidTestImplementation "com.android.support:multidex:1.0.2" + androidTestImplementation "com.android.support:multidex:1.0.3" } idea.module { diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt index 80db0e4..e7dc2db 100644 --- a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt @@ -48,7 +48,8 @@ class AddressListFragment : AbstractItemListFragment() activity, R.layout.subscription_row, R.id.name, - LinkedList()) { + LinkedList() + ) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val result: View val v: ViewHolder @@ -72,7 +73,8 @@ class AddressListFragment : AbstractItemListFragment() v.avatar.setImageDrawable(Identicon(item)) v.name.text = item.toString() v.streamNumber.text = v.ctx.getString(R.string.stream_number, item.stream) - v.subscribed.visibility = if (item.isSubscribed) View.VISIBLE else View.INVISIBLE + v.subscribed.visibility = + if (item.isSubscribed) View.VISIBLE else View.INVISIBLE } return result } @@ -109,7 +111,7 @@ class AddressListFragment : AbstractItemListFragment() .addOnMenuItemClickListener { _, _, itemId -> when (itemId) { 1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment) - .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) + .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE) .initiateScan() 2 -> { val intent = Intent(getActivity(), CreateAddressActivity::class.java) @@ -121,7 +123,11 @@ class AddressListFragment : AbstractItemListFragment() } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_address_list, container, false) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/build.gradle b/build.gradle index 9232626..bcaecb5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.21' + ext.kotlin_version = '1.2.30' ext.anko_version = '0.10.4' repositories { jcenter() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee94c0ab25cf079ac8ccdf87f41d455d42..9352c13aa2acc870c14aa9cb6bef4567a02fb07b 100644 GIT binary patch delta 46271 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

QSaV|LU_x6Q z#O5U`F{AJ>$LjLLJLG?Y^~-Gx&F3#zRpJrTWB}N{Y@2m8m?e;K8t@@74B|;(GE%Uy z>?~GuA<~GUTR=AzAbq>3yX?E>&cS40r-#27+WxBG*z;slo=U?||J0Momd|!N7u(yz z`tUSJn1`(q3_K=1e~K1+Y_nlb&Tlic=zI26478?Db)di9VVUD^lBDq~Hf*zM5*&&0 zF+k$9?lANmfaV+iSV5WokMD1Nnmm`3k=II)2&bL#N}l!%_lfKD-osRVSh>e+2!(M6JF@b^MiU1QWK%8hyE+u$u}`eY~pe}6o{4g>VRV3w*l)_A|iJ=83uk7~-HlR0ir{!QyL z{sfw4e_^rrE6H>GKxw+~YW^B1!k``gq*-#i(swUOP`}gh`yrtEx--ZBN8>&Z0u@`q z5b>On6FckiPz%$)qQ(CwI7XT3syDd#EAh|aR0zr#b1D7f$(omJtfOXQf?Dh$2NgiL zQPr39Orc}JIs#I@g$S2T(>89!9B+M(W4L}HXjO2bWe{mPJsF-s8^o8;w_)l?aGue4 zskrPD5J$Xy)6}Hm_Gfo_%JMxxj%Ip&d)1tWH2kl^-v ztga-Kd#FC;}c&D&viOT4*eRKCO>`tK2pu)H-1G!?9RfWteS~RM*r~lgF+8 z=jpedFf07}7R5W33Grv>0fqJ7*ktLs@5s(lv0RnAqryCu_(?G@|C2|K`M-(kH7vx- z;D2G+^7oGl>p#QZNSTCyU-q=m-D9L<@tU!zzC z>^HrFJ%9HQ_T!Xz+mK9z&oa(y);uC58i{0^sETGf%@r~n+Kt(cb3tBzbS#!s zpG3Ypr2yDv8Tw0*DfSE#e!COMnB3D7XxFyH5tU z{Zb74fiN-CK$kXveR|9r?f~JS5?_0T#Fi`R?!x_E8815<#ri9da^+)34{_5yOWDf4$1+2~&$7mqyu05s{ z)I(wvO|a6x%Ms)^X$!mh-@PKu>55k6h+*X~Hv8<5(d>8Z<$RFLrqC`8%4`N%wwDtZ zEfSPPv}Y2Kii4R}aOZ1}!p7OW=90bUR{HW=Dp2={_9@p{Ro;uDIOdPScrI&vb9RF- z{*@$#^6xSREU2Gcn}5+^6EBc03s6SpcfTdaA%+Nf77 zsNGg?IwtcMNpke-3N)I|rn3t!knf)a*U0=tXTmpx1R}9{^9@4z1q2-Hm2Al|RNEP@~ExC;QS@CcDL|lKBokDAH#80W(R-fB^tE)Rn zC^Or1mfrknp4rzVjuX|M=g=BX|76NDf0<$2k`e*?_QBP2f26tz-+rX6{LYK~ zLz-jejYGcrAq)5xjrquP_qZKCgZ&~sN0u>u*e}1u%V8AzQlL=BkQbs?HhJ=#Z zIWaGmJ(tS(fC8qxH7z$EBI7qDO(MQ`Ifmjbb@XK4%>0(UbNs=QVz%nJ5#(x-JEJC+ zpK(M*QW+@=#9u$AcGz7#Lq}2HZOt9|vETlSTDrs%0ixDfrK-%~Tl!Q)ez*+|4{H3~HC zQdSm|v^#V+nu{q5ByV-x=`b0{3)Pis6_oOqjA`++BN4fM0-!rH1_yA@DWu8#+?e19-L}+5 z5u3$^z3N(_2=-{iGgpgmc4Ho{v+Z6A54(bOEuV$VMsL?~4AiyLYhom3eJ~-L1=+vn zrvpo7liTJiuu*iaF!)Ttw+Gd6T!cDeh<+QMsJ3T z)tK;OG5LUnF|tkh3M#UjdjVw@jh{uUg~!D$^?|KIlBbLTN_JVygkX_?rIW!{4Z1(q zdcBDwH^hjh{U@YH)N=o>rGtrO^sIS1d}#+PX39R!TX1S0XFjhvge$;>i5POM|98@b zQo|bl8aBKUc7vdW_lwXwK~rNEZKVT z)c5r{hyYPP$7d=C-Wq_Bislt8?&z-w*1y$#`PRQEjg9R0*j0_ZyHlnJ7xC&PO_5qW zBBlOjUafEbnhB%a*W|m(=_1cHMwuL5D`8kKg#QhzP(<9+R-fp;!b1hF?vk51uAZNs zY_&2lF>-pYAQ2ruOn@pmXDmO}!2M?>VwWC#Y-JX;IvnA6<01}#5|I(0!+}#N4%d8) zTccn*1YO^|vmZYO6`=+LgC1dsGCfdXPUa73VHQO{j+nS$FB~Vk?*-k&5YvU_y15ym zOQ#t#rNa*HP90trhsnVdUEMD3~vg(Jw`` zvd$LY^GvT9z&q6mOD1Qtw2%{1iJt~g=*xoqCEVw+*w+-@t$yNdR?TMq#F|eaziQ+U zz088}u|D|$NP-D&=kSXE+yBetc_#3d8%7A9^BRnQ3zp0&iuZ&X-pP-O{c?8`mmp6v zB}|eRM50C#mu{o{hVT*%s5x>hQ1bSR& zBD+6*{SXp3CKD+*&c&=AMa$lqgsIK7hh0*!;qwBxtvtlt9$2<{=kC(p*L~KAbl6`9 zwWOfiFXSA&En|(m+L?x*&BWavd;?uO>W~|!-y}; zWQ04LX8Cf=Kil|__b0Sz$z8>hiS7;~V`B{Be&BWH-|ReU z)vpP{6n!9#`zX09|4=g~(YV`!V#+u1J; z4^=dN!F8A1$mWeK#4*r4*`IdRF(Pzn+9^EHHrp}KEk2w1K`Ib%WGFpkv(e6$T!G0- zm^qbE1?Y)vy@2{#^-^^c9|jB+d-wpfQtkX+COOG-|IZVmtY6n{I8_|ANQJ){KVtkw$6oG_}&=shcB; z2mN7axb(im-db?4wrH}0-dKLZKKeo%otnwLAdKI*#XEv}_;%Os0iiV{qRCsU~PTpFk)qK<<^~|NGbt=t<8eqb+LX&hcq+@lEIKE|WQP&iT&skr`6QWL2lDtg@kB|RnF(+K2aBS6_LnEA z4w9Cbb>!1BE>rMEuDjR6JxFun!NR4@fj2nZ_MN>ElVU>SxyQIShV%(OL+44F5rf|Z zMrUQWG9|K4vugX}BilTN$v{JWZ&HzHDI<~gxU{C}^?z1g*AmJAiD2hM4bim}0dlEl z39J3RM^;Er1G$S3R1SLp;z<^_KU4t}*;30BIc{Q-wyoA0$xd_)b7x->g4hL)4CeYf zzi}4xjcuX2aAR{~cgbu#^BXNo!H$|sY;Sb9IifYW-RpHP4-x^w4 z_r00?7^t|suo>`3702Tb2#C#o`AEt`84PHO%QnOf&2j%E@=6m|c$x>d3|Jvk`d$#|r1=@-=qo-P$ zsplRsP3624!Q=Jx9{gRaNA-JSb`e2N07=>wsjLM%!Ow!@muG0h$I7LR+JnIBfzOV%=%%*262M?zyrN+Ba?^%ifsZY`fJM zCSAIcu&nkCpv-|iNtdy$2p{AjESO30r$Wb|XIasLD5viilNF;{Zn~+5QGD@`!2wqC zB7WXr4P5s?RcoU)rR<`4X^|GA?b`8iKa~NW99^!gep}n)yHOfjn!ZRunr>6c>9yn? z?VFWT@Id@{L@`w<*!CoQYSRq;fCs@31*|zv9mRENz;Jiwf{ZVEL3wGUJtesGauoJ? zPH|~`mo$l2yxR3Jb8@!+3-I83lM5maaY@0)XV%}%6HZ~?CX~tSW z(2yt-fQetmV9;G~GN;;T0)BHS)dY@xzT*Gm>KvOh3%7OMNhclKwr$()*tTuF9ox2T z+v?ah-`M8qRke53+3VZ<0kg(<>c%xRPmJcM;@A`M3rD4nIGw)EHTFW)s*kG>oIc%Z zlk|!y1WN@Wtg8`HKEq>-CS_bdU_YKX@1WL_Cix%m3rgc06JMWirkM_pX}`!GNji00 zc*AMQmkh*XwECW4-$Wj&qS;`Wh&^kP+5xfgoi?1GbZs3F1qem`$q50KpVPE=;&p8Z zv{&v1j&OVu6gKGBg3jQV=t7YoTJsv)DxL)+mle0Do)Md&abaRP;LY0+CgMxS{Y}N5 zO^&$`itmjPLRuq>DwaZ!aurQTChfb@Vgl8iK6hIUdy-jD1SlW-feIx*n~(FW{Q*FR zk!iajdE0^M@E&uK`_n(_)(rF`-@tyAJ~tU?s8&LDa46rf1N%9cI?nc=%YP$Fob?J) z>11@B`@vWL)*{S}r`&;XLl|g4#OW-7%2pEOIcHKUTX0EM8tx-zL!@TupVzQfSAi3+ zx%H6qZTzksJ_a>h_`yzILj+)uWd^iE$}STsphZRs?6$Vs#O1)XRR{`4zYAqx-Ea|& z5V8fV8D=o?^0^*9jD`N}87MhuLRZQg;5K|`KFaJuNKo=%`n3rH5X38z(GeCvz@e+# zp=FPB(_1r9?mV4uY*voo4Pa**we*dK*r1o8$7?U*X*LP;FpOi*n2_5mRR^Hz9s9*- zY00*U9j$LFWE##UL=^F98%QG&+F0)$r6}gW-l4Zn&1reunFX!16qe6+fP#ofiU}kz z{rOE9>;@C(bROr#%}gGUcP+NM6aCxkhgP;=*aw)v?oqA#$~Wb6JBN!M~8Y+ukiLj;GX#g)-}psxK8(2C&{Za5OxOxiygdNHCoD4nUPS}!6;o`7looWvXNi_^WE0Q*=VRh!%jGcQpcyB0aC@=4DFF zv{j;a6=A~v-DUKGSeDQ9@)b-zs}It3yJ1JNo+Dn~rZzDpARnxxXZPvS_nyq%C z8?|R$w?WwO^&H?D552Pi#uzvZa7fBZ%{=@r_;%BnO`Sw1FXSukwhnN1-y;=aX+I8!j@oN z)uL9=n{2&%*AZ=9!SHCwLf}#jX}a@7D@sAa8*km$LVzdmmAGl8@RN2OXVcS#WIa>x zo2!W<(C(-ANvGcT_kSr#jDO^;Z2#rTJ(2#SBqhQx5+}+xLjukLsOD&XW(?_5SBMF= zl9E~{DaZ|LHesOD?W}CS5rvVdslxNM@EQiq92y!Yr^V;Bv@4s3m$jQQ&OWilR8AE7 z{?1Hre7Acl$%iSP~A>+0r{M#?{n_EfLUvwqk!)h^B)95-cl|DarH@} zVB#!ylw*vEVSvue1`nMCL$bLGD{89MvXZ)}neb6lEE?tG28ua2693fUw`4Fqr1Q#w{fL_nE9lyM>XurVKeb~oUZ6XY z1rR&3d)gi%t3KMiJtX+{e?623+!}4p&B~ojOh;NClmPJ=W07YK+#1cU#tzbSv62=! zbCG0AHCh=#%Kfc{SIfU;hJ`Q;Oy-*+0>xzLFxPhj@)7vUwa}^^7In0gWP+u-i?ZG1 zaj{68WY*@XwZ`03lXv%6GUO5^0yE-%;t|SdGp9BOAS@Q>w$Qn5QmdtgDTrC)O&94Q z&3bc`u><<;u$N>y_{j5Fw9OsnmuVln@}nQ7q{HyoU^2pTPUX~7r>&Qj_p^f@irVwA z8!L(r3cup#QSd5um2T};GAC9JoJvd40cKFxkK>M86|>UZ6n5;i-LurVTMf1rT~m=(#cB#X z4bkYnQ6;X5k5*Sv8*-#!b_d1D8RqIOHKs8cA^CKiJ}}ppA(~l>DQtt+{V@*4Q0=oA znE+CLL_af+Gax&n>V(}n-lE##g)xqqM28yRgMP#(+jVU%VwdecFN#ZAnwmrr5~@X%XyC|!!v zAT7;936cIEPaIvl4(cy*e0jTw@RA+|pMc9Y({h>s18{FlL^5-fIKub}h4i=#Of~}eTstwScp=%B}Z~;fI zECJa{D$!ahqx1|G_y{~(Y0TYx$=kAHO)TJ-pp6U{7#wPGx7y&ngKXd&4)XnSI@YAE z{#;OVRb`Pb2T}XD%fS|zlSL%6tQ35ol2c51M|Fn}>Ex~O+}b>9PVD`LSIAOfq&zFR ze7639o4Lpvpb>pJs$(Kqr@ovUkbvznsOY@Y086^^Axvn?jnW+glh28QlnJf!I{ue{ zK%0978KZvMwF>%%9i)Un!a6>i1m@dgN0?$p_i` z7{NGGk=Urn0HHgQ&mV90j$VPkpZReA`S^xZ(m^l|yJIADC4x@RplRm;#5uVmrS}G& z&<#~f%!IhO!ls0b*cya(?pbzUP?jg@A7YhN3(Kyn9 zuq@;)!b!(oUs#aN3O`Sroh{4`?avcS0V`75V4I@JDU@yfj-4*}{h&IDCcO+CleHBJg4 zao2Hq5ek_@XbKgodM;xaUp8^MEo`Rr23DR2mE7W+lz;jq@qu6DtkAUyy0X+qYes3m z?9qZxN-oG+yXG6f&g>kZeEcC96xb%NH_wE~@z#bSA9}tEF*lMo3Q4hta{V(CGBlg; zqWryY`XI|)M7?bU3;?#Fyz+Ca8Q&szy8elvJHq4Q?X3G-wDx_-opUkUlx`+roa=(j zhQWV$4r|?oh0xJpu{}o%(boez7k061)@PjJ9!vTo%u^GfzWT3iWWf;9#xW45ReA>) z{I}xL6|N(>k~bJ4?%}<@6ZC6mn-bOirwSE&{^d+RX9GamtD5T2#GJ(_0lz9nXYvLy z5HaBD>p!A9?qa?F*r5Yob$0jc&=ow~OCb@>;-d|&jzg?z8lxDNbTjuM${78bfKf1% zUSVX{>uvxDk973m6nAn}f>=zA*w$ewE!hO>(y0b#`DwA6nYBwHqqFHqoX ziPiv|LH~_z2pCfb~M{R27>Tk zB)9kve9Zrw?l|&T)Eob2p+`d{<`G~e%7Nelw6#$rF#T3+8tQ5SkY@7+sEMIMMXQ_9 zXjPRJN%P18qF2|+uo9N0=(l8fA50KhKH;{XN?ISuSML`2yDxiB5Ki|pw`fU;-dHgj zS$2F%pFfW?$8x_v-jMsKKJtho+*NUh!ND=mbd=^on2hp!uBd65Y1F|2D2~$ z%y?-S3Bm9>>JB3PZ4vZcwFZh}=p1OYm*NO1Xq#t!5CgjMu&u*&^Zq>4{6iAZS8lt; zOl&H+{S^USL4Q3L*k7B1e)HDT3d!?;ljqYIuiNBn>6u>187~n_u^t@4S+6M_$GBV&!yl|;jJ6psX*qPJH>sfZBGRNe@%6_xS@1Eb zCh8)~Y2D>IIgeyyDcFp)Y{n->9U@T;0XU{m6$Ez`$|X8a>^L$f5AL;C^rpG)Hq-dG zO{OZ;SfiAT$Rj>5nYWAcMkR(an5k6O3Z#bmA~p)7!jYezBQo+r!hpbliDg6*m5Ee$bWe?cj z*TIyue2=OJrQDan@DR{~{5a%h1cw#e-q^$`6EutxJ?iaN(H_+3IyDJD1#9`#f=a{* z#wRw?7)z6toQXqqGi$ZyN_4;ho4U4mAD#ycl&q~oj^6owlS!6}wFu>Xr>f2gz04

Jx`H9E%LU}b<@nCq3}Z~Lq$ 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 delta 47283 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4eb3cdf..cefb6bf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 23 08:19:50 CEST 2017 +#Sat Mar 03 14:35:52 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/gradlew b/gradlew index cccdd3d..4453cce 100755 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn () { +warn ( ) { echo "$*" } -die () { +die ( ) { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save () { +save ( ) { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } From 101913a53160bdb5930a9051c5cfa4fb1662be5d Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 5 Mar 2018 09:48:49 +0100 Subject: [PATCH 02/28] Show messages in inbox and archive as conversations Work in progress - detail view not yet adapted, and needs extended encoding for sensible results. --- .../apps/abit/ConversationListFragment.kt | 341 ++++++++++++++++++ .../java/ch/dissem/apps/abit/Identicon.kt | 104 +++++- .../java/ch/dissem/apps/abit/MainActivity.kt | 10 +- .../dissem/apps/abit/MessageListFragment.kt | 4 +- .../adapter/SwipeableConversationAdapter.kt | 262 ++++++++++++++ .../repository/AndroidMessageRepository.kt | 41 ++- 6 files changed, 737 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt create mode 100644 app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt new file mode 100644 index 0000000..38dbf26 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt @@ -0,0 +1,341 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.apps.abit + + +import android.content.Intent +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.content.ContextCompat +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.RecyclerView.OnScrollListener +import android.view.* +import android.widget.Toast +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY +import ch.dissem.apps.abit.adapter.SwipeableConversationAdapter +import ch.dissem.apps.abit.listener.ListSelectionListener +import ch.dissem.apps.abit.repository.AndroidMessageRepository +import ch.dissem.apps.abit.service.Singleton +import ch.dissem.apps.abit.service.Singleton.currentLabel +import ch.dissem.apps.abit.util.FabUtils +import ch.dissem.bitmessage.entity.Conversation +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.utils.ConversationService +import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator +import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator +import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager +import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils +import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu +import kotlinx.android.synthetic.main.fragment_message_list.* +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.support.v4.onUiThread +import org.jetbrains.anko.uiThread +import java.util.* + +private const val PAGE_SIZE = 15 + +/** + * A list fragment representing a list of Messages. This fragment + * also supports tablet devices by allowing list items to be given an + * 'activated' state upon selection. This helps indicate which item is + * currently being viewed in a [MessageDetailFragment]. + * + * + * Activities containing this fragment MUST implement the [ListSelectionListener] + * interface. + */ +class ConversationListFragment : Fragment(), ListHolder

+ ) : RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder>() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RelatedMessageAdapter.ViewHolder { + val context = parent.context + val inflater = LayoutInflater.from(context) + + // Inflate the custom layout + val contactView = inflater.inflate(R.layout.item_message_minimized, parent, false) + + // Return a new holder instance + return ViewHolder(contactView) + } + + // Involves populating data into the item through holder + override fun onBindViewHolder(viewHolder: RelatedMessageAdapter.ViewHolder, position: Int) { + // Get the data model based on position + val message = messages[position] + + viewHolder.avatar.setImageDrawable(Identicon(message.from)) + viewHolder.status.setImageResource(Assets.getStatusDrawable(message.status)) + viewHolder.sender.text = message.from.toString() + viewHolder.extract.text = prepareMessageExtract(message.text) + viewHolder.item = message + } + + // Returns the total count of items in the list + override fun getItemCount() = messages.size + + internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + internal val avatar = itemView.findViewById<ImageView>(R.id.avatar) + internal val status = itemView.findViewById<ImageView>(R.id.status) + internal val sender = itemView.findViewById<TextView>(R.id.sender) + internal val extract = itemView.findViewById<TextView>(R.id.text) + internal var item: Plaintext? = null + + init { + itemView.setOnClickListener { + if (ctx is MainActivity) { + item?.let { ctx.onItemSelected(it) } + } else { + val detailIntent = Intent(ctx, MessageDetailActivity::class.java) + detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item) + ctx.startActivity(detailIntent) + } + } + } + } + } + + companion object { + /** + * The fragment argument representing the item ID that this fragment + * represents. + */ + const val ARG_ITEM = "item" + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt index 38dbf26..c5a49c7 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt @@ -203,9 +203,7 @@ class ConversationListFragment : Fragment(), ListHolder<Label> { val position = recycler_view.getChildAdapterPosition(v) adapter.setSelectedPosition(position) if (position != RecyclerView.NO_POSITION) { - adapter.getItem(position).messages.firstOrNull()?.let { - MainActivity.apply { onItemSelected(it) } - } + MainActivity.apply { onItemSelected(adapter.getItem(position)) } } } } diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt index 4709b1b..1ec06c9 100644 --- a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt +++ b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt @@ -124,7 +124,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundCol color = backgroundColor } - val identicons = input.map { Identicon(it) }.take(4) + private val identicons = input.map { Identicon(it) }.take(4) override fun draw(canvas: Canvas) { val width = canvas.width.toFloat() @@ -132,7 +132,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundCol when (identicons.size) { 0 -> canvas.drawCircle(width / 2, height / 2, width / 2, paint) - 1 -> identicons.first().draw(canvas) + 1 -> identicons.first().draw(canvas, 0f, 0f, width, height) 2 -> { canvas.drawCircle(width / 2, height / 2, width / 2, paint) val w = width / 2 diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt index 7277e88..3a17c41 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt @@ -32,9 +32,13 @@ import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARC import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton.currentLabel import ch.dissem.apps.abit.synchronization.SyncAdapter -import ch.dissem.apps.abit.util.* +import ch.dissem.apps.abit.util.NetworkUtils +import ch.dissem.apps.abit.util.Preferences +import ch.dissem.apps.abit.util.getColor +import ch.dissem.apps.abit.util.getIcon import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label import com.github.amlcurran.showcaseview.ShowcaseView @@ -458,6 +462,13 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { // adding or replacing the detail fragment using a // fragment transaction. val fragment = when (item) { + is Conversation -> { + ConversationDetailFragment().apply { + arguments = Bundle().apply { + putSerializable(ConversationDetailFragment.ARG_ITEM, item) + } + } + } is Plaintext -> { if (item.labels.any { it.type == Label.Type.DRAFT }) { ComposeMessageFragment().apply { @@ -489,6 +500,11 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { // In single-pane mode, simply start the detail activity // for the selected item ID. val detailIntent = when (item) { + is Conversation -> { + Intent(this, MessageDetailActivity::class.java).apply { + putExtra(ConversationDetailFragment.ARG_ITEM, item) + } + } is Plaintext -> { if (item.labels.any { it.type == Label.Type.DRAFT }) { Intent(this, ComposeMessageActivity::class.java).apply { diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt index 62d2cec..bb29b16 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.os.Bundle import android.support.v4.app.NavUtils import android.view.MenuItem +import ch.dissem.bitmessage.entity.Conversation /** @@ -33,13 +34,17 @@ class MessageDetailActivity : DetailActivity() { // Create the detail fragment and add it to the activity // using a fragment transaction. val arguments = Bundle() - arguments.putSerializable(MessageDetailFragment.ARG_ITEM, - intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM)) - val fragment = MessageDetailFragment() + val item = intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM) + arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item) + val fragment = if (item is Conversation) { + ConversationDetailFragment() + } else { + MessageDetailFragment() + } fragment.arguments = arguments supportFragmentManager.beginTransaction() - .add(R.id.content, fragment) - .commit() + .add(R.id.content, fragment) + .commit() } } diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt new file mode 100644 index 0000000..b2edc00 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt @@ -0,0 +1,152 @@ +package ch.dissem.apps.abit.adapter + +import android.content.Context +import android.support.v4.app.Fragment +import android.support.v7.widget.GridLayoutManager +import android.support.v7.widget.PopupMenu +import android.support.v7.widget.RecyclerView +import android.text.util.Linkify +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import ch.dissem.apps.abit.* +import ch.dissem.apps.abit.service.Singleton +import ch.dissem.apps.abit.util.Assets +import ch.dissem.apps.abit.util.Constants +import ch.dissem.bitmessage.entity.Conversation +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.ports.MessageRepository + + +class ConversationAdapter internal constructor( + ctx: Context, + private val parent: Fragment, + private val conversation: Conversation +) : RecyclerView.Adapter<ConversationAdapter.ViewHolder>() { + + private val messageRepo = Singleton.getMessageRepository(ctx) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ConversationAdapter.ViewHolder { + val context = parent.context + val inflater = LayoutInflater.from(context) + + // Inflate the custom layout + val messageView = inflater.inflate(R.layout.item_message_detail, parent, false) + + // Return a new holder instance + return ViewHolder(messageView, this.parent, messageRepo) + } + + // Involves populating data into the item through holder + override fun onBindViewHolder(viewHolder: ConversationAdapter.ViewHolder, position: Int) { + // Get the data model based on position + val message = conversation.messages[position] + + viewHolder.apply { + item = message + avatar.setImageDrawable(Identicon(message.from)) + sender.text = message.from.toString() + val senderClickListener: (View) -> Unit = { + MainActivity.apply { + onItemSelected(message.from) + } + } + avatar.setOnClickListener(senderClickListener) + sender.setOnClickListener(senderClickListener) + + recipient.text = message.to.toString() + status.setImageResource(Assets.getStatusDrawable(message.status)) + text.text = message.text + + Linkify.addLinks(text, Linkify.WEB_URLS) + Linkify.addLinks(text, + Constants.BITMESSAGE_ADDRESS_PATTERN, + Constants.BITMESSAGE_URL_SCHEMA, null, + Linkify.TransformFilter { match, _ -> match.group() } + ) + + labelAdapter.labels = message.labels.toList() + + // FIXME: I think that's not quite correct + if (message.isUnread()) { + Singleton.labeler.markAsRead(message) + messageRepo.save(message) + MainActivity.apply { updateUnread() } + } + + } + } + + override fun getItemCount() = conversation.messages.size + + class ViewHolder( + itemView: View, + parent: Fragment, + messageRepo: MessageRepository + ) : RecyclerView.ViewHolder(itemView) { + var item: Plaintext? = null + val avatar = itemView.findViewById<ImageView>(R.id.avatar)!! + val sender = itemView.findViewById<TextView>(R.id.sender)!! + val recipient = itemView.findViewById<TextView>(R.id.recipient)!! + val status = itemView.findViewById<ImageView>(R.id.status)!! + val menu = itemView.findViewById<ImageView>(R.id.menu)!!.also { view -> + view.setOnClickListener { + val popup = PopupMenu(itemView.context, view) + popup.menuInflater.inflate(R.menu.message, popup.menu) + popup.setOnMenuItemClickListener { + item?.let { item -> + when (it.itemId) { + R.id.reply -> { + ComposeMessageActivity.launchReplyTo(parent, item) + true + } + R.id.delete -> { + if (MessageDetailFragment.isInTrash(item)) { + Singleton.labeler.delete(item) + messageRepo.remove(item) + } else { + Singleton.labeler.delete(item) + messageRepo.save(item) + } + MainActivity.apply { + updateUnread() + onBackPressed() + } + true + } + R.id.mark_unread -> { + Singleton.labeler.markAsUnread(item) + messageRepo.save(item) + MainActivity.apply { updateUnread() } + true + } + R.id.archive -> { + Singleton.labeler.archive(item) + messageRepo.save(item) + MainActivity.apply { updateUnread() } + true + } + else -> false + } + } ?: false + } + popup.show() + } + } + val text = itemView.findViewById<TextView>(R.id.text)!!.apply { + linksClickable = true + setTextIsSelectable(true) + } + val labelAdapter = LabelAdapter(itemView.context, emptySet<Label>()) + val labels = itemView.findViewById<RecyclerView>(R.id.labels)!!.apply { + adapter = labelAdapter + layoutManager = GridLayoutManager(itemView.context, 2) + } + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt index cf0a41f..9d89902 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt @@ -46,15 +46,14 @@ class LabelAdapter internal constructor(private val ctx: Context, labels: Collec override fun getItemCount() = labels.size class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val background = itemView var icon = itemView.findViewById<IconicsImageView>(R.id.icon)!! var label = itemView.findViewById<TextView>(R.id.label)!! fun setBackground(@ColorInt color: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - background.backgroundTintList = ColorStateList.valueOf(color) + itemView.backgroundTintList = ColorStateList.valueOf(color) } else { - background.backgroundColor = color + itemView.backgroundColor = color } } } diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000..7b81bbb --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000" + android:pathData="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z" /> +</vector> diff --git a/app/src/main/res/layout/fragment_conversation_detail.xml b/app/src/main/res/layout/fragment_conversation_detail.xml new file mode 100644 index 0000000..64a8b25 --- /dev/null +++ b/app/src/main/res/layout/fragment_conversation_detail.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true" + android:focusableInTouchMode="true" + android:orientation="vertical" + android:paddingBottom="64dp"> + + <TextView + android:id="@+id/subject" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_toStartOf="@+id/avatar" + android:elegantTextHeight="false" + android:enabled="false" + android:gravity="center_vertical" + android:padding="16dp" + android:textAppearance="?android:attr/textAppearanceLarge" + tools:ignore="UnusedAttribute" + tools:text="Subject" /> + + <ImageView + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentEnd="true" + android:layout_alignParentTop="true" + android:layout_margin="10dp" + android:src="@color/colorAccent" + tools:ignore="ContentDescription" /> + + + <View + android:id="@+id/divider" + android:layout_width="fill_parent" + android:layout_height="2dip" + android:layout_below="@id/subject" + android:background="@color/divider" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/messages" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/divider" + android:animateLayoutChanges="true" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp"/> + + </RelativeLayout> +</ScrollView> diff --git a/app/src/main/res/layout/item_message_detail.xml b/app/src/main/res/layout/item_message_detail.xml new file mode 100644 index 0000000..203903b --- /dev/null +++ b/app/src/main/res/layout/item_message_detail.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true" + android:focusableInTouchMode="true" + android:orientation="vertical"> + + <RelativeLayout + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginTop="8dp" + android:src="@color/colorAccent" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/sender" + android:layout_width="wrap_content" + android:layout_height="20dp" + android:layout_alignTop="@+id/avatar" + android:layout_toEndOf="@+id/avatar" + android:layout_toStartOf="@+id/status" + android:gravity="center_vertical" + android:paddingEnd="0dp" + android:paddingStart="8dp" + android:textStyle="bold" + tools:text="Sender" /> + + <TextView + android:id="@+id/recipient" + android:layout_width="wrap_content" + android:layout_height="20dp" + android:layout_alignBottom="@+id/avatar" + android:layout_toEndOf="@+id/avatar" + android:layout_toStartOf="@+id/status" + android:gravity="center_vertical" + android:paddingEnd="0dp" + android:paddingStart="8dp" + tools:text="Recipient" /> + + <ImageView + android:id="@+id/status" + android:layout_width="wrap_content" + android:layout_height="40dp" + android:layout_centerVertical="true" + android:layout_toStartOf="@+id/menu" + android:paddingBottom="8dp" + android:paddingTop="8dp" + android:tint="@color/colorAccent" + tools:ignore="ContentDescription" + tools:src="@drawable/ic_notification_proof_of_work" /> + + <ImageView + android:id="@+id/menu" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:contentDescription="@string/context_menu" + android:padding="8dp" + android:src="@drawable/ic_menu" /> + + </RelativeLayout> + + <LinearLayout + android:id="@+id/body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_marginTop="16dp" + android:textIsSelectable="true" + tools:text="Message Body" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/labels" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" /> + + </LinearLayout> + + <View + android:id="@+id/divider" + android:layout_width="fill_parent" + android:layout_height="2dip" + android:background="@color/divider" /> + +</LinearLayout> diff --git a/app/src/main/res/menu/conversation.xml b/app/src/main/res/menu/conversation.xml new file mode 100644 index 0000000..48cbb38 --- /dev/null +++ b/app/src/main/res/menu/conversation.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/delete" + app:showAsAction="ifRoom" + android:icon="@drawable/ic_action_delete" + android:title="@string/delete"/> + <item + android:id="@+id/archive" + app:showAsAction="ifRoom" + android:icon="@drawable/ic_action_archive" + android:title="@string/archive"/> +</menu> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e0747a..007bb84 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -136,4 +136,5 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="broadcasts">Broadcasts</string> <string name="encoding_simple">simple</string> <string name="encoding_extended">extended</string> + <string name="context_menu">actions</string> </resources> From 49e77199b06decb3a5b7c79abb55baed78e1953a Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 23 Mar 2018 17:50:43 +0100 Subject: [PATCH 05/28] Improve utilities --- .../dissem/apps/abit/AddressDetailFragment.kt | 3 +- .../dissem/apps/abit/AddressListFragment.kt | 3 +- .../apps/abit/ConversationDetailFragment.kt | 5 +- .../apps/abit/ConversationListFragment.kt | 3 +- .../java/ch/dissem/apps/abit/MainActivity.kt | 21 ++++ .../dissem/apps/abit/MessageDetailFragment.kt | 11 +- .../dissem/apps/abit/MessageListFragment.kt | 28 +++-- .../apps/abit/adapter/ConversationAdapter.kt | 4 +- .../abit/adapter/SwipeableMessageAdapter.kt | 46 +++++--- .../apps/abit/drawer/ProfileImageListener.kt | 4 +- .../notification/NewMessageNotification.kt | 14 +-- .../abit/repository/AndroidLabelRepository.kt | 20 +++- .../java/ch/dissem/apps/abit/util/Assets.kt | 43 ++++---- .../ch/dissem/apps/abit/util/Drawables.kt | 103 +++++++++--------- .../java/ch/dissem/apps/abit/util/FabUtils.kt | 31 ------ .../java/ch/dissem/apps/abit/util/Labels.kt | 28 ++--- .../java/ch/dissem/apps/abit/util/PowStats.kt | 7 +- 17 files changed, 194 insertions(+), 180 deletions(-) delete mode 100644 app/src/main/java/ch/dissem/apps/abit/util/FabUtils.kt diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt index af66c9e..6fc090c 100644 --- a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt @@ -26,6 +26,7 @@ import android.view.* import android.widget.Toast import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.util.Drawables +import ch.dissem.apps.abit.util.qrCode import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.wif.WifExporter import com.mikepenz.community_material_typeface_library.CommunityMaterial @@ -185,7 +186,7 @@ class AddressDetailFragment : Fragment() { } // QR code - qr_code.setImageBitmap(Drawables.qrCode(item)) + qr_code.setImageBitmap(item.qrCode()) } } diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt index e7dc2db..5f77d05 100644 --- a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt @@ -27,7 +27,6 @@ import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.TextView import ch.dissem.apps.abit.service.Singleton -import ch.dissem.apps.abit.util.FabUtils import ch.dissem.bitmessage.entity.BitmessageAddress import com.google.zxing.integration.android.IntentIntegrator import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu @@ -107,7 +106,7 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>() val menu = FabSpeedDialMenu(activity) menu.add(R.string.scan_qr_code).setIcon(R.drawable.ic_action_qr_code) menu.add(R.string.create_contact).setIcon(R.drawable.ic_action_create_contact) - FabUtils.initFab(activity, R.drawable.ic_action_add_contact, menu) + activity.initFab(R.drawable.ic_action_add_contact, menu) .addOnMenuItemClickListener { _, _, itemId -> when (itemId) { 1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment) diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt index f1e202b..3030c57 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt @@ -19,7 +19,6 @@ package ch.dissem.apps.abit import android.content.Context import android.content.Intent import android.os.Bundle -import android.support.annotation.IdRes import android.support.v4.app.Fragment import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView @@ -28,9 +27,9 @@ import android.widget.ImageView import android.widget.TextView import ch.dissem.apps.abit.adapter.ConversationAdapter import ch.dissem.apps.abit.service.Singleton -import ch.dissem.apps.abit.util.Assets import ch.dissem.apps.abit.util.Drawables import ch.dissem.apps.abit.util.Strings.prepareMessageExtract +import ch.dissem.apps.abit.util.getDrawable import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.Plaintext import com.mikepenz.google_material_typeface_library.GoogleMaterial @@ -148,7 +147,7 @@ class ConversationDetailFragment : Fragment() { val message = messages[position] viewHolder.avatar.setImageDrawable(Identicon(message.from)) - viewHolder.status.setImageResource(Assets.getStatusDrawable(message.status)) + viewHolder.status.setImageResource(message.status.getDrawable()) viewHolder.sender.text = message.from.toString() viewHolder.extract.text = prepareMessageExtract(message.text) viewHolder.item = message diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt index c5a49c7..80f7427 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt @@ -33,7 +33,6 @@ import ch.dissem.apps.abit.listener.ListSelectionListener import ch.dissem.apps.abit.repository.AndroidMessageRepository import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton.currentLabel -import ch.dissem.apps.abit.util.FabUtils import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.valueobject.Label import ch.dissem.bitmessage.utils.ConversationService @@ -248,7 +247,7 @@ class ConversationListFragment : Fragment(), ListHolder<Label> { val menu = FabSpeedDialMenu(context) menu.add(R.string.broadcast).setIcon(R.drawable.ic_action_broadcast) menu.add(R.string.personal_message).setIcon(R.drawable.ic_action_personal) - FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu) + context.initFab(R.drawable.ic_action_compose_message, menu) .addOnMenuItemClickListener { _, _, itemId -> val identity = Singleton.getIdentity(context) if (identity == null) { diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt index 3a17c41..5f7df5f 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt @@ -19,6 +19,7 @@ package ch.dissem.apps.abit import android.content.Intent import android.graphics.Point import android.os.Bundle +import android.support.annotation.DrawableRes import android.support.v4.app.Fragment import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar @@ -54,6 +55,7 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem import com.mikepenz.materialdrawer.model.interfaces.IProfile import com.mikepenz.materialdrawer.model.interfaces.Nameable import io.github.kobakei.materialfabspeeddial.FabSpeedDial +import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu import kotlinx.android.synthetic.main.activity_main.* import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread @@ -538,6 +540,25 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { supportActionBar?.title = title } + fun initFab(@DrawableRes drawableRes: Int, menu: FabSpeedDialMenu): FabSpeedDial { + val fab = floatingActionButton ?: throw IllegalStateException("Fab must not be null") + fab.removeAllOnMenuItemClickListeners() + fab.show() + fab.closeMenu() + val mainFab = fab.mainFab + mainFab.setImageResource(drawableRes) + fab.setMenu(menu) + fab.addOnStateChangeListener { isOpened: Boolean -> + if (isOpened) { + // It will be turned 45 degrees, which makes an x out of the + + mainFab.setImageResource(R.drawable.ic_action_add) + } else { + mainFab.setImageResource(drawableRes) + } + } + return fab + } + companion object { const val EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage" const val EXTRA_SHOW_LABEL = "ch.dissem.abit.ShowLabel" diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.kt index e9d9780..3f02bd8 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.kt @@ -31,16 +31,15 @@ import android.widget.ImageView import android.widget.TextView import ch.dissem.apps.abit.adapter.LabelAdapter import ch.dissem.apps.abit.service.Singleton -import ch.dissem.apps.abit.util.Assets import ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN import ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA import ch.dissem.apps.abit.util.Drawables -import ch.dissem.apps.abit.util.Labels import ch.dissem.apps.abit.util.Strings.prepareMessageExtract +import ch.dissem.apps.abit.util.getDrawable +import ch.dissem.apps.abit.util.getString import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.mikepenz.iconics.view.IconicsImageView import kotlinx.android.synthetic.main.fragment_message_detail.* import java.util.* @@ -86,8 +85,8 @@ class MessageDetailFragment : Fragment() { // Show the dummy content as text in a TextView. item?.let { item -> subject.text = item.subject - status.setImageResource(Assets.getStatusDrawable(item.status)) - status.contentDescription = getString(Assets.getStatusString(item.status)) + status.setImageResource(item.status.getDrawable()) + status.contentDescription = getString(item.status.getString()) avatar.setImageDrawable(Identicon(item.from)) val senderClickListener: (View) -> Unit = { MainActivity.apply { @@ -230,7 +229,7 @@ class MessageDetailFragment : Fragment() { val message = messages[position] viewHolder.avatar.setImageDrawable(Identicon(message.from)) - viewHolder.status.setImageResource(Assets.getStatusDrawable(message.status)) + viewHolder.status.setImageResource(message.status.getDrawable()) viewHolder.sender.text = message.from.toString() viewHolder.extract.text = prepareMessageExtract(message.text) viewHolder.item = message diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt index 11adc5e..f4eac98 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt @@ -33,7 +33,6 @@ import ch.dissem.apps.abit.listener.ListSelectionListener import ch.dissem.apps.abit.repository.AndroidMessageRepository import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton.currentLabel -import ch.dissem.apps.abit.util.FabUtils import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator @@ -98,7 +97,11 @@ class MessageListFragment : Fragment(), ListHolder<Label> { isLoading = true swipeableMessageAdapter?.let { messageAdapter -> doAsync { - val messages = messageRepo.findMessages(currentLabel.value, messageAdapter.itemCount, PAGE_SIZE) + val messages = messageRepo.findMessages( + currentLabel.value, + messageAdapter.itemCount, + PAGE_SIZE + ) onUiThread { messageAdapter.addAll(messages) isLoading = false @@ -149,7 +152,11 @@ class MessageListFragment : Fragment(), ListHolder<Label> { loadMoreItems() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_message_list, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -213,8 +220,11 @@ class MessageListFragment : Fragment(), ListHolder<Label> { recycler_view.itemAnimator = animator recycler_view.addOnScrollListener(recyclerViewOnScrollListener) - recycler_view.addItemDecoration(SimpleListDividerDecorator( - ContextCompat.getDrawable(context, R.drawable.list_divider_h), true)) + recycler_view.addItemDecoration( + SimpleListDividerDecorator( + ContextCompat.getDrawable(context, R.drawable.list_divider_h), true + ) + ) // NOTE: // The initialization order is very important! This order determines the priority of @@ -235,12 +245,14 @@ class MessageListFragment : Fragment(), ListHolder<Label> { val menu = FabSpeedDialMenu(context) menu.add(R.string.broadcast).setIcon(R.drawable.ic_action_broadcast) menu.add(R.string.personal_message).setIcon(R.drawable.ic_action_personal) - FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu) + context.initFab(R.drawable.ic_action_compose_message, menu) .addOnMenuItemClickListener { _, _, itemId -> val identity = Singleton.getIdentity(context) if (identity == null) { - Toast.makeText(activity, R.string.no_identity_warning, - Toast.LENGTH_LONG).show() + Toast.makeText( + activity, R.string.no_identity_warning, + Toast.LENGTH_LONG + ).show() } else { when (itemId) { 1 -> { diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt index b2edc00..c07ec0d 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt @@ -13,8 +13,8 @@ import android.widget.ImageView import android.widget.TextView import ch.dissem.apps.abit.* import ch.dissem.apps.abit.service.Singleton -import ch.dissem.apps.abit.util.Assets import ch.dissem.apps.abit.util.Constants +import ch.dissem.apps.abit.util.getDrawable import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label @@ -61,7 +61,7 @@ class ConversationAdapter internal constructor( sender.setOnClickListener(senderClickListener) recipient.text = message.to.toString() - status.setImageResource(Assets.getStatusDrawable(message.status)) + status.setImageResource(message.status.getDrawable()) text.text = message.text Linkify.addLinks(text, Linkify.WEB_URLS) diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableMessageAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableMessageAdapter.kt index 76804c0..f01e1b2 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableMessageAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableMessageAdapter.kt @@ -29,8 +29,9 @@ import android.widget.TextView import ch.dissem.apps.abit.Identicon import ch.dissem.apps.abit.R import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE -import ch.dissem.apps.abit.util.Assets import ch.dissem.apps.abit.util.Strings.prepareMessageExtract +import ch.dissem.apps.abit.util.getDrawable +import ch.dissem.apps.abit.util.getString import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter @@ -48,7 +49,8 @@ import java.util.* * @author Christian Basler * @see [https://github.com/h6ah4i/android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview) */ -class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.ViewHolder>(), SwipeableItemAdapter<SwipeableMessageAdapter.ViewHolder>, SwipeableItemConstants { +class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.ViewHolder>(), + SwipeableItemAdapter<SwipeableMessageAdapter.ViewHolder>, SwipeableItemConstants { private val data = LinkedList<Plaintext>() var eventListener: EventListener? = null @@ -84,7 +86,8 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie init { itemViewOnClickListener = View.OnClickListener { view -> onItemViewClick(view) } - swipeableViewContainerOnClickListener = View.OnClickListener { view -> onSwipeableViewContainerClick(view) } + swipeableViewContainerOnClickListener = + View.OnClickListener { view -> onSwipeableViewContainerClick(view) } // SwipeableItemAdapter requires stable ID, and also // have to implement the getItemId() method appropriately. @@ -134,7 +137,8 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie private fun onSwipeableViewContainerClick(v: View) { eventListener?.onItemViewClicked( - RecyclerViewAdapterUtils.getParentViewHolderItemView(v)) + RecyclerViewAdapterUtils.getParentViewHolderItemView(v) + ) } fun getItem(position: Int) = data[position] @@ -168,8 +172,8 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie // set data avatar.setImageDrawable(Identicon(item.from)) - status.setImageResource(Assets.getStatusDrawable(item.status)) - status.contentDescription = holder.status.context.getString(Assets.getStatusString(item.status)) + status.setImageResource(item.status.getDrawable()) + status.contentDescription = holder.status.context.getString(item.status.getString()) sender.text = item.from.toString() subject.text = prepareMessageExtract(item.subject) extract.text = prepareMessageExtract(item.text) @@ -194,16 +198,18 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie @SuppressLint("SwitchIntDef") override fun onSetSwipeBackground(holder: ViewHolder, position: Int, type: Int) = - holder.itemView.setBackgroundResource(when (type) { - DRAWABLE_SWIPE_NEUTRAL_BACKGROUND -> R.drawable.bg_swipe_item_neutral - DRAWABLE_SWIPE_LEFT_BACKGROUND -> R.drawable.bg_swipe_item_left - DRAWABLE_SWIPE_RIGHT_BACKGROUND -> if (label === LABEL_ARCHIVE || label?.type == Label.Type.TRASH) { - R.drawable.bg_swipe_item_neutral - } else { - R.drawable.bg_swipe_item_right + holder.itemView.setBackgroundResource( + when (type) { + DRAWABLE_SWIPE_NEUTRAL_BACKGROUND -> R.drawable.bg_swipe_item_neutral + DRAWABLE_SWIPE_LEFT_BACKGROUND -> R.drawable.bg_swipe_item_left + DRAWABLE_SWIPE_RIGHT_BACKGROUND -> if (label === LABEL_ARCHIVE || label?.type == Label.Type.TRASH) { + R.drawable.bg_swipe_item_neutral + } else { + R.drawable.bg_swipe_item_right + } + else -> R.drawable.bg_swipe_item_neutral } - else -> R.drawable.bg_swipe_item_neutral - }) + ) @SuppressLint("SwitchIntDef") override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int) = @@ -222,7 +228,10 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie notifyItemChanged(selectedPosition) } - private class SwipeLeftResultAction internal constructor(adapter: SwipeableMessageAdapter, position: Int) : SwipeResultActionMoveToSwipedDirection() { + private class SwipeLeftResultAction internal constructor( + adapter: SwipeableMessageAdapter, + position: Int + ) : SwipeResultActionMoveToSwipedDirection() { private var adapter: SwipeableMessageAdapter? = adapter private val item = adapter.data[position] @@ -235,7 +244,10 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie } } - private class SwipeRightResultAction internal constructor(adapter: SwipeableMessageAdapter, position: Int) : SwipeResultActionRemoveItem() { + private class SwipeRightResultAction internal constructor( + adapter: SwipeableMessageAdapter, + position: Int + ) : SwipeResultActionRemoveItem() { private var adapter: SwipeableMessageAdapter? = adapter private val item = adapter.data[position] diff --git a/app/src/main/java/ch/dissem/apps/abit/drawer/ProfileImageListener.kt b/app/src/main/java/ch/dissem/apps/abit/drawer/ProfileImageListener.kt index e5cf436..09d0d62 100644 --- a/app/src/main/java/ch/dissem/apps/abit/drawer/ProfileImageListener.kt +++ b/app/src/main/java/ch/dissem/apps/abit/drawer/ProfileImageListener.kt @@ -10,7 +10,7 @@ import android.view.WindowManager import android.widget.ImageView import android.widget.RelativeLayout import ch.dissem.apps.abit.service.Singleton -import ch.dissem.apps.abit.util.Drawables +import ch.dissem.apps.abit.util.qrCode import com.mikepenz.materialdrawer.AccountHeader import com.mikepenz.materialdrawer.model.interfaces.IProfile @@ -23,7 +23,7 @@ class ProfileImageListener(private val ctx: Context) : AccountHeader.OnAccountHe dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) val imageView = ImageView(ctx) - imageView.setImageBitmap(Drawables.qrCode(Singleton.getIdentity(ctx))) + imageView.setImageBitmap(Singleton.getIdentity(ctx)?.qrCode()) imageView.setOnClickListener { dialog.dismiss() } dialog.addContentView( imageView, diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt index 3328d26..b094011 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt @@ -17,6 +17,7 @@ package ch.dissem.apps.abit.notification import android.app.PendingIntent +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context import android.content.Intent import android.graphics.Typeface @@ -27,18 +28,15 @@ import android.text.Spannable import android.text.SpannableString import android.text.Spanned import android.text.style.StyleSpan - import ch.dissem.apps.abit.Identicon import ch.dissem.apps.abit.MainActivity -import ch.dissem.apps.abit.R -import ch.dissem.apps.abit.service.BitmessageIntentService -import ch.dissem.bitmessage.entity.Plaintext - -import android.app.PendingIntent.FLAG_UPDATE_CURRENT import ch.dissem.apps.abit.MainActivity.Companion.EXTRA_REPLY_TO_MESSAGE import ch.dissem.apps.abit.MainActivity.Companion.EXTRA_SHOW_MESSAGE +import ch.dissem.apps.abit.R +import ch.dissem.apps.abit.service.BitmessageIntentService import ch.dissem.apps.abit.service.BitmessageIntentService.Companion.EXTRA_DELETE_MESSAGE -import ch.dissem.apps.abit.util.Drawables.toBitmap +import ch.dissem.apps.abit.util.toBitmap +import ch.dissem.bitmessage.entity.Plaintext class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { @@ -53,7 +51,7 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) } builder.setSmallIcon(R.drawable.ic_notification_new_message) - .setLargeIcon(toBitmap(Identicon(plaintext.from), 192)) + .setLargeIcon(Identicon(plaintext.from).toBitmap(192)) .setContentTitle(plaintext.from.toString()) .setContentText(plaintext.subject) .setStyle(BigTextStyle().bigText(bigText)) diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidLabelRepository.kt b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidLabelRepository.kt index f9b000d..165a6e2 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidLabelRepository.kt +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidLabelRepository.kt @@ -20,7 +20,7 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import android.database.DatabaseUtils -import ch.dissem.apps.abit.util.Labels +import ch.dissem.apps.abit.util.getText import ch.dissem.bitmessage.entity.valueobject.Label import ch.dissem.bitmessage.ports.AbstractLabelRepository import ch.dissem.bitmessage.ports.MessageRepository @@ -30,7 +30,8 @@ import java.util.* /** * [MessageRepository] implementation using the Android SQL API. */ -class AndroidLabelRepository(private val sql: SqlHelper, private val context: Context) : AbstractLabelRepository() { +class AndroidLabelRepository(private val sql: SqlHelper, private val context: Context) : + AbstractLabelRepository() { override fun find(where: String): List<Label> { val result = LinkedList<Label>() @@ -62,7 +63,12 @@ class AndroidLabelRepository(private val sql: SqlHelper, private val context: Co db.update(TABLE_NAME, values, "id=?", arrayOf(label.id.toString())) } else { db.transaction { - val exists = DatabaseUtils.queryNumEntries(db, TABLE_NAME, "label=?", arrayOf(label.toString())) > 0 + val exists = DatabaseUtils.queryNumEntries( + db, + TABLE_NAME, + "label=?", + arrayOf(label.toString()) + ) > 0 if (exists) { val values = ContentValues() @@ -82,7 +88,8 @@ class AndroidLabelRepository(private val sql: SqlHelper, private val context: Co } } - internal fun findLabels(msgId: Any) = find("id IN (SELECT label_id FROM Message_Label WHERE message_id=$msgId)") + internal fun findLabels(msgId: Any) = + find("id IN (SELECT label_id FROM Message_Label WHERE message_id=$msgId)") companion object { val LABEL_ARCHIVE = Label("archive", null, 0).apply { id = Long.MAX_VALUE } @@ -97,11 +104,12 @@ class AndroidLabelRepository(private val sql: SqlHelper, private val context: Co internal fun getLabel(c: Cursor, context: Context): Label { val typeName = c.getString(c.getColumnIndex(COLUMN_TYPE)) val type = if (typeName == null) null else Label.Type.valueOf(typeName) - val text: String? = Labels.getText(type, null, context) + val text: String? = type?.getText(null, context) val label = Label( text ?: c.getString(c.getColumnIndex(COLUMN_LABEL)), type, - c.getInt(c.getColumnIndex(COLUMN_COLOR))) + c.getInt(c.getColumnIndex(COLUMN_COLOR)) + ) label.id = c.getLong(c.getColumnIndex(COLUMN_ID)) return label } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Assets.kt b/app/src/main/java/ch/dissem/apps/abit/util/Assets.kt index d234870..c09ef2b 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Assets.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Assets.kt @@ -43,28 +43,25 @@ object Assets { } catch (e: IOException) { throw RuntimeException(e) } - - } - - @DrawableRes - fun getStatusDrawable(status: Plaintext.Status) = when (status) { - Plaintext.Status.RECEIVED -> 0 - Plaintext.Status.DRAFT -> R.drawable.draft - Plaintext.Status.PUBKEY_REQUESTED -> R.drawable.public_key - Plaintext.Status.DOING_PROOF_OF_WORK -> R.drawable.ic_notification_proof_of_work - Plaintext.Status.SENT -> R.drawable.sent - Plaintext.Status.SENT_ACKNOWLEDGED -> R.drawable.sent_acknowledged - else -> 0 - } - - @StringRes - fun getStatusString(status: Plaintext.Status) = when (status) { - Plaintext.Status.RECEIVED -> R.string.status_received - Plaintext.Status.DRAFT -> R.string.status_draft - Plaintext.Status.PUBKEY_REQUESTED -> R.string.status_public_key - Plaintext.Status.DOING_PROOF_OF_WORK -> R.string.proof_of_work_title - Plaintext.Status.SENT -> R.string.status_sent - Plaintext.Status.SENT_ACKNOWLEDGED -> R.string.status_sent_acknowledged - else -> 0 } } + +fun Plaintext.Status.getDrawable() = when (this) { + Plaintext.Status.RECEIVED -> 0 + Plaintext.Status.DRAFT -> R.drawable.draft + Plaintext.Status.PUBKEY_REQUESTED -> R.drawable.public_key + Plaintext.Status.DOING_PROOF_OF_WORK -> R.drawable.ic_notification_proof_of_work + Plaintext.Status.SENT -> R.drawable.sent + Plaintext.Status.SENT_ACKNOWLEDGED -> R.drawable.sent_acknowledged + else -> 0 +} + +fun Plaintext.Status.getString() = when (this) { + Plaintext.Status.RECEIVED -> R.string.status_received + Plaintext.Status.DRAFT -> R.string.status_draft + Plaintext.Status.PUBKEY_REQUESTED -> R.string.status_public_key + Plaintext.Status.DOING_PROOF_OF_WORK -> R.string.proof_of_work_title + Plaintext.Status.SENT -> R.string.status_sent + Plaintext.Status.SENT_ACKNOWLEDGED -> R.string.status_sent_acknowledged + else -> 0 +} diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Drawables.kt b/app/src/main/java/ch/dissem/apps/abit/util/Drawables.kt index 5905cbf..a11747a 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Drawables.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Drawables.kt @@ -21,13 +21,14 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color.BLACK import android.graphics.Color.WHITE +import android.graphics.drawable.Drawable import android.util.Base64 import android.util.Base64.NO_WRAP import android.util.Base64.URL_SAFE import android.view.Menu import android.view.MenuItem -import ch.dissem.apps.abit.Identicon import ch.dissem.apps.abit.R +import ch.dissem.apps.abit.util.Drawables.QR_CODE_SIZE import ch.dissem.bitmessage.entity.BitmessageAddress import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter @@ -42,61 +43,61 @@ import java.io.ByteArrayOutputStream * Some helper methods to work with drawables. */ object Drawables { - private val LOG = LoggerFactory.getLogger(Drawables::class.java) + internal val LOG = LoggerFactory.getLogger(Drawables::class.java) - private const val QR_CODE_SIZE = 350 + internal const val QR_CODE_SIZE = 350 fun addIcon(ctx: Context, menu: Menu, menuItem: Int, icon: IIcon): MenuItem { val item = menu.findItem(menuItem) item.icon = IconicsDrawable(ctx, icon).colorRes(R.color.colorPrimaryDarkText).actionBar() return item } - - fun toBitmap(identicon: Identicon, width: Int, height: Int = width): Bitmap { - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - identicon.setBounds(0, 0, canvas.width, canvas.height) - identicon.draw(canvas) - return bitmap - } - - fun qrCode(address: BitmessageAddress?): Bitmap? { - if (address == null) { - return null - } - val link = StringBuilder() - link.append(Constants.BITMESSAGE_URL_SCHEMA) - link.append(address.address) - if (address.alias != null) { - link.append("?label=").append(address.alias) - } - address.pubkey?.apply { - link.append(if (address.alias == null) '?' else '&') - val pubkey = ByteArrayOutputStream() - writer().writeUnencrypted(pubkey) - link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE or NO_WRAP)) - - } - val result: BitMatrix - try { - result = MultiFormatWriter().encode(link.toString(), - BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null) - } catch (e: WriterException) { - LOG.error(e.message, e) - return null - } - - val w = result.width - val h = result.height - val pixels = IntArray(w * h) - for (y in 0 until h) { - val offset = y * w - for (x in 0 until w) { - pixels[offset + x] = if (result.get(x, y)) BLACK else WHITE - } - } - val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) - bitmap.setPixels(pixels, 0, QR_CODE_SIZE, 0, 0, w, h) - return bitmap - } +} + +fun Drawable.toBitmap(width: Int, height: Int = width): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + setBounds(0, 0, canvas.width, canvas.height) + draw(canvas) + return bitmap +} + +fun BitmessageAddress.qrCode(): Bitmap? { + val link = StringBuilder() + link.append(Constants.BITMESSAGE_URL_SCHEMA) + link.append(address) + if (alias != null) { + link.append("?label=").append(alias) + } + pubkey?.apply { + link.append(if (alias == null) '?' else '&') + val pubkey = ByteArrayOutputStream() + writer().writeUnencrypted(pubkey) + link.append("pubkey=") + .append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE or NO_WRAP)) + + } + val result: BitMatrix + try { + result = MultiFormatWriter().encode( + link.toString(), + BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null + ) + } catch (e: WriterException) { + Drawables.LOG.error(e.message, e) + return null + } + + val w = result.width + val h = result.height + val pixels = IntArray(w * h) + for (y in 0 until h) { + val offset = y * w + for (x in 0 until w) { + pixels[offset + x] = if (result.get(x, y)) BLACK else WHITE + } + } + val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + bitmap.setPixels(pixels, 0, QR_CODE_SIZE, 0, 0, w, h) + return bitmap } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/FabUtils.kt b/app/src/main/java/ch/dissem/apps/abit/util/FabUtils.kt deleted file mode 100644 index 93647b4..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/util/FabUtils.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ch.dissem.apps.abit.util - -import android.support.annotation.DrawableRes -import ch.dissem.apps.abit.MainActivity -import ch.dissem.apps.abit.R -import io.github.kobakei.materialfabspeeddial.FabSpeedDial -import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu - -/** - * Utilities to work with the common floating action button in the main activity - */ -object FabUtils { - fun initFab(activity: MainActivity, @DrawableRes drawableRes: Int, menu: FabSpeedDialMenu): FabSpeedDial { - val fab = activity.floatingActionButton ?: throw IllegalStateException("Fab must not be null") - fab.removeAllOnMenuItemClickListeners() - fab.show() - fab.closeMenu() - val mainFab = fab.mainFab - mainFab.setImageResource(drawableRes) - fab.setMenu(menu) - fab.addOnStateChangeListener { isOpened: Boolean -> - if (isOpened) { - // It will be turned 45 degrees, which makes an x out of the + - mainFab.setImageResource(R.drawable.ic_action_add) - } else { - mainFab.setImageResource(drawableRes) - } - } - return fab - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Labels.kt b/app/src/main/java/ch/dissem/apps/abit/util/Labels.kt index f9d13df..9fdbba2 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Labels.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Labels.kt @@ -2,31 +2,27 @@ package ch.dissem.apps.abit.util import android.content.Context import android.support.annotation.ColorInt - +import ch.dissem.apps.abit.R +import ch.dissem.bitmessage.entity.valueobject.Label import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.typeface.IIcon -import ch.dissem.apps.abit.R -import ch.dissem.bitmessage.entity.valueobject.Label - /* * Helper methods to help with translating the default labels, getting label colors and so on. */ -fun Label.getText(ctx: Context): String = Labels.getText(type, toString(), ctx)!! +fun Label.getText(ctx: Context): String = type?.getText(toString(), ctx) ?: toString() -object Labels { - fun getText(type: Label.Type?, alternative: String?, ctx: Context) = when (type) { - Label.Type.INBOX -> ctx.getString(R.string.inbox) - Label.Type.DRAFT -> ctx.getString(R.string.draft) - Label.Type.OUTBOX -> ctx.getString(R.string.outbox) - Label.Type.SENT -> ctx.getString(R.string.sent) - Label.Type.UNREAD -> ctx.getString(R.string.unread) - Label.Type.TRASH -> ctx.getString(R.string.trash) - Label.Type.BROADCAST -> ctx.getString(R.string.broadcasts) - else -> alternative - } +fun Label.Type.getText(alternative: String?, ctx: Context) = when (this) { + Label.Type.INBOX -> ctx.getString(R.string.inbox) + Label.Type.DRAFT -> ctx.getString(R.string.draft) + Label.Type.OUTBOX -> ctx.getString(R.string.outbox) + Label.Type.SENT -> ctx.getString(R.string.sent) + Label.Type.UNREAD -> ctx.getString(R.string.unread) + Label.Type.TRASH -> ctx.getString(R.string.trash) + Label.Type.BROADCAST -> ctx.getString(R.string.broadcasts) + else -> alternative } fun Label.getIcon(): IIcon = when (type) { diff --git a/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt b/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt index 889717d..05a761b 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt @@ -23,7 +23,7 @@ object PowStats { powCount = preferences.getLong(PREFERENCE_POW_COUNT, 0L) } } - return (BigInteger.valueOf(averagePowUnitTime) * BigInteger(target) / TWO_POW_64).toLong() + return (averagePowUnitTime * BigInteger(target) / TWO_POW_64).toLong() } fun addPow(ctx: Context, time: Long, target: ByteArray) { @@ -32,7 +32,7 @@ object PowStats { synchronized(this) { powCount++ averagePowUnitTime = ( - (BigInteger.valueOf(averagePowUnitTime) * powCountBefore + (BigInteger.valueOf(time) * TWO_POW_64 / targetBigInt)) / BigInteger.valueOf(powCount) + (averagePowUnitTime * powCountBefore + (time * TWO_POW_64 / targetBigInt)) / powCount ).toLong() val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) @@ -42,4 +42,7 @@ object PowStats { .apply() } } + + private operator fun Long.times(other: BigInteger) = this.toBigInteger() * other + private operator fun BigInteger.div(other: Long) = this / other.toBigInteger() } From f374748f714147ed8b3ed369da7d699a7dcb8dff Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 3 Apr 2018 22:11:37 +0200 Subject: [PATCH 06/28] Fixed tests and updated dependencies --- app/build.gradle | 8 +++- .../AndroidMessageRepositoryTest.kt | 4 +- .../AndroidProofOfWorkRepositoryTest.kt | 41 +++++++++++-------- build.gradle | 4 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 41b9321..330ee5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,11 @@ if (project.hasProperty("project.configs") //noinspection GroovyMissingReturnStatement android { compileSdkVersion 27 - buildToolsVersion "26.0.2" + buildToolsVersion "27.0.3" + signingConfigs { + release + } defaultConfig { applicationId "ch.dissem.apps.${appName.toLowerCase()}" minSdkVersion 19 @@ -69,10 +72,11 @@ dependencies { implementation "ch.dissem.jabit:jabit-core:$jabitVersion" implementation "ch.dissem.jabit:jabit-networking:$jabitVersion" - implementation "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion" implementation "ch.dissem.jabit:jabit-extensions:$jabitVersion" implementation "ch.dissem.jabit:jabit-wif:$jabitVersion" implementation "ch.dissem.jabit:jabit-exports:$jabitVersion" + implementation "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion" + testImplementation "ch.dissem.jabit:jabit-cryptography-bouncy:$jabitVersion" implementation 'org.slf4j:slf4j-android:1.7.25' diff --git a/app/src/test/java/ch/dissem/bitmessage/repository/AndroidMessageRepositoryTest.kt b/app/src/test/java/ch/dissem/bitmessage/repository/AndroidMessageRepositoryTest.kt index ead5c9d..de5acd2 100644 --- a/app/src/test/java/ch/dissem/bitmessage/repository/AndroidMessageRepositoryTest.kt +++ b/app/src/test/java/ch/dissem/bitmessage/repository/AndroidMessageRepositoryTest.kt @@ -21,7 +21,7 @@ import ch.dissem.apps.abit.repository.AndroidAddressRepository import ch.dissem.apps.abit.repository.AndroidLabelRepository import ch.dissem.apps.abit.repository.AndroidMessageRepository import ch.dissem.apps.abit.repository.SqlHelper -import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.Plaintext @@ -69,7 +69,7 @@ class AndroidMessageRepositoryTest : TestBase() { val labelRepo = AndroidLabelRepository(sqlHelper, RuntimeEnvironment.application) repo = AndroidMessageRepository(sqlHelper) mockedInternalContext( - cryptography = SpongyCryptography(), + cryptography = BouncyCryptography(), addressRepository = addressRepo, labelRepository = labelRepo, messageRepository = repo, diff --git a/app/src/test/java/ch/dissem/bitmessage/repository/AndroidProofOfWorkRepositoryTest.kt b/app/src/test/java/ch/dissem/bitmessage/repository/AndroidProofOfWorkRepositoryTest.kt index 5c06974..a86baa7 100644 --- a/app/src/test/java/ch/dissem/bitmessage/repository/AndroidProofOfWorkRepositoryTest.kt +++ b/app/src/test/java/ch/dissem/bitmessage/repository/AndroidProofOfWorkRepositoryTest.kt @@ -59,7 +59,6 @@ class AndroidProofOfWorkRepositoryTest : TestBase() { fun setUp() { RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME) val sqlHelper = SqlHelper(RuntimeEnvironment.application) - addressRepo = AndroidAddressRepository(sqlHelper) messageRepo = AndroidMessageRepository(sqlHelper) repo = AndroidProofOfWorkRepository(sqlHelper) @@ -94,12 +93,14 @@ class AndroidProofOfWorkRepositoryTest : TestBase() { messageRepo.save(plaintext) plaintext.ackMessage!!.let { ackMessage -> initialHash2 = cryptography().getInitialHash(ackMessage) - repo.putObject(ProofOfWorkRepository.Item( - objectMessage = ackMessage, - nonceTrialsPerByte = 1000, extraBytes = 1000, - expirationTime = UnixTime.now + 10 * UnixTime.MINUTE, - message = plaintext - )) + repo.putObject( + ProofOfWorkRepository.Item( + objectMessage = ackMessage, + nonceTrialsPerByte = 1000, extraBytes = 1000, + expirationTime = UnixTime.now + 10 * UnixTime.MINUTE, + message = plaintext + ) + ) } } @@ -132,13 +133,15 @@ class AndroidProofOfWorkRepositoryTest : TestBase() { .build() messageRepo.save(plaintext) plaintext.ackMessage!!.let { ackMessage -> - repo.putObject(ProofOfWorkRepository.Item( - objectMessage = ackMessage, - nonceTrialsPerByte = 1000, - extraBytes = 1000, - expirationTime = UnixTime.now + 10 * UnixTime.MINUTE, - message = plaintext - )) + repo.putObject( + ProofOfWorkRepository.Item( + objectMessage = ackMessage, + nonceTrialsPerByte = 1000, + extraBytes = 1000, + expirationTime = UnixTime.now + 10 * UnixTime.MINUTE, + message = plaintext + ) + ) } assertThat(repo.getItems().size, `is`(sizeBefore + 1)) } @@ -147,7 +150,10 @@ class AndroidProofOfWorkRepositoryTest : TestBase() { fun `ensure item can be retrieved`() { val item = repo.getItem(initialHash1) assertThat(item, notNullValue()) - assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) + assertThat<ObjectPayload>( + item.objectMessage.payload, + instanceOf<ObjectPayload>(GetPubkey::class.java) + ) assertThat(item.nonceTrialsPerByte, `is`(1000L)) assertThat(item.extraBytes, `is`(1000L)) } @@ -156,7 +162,10 @@ class AndroidProofOfWorkRepositoryTest : TestBase() { fun `ensure ack item can be retrieved`() { val item = repo.getItem(initialHash2) assertThat(item, notNullValue()) - assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) + assertThat<ObjectPayload>( + item.objectMessage.payload, + instanceOf<ObjectPayload>(GenericPayload::class.java) + ) assertThat(item.nonceTrialsPerByte, `is`(1000L)) assertThat(item.extraBytes, `is`(1000L)) assertThat(item.expirationTime, not<Number>(0)) diff --git a/build.gradle b/build.gradle index bcaecb5..ed23df1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.2.30' + ext.kotlin_version = '1.2.31' ext.anko_version = '0.10.4' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' From 4e5ba4401a9a30fa43a7e3a7f12af3bf2f867562 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 3 Apr 2018 22:12:13 +0200 Subject: [PATCH 07/28] Improved multi identicon background colour --- app/src/main/java/ch/dissem/apps/abit/Identicon.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt index 1ec06c9..6cfd56b 100644 --- a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt +++ b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt @@ -115,7 +115,7 @@ class Identicon(input: BitmessageAddress) : Drawable() { } } -class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundColor: Int = Color.WHITE) : +class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt private val backgroundColor: Int = 0xFFAEC2CC.toInt()) : Drawable() { private val paint = Paint().apply { From 9b75a8c2ef43276945f973b48488b1c6fd6729ee Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 3 Apr 2018 22:13:23 +0200 Subject: [PATCH 08/28] Fixed tests --- app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt b/app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt index 08aff58..4055ac4 100644 --- a/app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt +++ b/app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt @@ -19,7 +19,7 @@ package ch.dissem.bitmessage.repository import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.Preferences -import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.payload.V4Pubkey @@ -41,7 +41,7 @@ open class TestBase { @JvmStatic fun init() { mockedInternalContext( - cryptography = SpongyCryptography(), + cryptography = BouncyCryptography(), proofOfWorkEngine = MultiThreadedPOWEngine() ) } From 6a311a0346e968b20716ddd930853b708a9f32ac Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 3 Apr 2018 22:14:00 +0200 Subject: [PATCH 09/28] Fix minor lint warning --- app/src/main/res/layout/dialog_add_deterministic_identity.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_add_deterministic_identity.xml b/app/src/main/res/layout/dialog_add_deterministic_identity.xml index cdcfc76..4e35bb2 100644 --- a/app/src/main/res/layout/dialog_add_deterministic_identity.xml +++ b/app/src/main/res/layout/dialog_add_deterministic_identity.xml @@ -46,7 +46,8 @@ android:id="@+id/label" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/label" /> + android:hint="@string/label" + android:inputType="text" /> </android.support.design.widget.TextInputLayout> From 1426b786e8a567c7f64954fd11f019cf2e48c984 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 3 Apr 2018 22:14:46 +0200 Subject: [PATCH 10/28] Add message grouping by subject --- .../java/ch/dissem/apps/abit/MainActivity.kt | 3 +- .../apps/abit/listener/MessageListener.kt | 30 +++++++++- .../ch/dissem/apps/abit/util/Constants.kt | 1 + .../ch/dissem/apps/abit/util/Preferences.kt | 60 ++++++++----------- app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 5 ++ 7 files changed, 67 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt index 5f7df5f..82fe544 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt @@ -114,7 +114,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) - val listFragment = MessageListFragment() + val listFragment = ConversationListFragment() supportFragmentManager .beginTransaction() .replace(R.id.item_list, listFragment) @@ -303,6 +303,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { currentLabel.value = intent.getSerializableExtra(EXTRA_SHOW_LABEL) as Label } else if (currentLabel.value == null) { currentLabel.value = labels[0] + } for (label in labels) { addLabelEntry(label) diff --git a/app/src/main/java/ch/dissem/apps/abit/listener/MessageListener.kt b/app/src/main/java/ch/dissem/apps/abit/listener/MessageListener.kt index cb0e4ac..a9405af 100644 --- a/app/src/main/java/ch/dissem/apps/abit/listener/MessageListener.kt +++ b/app/src/main/java/ch/dissem/apps/abit/listener/MessageListener.kt @@ -19,8 +19,11 @@ package ch.dissem.apps.abit.listener import android.content.Context import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.notification.NewMessageNotification +import ch.dissem.apps.abit.util.Preferences import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.utils.ConversationService import java.util.* import java.util.concurrent.Executors @@ -33,14 +36,26 @@ import java.util.concurrent.Executors * notifications should be combined. * */ -class MessageListener(ctx: Context) : BitmessageContext.Listener { +class MessageListener(ctx: Context) : BitmessageContext.Listener.WithContext { + override fun setContext(ctx: BitmessageContext) { + messageRepo = ctx.messages + conversationService = ConversationService(messageRepo) + } + private val unacknowledged = LinkedList<Plaintext>() private var numberOfUnacknowledgedMessages = 0 private val notification = NewMessageNotification(ctx) private val pool = Executors.newSingleThreadExecutor() + private lateinit var messageRepo: MessageRepository + private lateinit var conversationService: ConversationService + + init { + emulateConversations = Preferences.isEmulateConversations(ctx) + } override fun receive(plaintext: Plaintext) { pool.submit { + updateConversation(plaintext) unacknowledged.addFirst(plaintext) numberOfUnacknowledgedMessages++ if (unacknowledged.size > 5) { @@ -65,4 +80,17 @@ class MessageListener(ctx: Context) : BitmessageContext.Listener { numberOfUnacknowledgedMessages = 0 } } + + fun updateConversation(plaintext: Plaintext) { + if (emulateConversations && plaintext.encoding != Plaintext.Encoding.EXTENDED) { + conversationService.getSubject(listOf(plaintext))?.let { subject -> + plaintext.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) + messageRepo.save(plaintext) + } + } + } + + companion object { + private var emulateConversations = false + } } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt b/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt index ffede4f..a57a4b2 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt @@ -23,6 +23,7 @@ import java.util.regex.Pattern */ object Constants { const val PREFERENCE_WIFI_ONLY = "wifi_only" + const val PREFERENCE_EMULATE_CONVERSATIONS = "emulate_conversations" const val PREFERENCE_TRUSTED_NODE = "trusted_node" const val PREFERENCE_SYNC_TIMEOUT = "sync_timeout" const val PREFERENCE_SERVER_POW = "server_pow" diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt index b8b571f..7b75161 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt @@ -17,15 +17,16 @@ package ch.dissem.apps.abit.util import android.content.Context -import android.preference.PreferenceManager import ch.dissem.apps.abit.R import ch.dissem.apps.abit.notification.ErrorNotification +import ch.dissem.apps.abit.util.Constants.PREFERENCE_EMULATE_CONVERSATIONS import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY import org.jetbrains.anko.connectivityManager +import org.jetbrains.anko.defaultSharedPreferences import org.slf4j.LoggerFactory import java.io.File import java.io.IOException @@ -70,57 +71,48 @@ object Preferences { return Integer.parseInt(portString) } catch (e: NumberFormatException) { ErrorNotification(ctx) - .setError(R.string.error_invalid_sync_port, portString) - .show() + .setError(R.string.error_invalid_sync_port, portString) + .show() } } return 8444 } - fun getTimeoutInSeconds(ctx: Context): Long { - val preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT) ?: return 120 - return preference.toLong() - } + fun getTimeoutInSeconds(ctx: Context): Long = + getPreference(ctx, PREFERENCE_SYNC_TIMEOUT)?.toLong() ?: 120 - private fun getPreference(ctx: Context, name: String): String? { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) + private fun getPreference(ctx: Context, name: String): String? = + ctx.defaultSharedPreferences.getString(name, null) - return preferences.getString(name, null) - } + fun isConnectionAllowed(ctx: Context) = + !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered - fun isConnectionAllowed(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered - - fun isWifiOnly(ctx: Context): Boolean { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) - return preferences.getBoolean(PREFERENCE_WIFI_ONLY, true) - } + fun isWifiOnly(ctx: Context) = + ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true) fun setWifiOnly(ctx: Context, status: Boolean) { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) - preferences.edit().putBoolean(PREFERENCE_WIFI_ONLY, status).apply() + ctx.defaultSharedPreferences.edit() + .putBoolean(PREFERENCE_WIFI_ONLY, status) + .apply() } - fun isFullNodeActive(ctx: Context): Boolean { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) - return preferences.getBoolean(PREFERENCE_FULL_NODE, false) - } + fun isEmulateConversations(ctx: Context) = + ctx.defaultSharedPreferences.getBoolean(PREFERENCE_EMULATE_CONVERSATIONS, true) + + + fun isFullNodeActive(ctx: Context) = + ctx.defaultSharedPreferences.getBoolean(PREFERENCE_FULL_NODE, false) fun setFullNodeActive(ctx: Context, status: Boolean) { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) - preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply() + ctx.defaultSharedPreferences.edit() + .putBoolean(PREFERENCE_FULL_NODE, status) + .apply() } fun getExportDirectory(ctx: Context) = File(ctx.filesDir, "exports") - fun requestAcknowledgements(ctx: Context): Boolean { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) - return preferences.getBoolean(PREFERENCE_REQUEST_ACK, true) - } - - fun setRequestAcknowledgements(ctx: Context, status: Boolean) { - val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) - preferences.edit().putBoolean(PREFERENCE_REQUEST_ACK, status).apply() - } + fun requestAcknowledgements(ctx: Context) = + ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true) fun cleanupExportDirectory(ctx: Context) { val exportDirectory = getExportDirectory(ctx) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f71c93c..d300a58 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -137,4 +137,6 @@ Als Alternative kann in den Einstellungen ein vertrauenswürdiger Knoten konfigu <string name="broadcasts">Broadcasts</string> <string name="encoding_simple">einfach</string> <string name="encoding_extended">erweitert</string> + <string name="emulate_conversations">Konversation erraten</string> + <string name="emulate_conversations_summary">Benutze Betreff um zu erraten welche Nachrichten zusammengehören. Die Reihenfolge stimmt häufig nicht.</string> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 007bb84..8cd8186 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -137,4 +137,6 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="encoding_simple">simple</string> <string name="encoding_extended">extended</string> <string name="context_menu">actions</string> + <string name="emulate_conversations">Guess conversations</string> + <string name="emulate_conversations_summary">Use subject to determine which messages belong together. The order will likely be wrong.</string> </resources> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index d62ac94..0a408e5 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -5,6 +5,11 @@ android:key="wifi_only" android:summary="@string/wifi_only_summary" android:title="@string/wifi_only" /> + <android.support.v7.preference.SwitchPreferenceCompat + android:defaultValue="true" + android:key="emulate_conversations" + android:summary="@string/emulate_conversations_summary" + android:title="@string/emulate_conversations" /> <android.support.v7.preference.SwitchPreferenceCompat android:defaultValue="true" android:key="request_acknowledgements" From a89f80f4007e8609b5c8d24d3e2ca3a0c394ba2a Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 13 Apr 2018 07:19:43 +0200 Subject: [PATCH 11/28] =?UTF-8?q?=F0=9F=8E=A8=20minor=20multi-identicon=20?= =?UTF-8?q?improvements/fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/ch/dissem/apps/abit/Identicon.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt index 6cfd56b..3dd417f 100644 --- a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt +++ b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt @@ -124,7 +124,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt private val backg color = backgroundColor } - private val identicons = input.map { Identicon(it) }.take(4) + private val identicons = input.sortedBy { it.isChan }.map { Identicon(it) }.take(4) override fun draw(canvas: Canvas) { val width = canvas.width.toFloat() @@ -145,17 +145,18 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt private val backg } } 3 -> { - val scale = 1f / (1f + 2f * sqrt(3f)) + val scale = 2f / (1f + 2f * sqrt(3f)) val w = width * scale val h = height * scale + canvas.drawCircle(width / 2, height / 2, width / 2, paint) identicons[0].draw(canvas, (width - w) / 2, 0f, w, h) identicons[1].draw(canvas, (width - 2 * w) / 2, h * sqrt(3f) / 2, w, h) identicons[2].draw(canvas, width / 2, h * sqrt(3f) / 2, w, h) } 4 -> { canvas.drawCircle(width / 2, height / 2, width / 2, paint) - val scale = 1f / (1f + sqrt(2f)) + val scale = 2f / (1f + sqrt(2f)) val borderScale = 0.5f - scale val w = width * scale val h = height * scale From 85562efc0d548559e37eb0e7fc0143e706c1292a Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 13 Apr 2018 12:36:08 +0200 Subject: [PATCH 12/28] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Update=20Android?= =?UTF-8?q?=20build=20tools?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ed23df1..8e37169 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0' + classpath 'com.android.tools.build:gradle:3.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' From 78f9621afad180d50167415e09f034b8c609fa48 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 13 Apr 2018 12:39:59 +0200 Subject: [PATCH 13/28] =?UTF-8?q?=F0=9F=9A=A7=20Add=20code=20to=20migrate?= =?UTF-8?q?=20existing=20conversations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Work in progress: does work, but usually doesn't finish. This needs to be moved into some proper batch processing. --- .../ch/dissem/apps/abit/SettingsFragment.kt | 51 ++++++++++++++++++- .../repository/AndroidMessageRepository.kt | 33 ++++++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/preferences.xml | 5 ++ 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt index 0cc5dca..7378af7 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt @@ -32,12 +32,14 @@ import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE import ch.dissem.apps.abit.util.Exports import ch.dissem.apps.abit.util.Preferences +import ch.dissem.bitmessage.entity.Plaintext import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.LibsBuilder import org.jetbrains.anko.doAsync import org.jetbrains.anko.support.v4.indeterminateProgressDialog import org.jetbrains.anko.support.v4.startActivity import org.jetbrains.anko.uiThread +import java.util.* /** * @author Christian Basler @@ -53,6 +55,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP findPreference("export")?.onPreferenceClickListener = exportClickListener() findPreference("import")?.onPreferenceClickListener = importClickListener() findPreference("status").onPreferenceClickListener = statusClickListener() + val conversationInit = findPreference("emulate_conversations_initialize") + conversationInit?.onPreferenceClickListener = conversationInitClickListener(conversationInit) + findPreference("emulate_conversations")?.onPreferenceChangeListener = emulateConversationChangeListener(conversationInit) } private fun aboutClickListener() = Preference.OnPreferenceClickListener { @@ -73,7 +78,8 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP } private fun cleanupClickListener(cleanup: Preference) = Preference.OnPreferenceClickListener { - val ctx = activity?.applicationContext ?: throw IllegalStateException("Context not available") + val ctx = activity?.applicationContext + ?: throw IllegalStateException("Context not available") cleanup.isEnabled = false Toast.makeText(ctx, R.string.cleanup_notification_start, Toast.LENGTH_SHORT).show() @@ -193,6 +199,49 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP } } + private fun conversationInitClickListener(conversationInit: Preference) = Preference.OnPreferenceClickListener { + val ctx = activity?.applicationContext + ?: throw IllegalStateException("Context not available") + conversationInit.isEnabled = false + Toast.makeText(ctx, R.string.emulate_conversations_summary, Toast.LENGTH_SHORT).show() + + doAsync { + val messageRepo = Singleton.getMessageRepository(ctx) + val conversationService = Singleton.getConversationService(ctx) + do { + var previous: Plaintext? = null + val messages = messageRepo.findNextLegacyMessages(previous) + messages.forEach { msg -> + if (msg.encoding == Plaintext.Encoding.SIMPLE) { + conversationService.getSubject(listOf(msg))?.let { subject -> + msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) + messageRepo.save(msg) + Thread.yield() + } + } + } + if (!messages.isEmpty()) { + previous = messages.last() + } + } while (!messages.isEmpty()) + + uiThread { + Toast.makeText( + ctx, + R.string.cleanup_notification_end, + Toast.LENGTH_LONG + ).show() + conversationInit.isEnabled = true + } + } + return@OnPreferenceClickListener true + } + + private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { preference, newValue -> + conversationInit?.isEnabled = newValue as Boolean + true + } + companion object { const val WRITE_EXPORT_REQUEST_CODE = 1 const val READ_IMPORT_REQUEST_CODE = 2 diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt index cec80db..174f9d1 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt @@ -254,6 +254,39 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo sql.writableDatabase.delete(TABLE_NAME, "id = ?", arrayOf(message.id.toString())) } + fun findNextLegacyMessages(previous: Plaintext?, limit: Int = 10): List<Plaintext> { + val result = mutableListOf<Plaintext>() + + val projection = arrayOf( + COLUMN_ID, + COLUMN_IV, + COLUMN_TYPE, + COLUMN_SENDER, + COLUMN_RECIPIENT, + COLUMN_DATA, + COLUMN_ACK_DATA, + COLUMN_SENT, + COLUMN_RECEIVED, + COLUMN_STATUS, + COLUMN_TTL, + COLUMN_RETRIES, + COLUMN_NEXT_TRY, + COLUMN_CONVERSATION + ) + + sql.readableDatabase.query( + TABLE_NAME, projection, + "$COLUMN_ID > ${previous?.id ?: Long.MIN_VALUE}", null, null, null, + "$COLUMN_ID ASC", + "$limit" + ).use { c -> + while (c.moveToNext()) { + result.add(getMessage(c)) + } + } + return result + } + companion object { private const val TABLE_NAME = "Message" private const val COLUMN_ID = "id" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8cd8186..630db20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,4 +139,5 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="context_menu">actions</string> <string name="emulate_conversations">Guess conversations</string> <string name="emulate_conversations_summary">Use subject to determine which messages belong together. The order will likely be wrong.</string> + <string name="emulate_conversations_initialize">Group existing messages by subject</string> </resources> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 0a408e5..bf163df 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -10,6 +10,11 @@ android:key="emulate_conversations" android:summary="@string/emulate_conversations_summary" android:title="@string/emulate_conversations" /> + <android.support.v7.preference.Preference + android:defaultValue="true" + android:key="emulate_conversations_initialize" + android:summary="@string/emulate_conversations_summary" + android:title="@string/emulate_conversations_initialize" /> <android.support.v7.preference.SwitchPreferenceCompat android:defaultValue="true" android:key="request_acknowledgements" From 412180f443b8ad7ff6efd03071233afdd1758a8f Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Sat, 14 Apr 2018 20:27:29 +0200 Subject: [PATCH 14/28] =?UTF-8?q?=E2=9C=A8=20Add=20batch=20service=20for?= =?UTF-8?q?=20migrating=20existing=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 + .../ch/dissem/apps/abit/SettingsFragment.kt | 80 ++++++------ .../abit/notification/BatchNotification.kt | 54 ++++++++ .../repository/AndroidMessageRepository.kt | 7 ++ .../abit/service/BatchProcessorService.kt | 117 ++++++++++++++++++ .../res/drawable/ic_notification_batch.xml | 10 ++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 234 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt create mode 100644 app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt create mode 100644 app/src/main/res/drawable/ic_notification_batch.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc7f881..2c41ccd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -198,6 +198,10 @@ android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE" /> + <service + android:name=".service.BatchProcessorService" + android:exported="false" /> + <activity android:name=".StatusActivity" android:label="@string/title_activity_status" diff --git a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt index 7378af7..1993737 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt @@ -17,15 +17,17 @@ package ch.dissem.apps.abit import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences +import android.content.* import android.os.Bundle +import android.os.IBinder import android.preference.PreferenceManager import android.support.v4.content.FileProvider.getUriForFile import android.support.v7.preference.Preference import android.support.v7.preference.PreferenceFragmentCompat +import android.support.v7.preference.SwitchPreferenceCompat import android.widget.Toast +import ch.dissem.apps.abit.service.BatchProcessorService +import ch.dissem.apps.abit.service.SimpleJob import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.synchronization.SyncAdapter import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW @@ -55,9 +57,12 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP findPreference("export")?.onPreferenceClickListener = exportClickListener() findPreference("import")?.onPreferenceClickListener = importClickListener() findPreference("status").onPreferenceClickListener = statusClickListener() - val conversationInit = findPreference("emulate_conversations_initialize") - conversationInit?.onPreferenceClickListener = conversationInitClickListener(conversationInit) - findPreference("emulate_conversations")?.onPreferenceChangeListener = emulateConversationChangeListener(conversationInit) + val conversationInit = findPreference("emulate_conversations_initialize") as? SwitchPreferenceCompat + conversationInit?.onPreferenceClickListener = conversationInitClickListener() + findPreference("emulate_conversations")?.apply { + onPreferenceChangeListener = emulateConversationChangeListener(conversationInit) + isEnabled = conversationInit?.isChecked ?: false + } } private fun aboutClickListener() = Preference.OnPreferenceClickListener { @@ -199,45 +204,42 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP } } - private fun conversationInitClickListener(conversationInit: Preference) = Preference.OnPreferenceClickListener { - val ctx = activity?.applicationContext - ?: throw IllegalStateException("Context not available") - conversationInit.isEnabled = false - Toast.makeText(ctx, R.string.emulate_conversations_summary, Toast.LENGTH_SHORT).show() + private val connection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + if (service is BatchProcessorService.BatchBinder) { + val messageRepo = Singleton.getMessageRepository(service.service) + val conversationService = Singleton.getConversationService(service.service) - doAsync { - val messageRepo = Singleton.getMessageRepository(ctx) - val conversationService = Singleton.getConversationService(ctx) - do { - var previous: Plaintext? = null - val messages = messageRepo.findNextLegacyMessages(previous) - messages.forEach { msg -> - if (msg.encoding == Plaintext.Encoding.SIMPLE) { - conversationService.getSubject(listOf(msg))?.let { subject -> - msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) - messageRepo.save(msg) - Thread.yield() + service.process(SimpleJob<Plaintext>( + messageRepo.count(), + { messageRepo.findNextLegacyMessages(it) }, + { msg -> + if (msg.encoding == Plaintext.Encoding.SIMPLE) { + conversationService.getSubject(listOf(msg))?.let { subject -> + msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) + messageRepo.save(msg) + Thread.yield() + } } - } - } - if (!messages.isEmpty()) { - previous = messages.last() - } - } while (!messages.isEmpty()) - - uiThread { - Toast.makeText( - ctx, - R.string.cleanup_notification_end, - Toast.LENGTH_LONG - ).show() - conversationInit.isEnabled = true + }, + R.drawable.ic_notification_batch, + R.string.emulate_conversations_batch + )) } } - return@OnPreferenceClickListener true + + override fun onServiceDisconnected(name: ComponentName) { + } } - private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { preference, newValue -> + private fun conversationInitClickListener() = Preference.OnPreferenceClickListener { + val ctx = activity?.applicationContext + ?: throw IllegalStateException("Context not available") + ctx.bindService(Intent(ctx, BatchProcessorService::class.java), connection, Context.BIND_AUTO_CREATE) + true + } + + private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { _, newValue -> conversationInit?.isEnabled = newValue as Boolean true } diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt new file mode 100644 index 0000000..aadd9af --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.apps.abit.notification + +import android.content.Context +import android.support.v4.app.NotificationCompat +import ch.dissem.apps.abit.R +import ch.dissem.apps.abit.service.Job + +/** + * Ongoing notification while proof of work is in progress. + */ +class BatchNotification(ctx: Context) : AbstractNotification(ctx) { + + private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setUsesChronometer(true) + + init { + initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent) + notification = builder.build() + } + + override val notificationId = ONGOING_NOTIFICATION_ID + + fun update(job: Job): BatchNotification { + + builder.setContentTitle(ctx.getString(job.description)) + .setSmallIcon(job.icon) + .setProgress(job.numberOfItems, job.numberOfProcessedItems, job.numberOfItems <= 0) + + notification = builder.build() + show() + return this + } + + companion object { + const val ONGOING_NOTIFICATION_ID = 4 + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt index 174f9d1..4fd1963 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt @@ -47,6 +47,13 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo super.findMessages(label, offset, limit) } + fun count() = DatabaseUtils.queryNumEntries( + sql.readableDatabase, + TABLE_NAME, + null, + null + ).toInt() + override fun countUnread(label: Label?) = when { label === LABEL_ARCHIVE -> 0 label == null -> DatabaseUtils.queryNumEntries( diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt b/app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt new file mode 100644 index 0000000..bf87598 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt @@ -0,0 +1,117 @@ +package ch.dissem.apps.abit.service + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.support.annotation.DrawableRes +import android.support.annotation.StringRes +import android.support.v4.content.ContextCompat +import ch.dissem.apps.abit.notification.BatchNotification +import ch.dissem.apps.abit.notification.BatchNotification.Companion.ONGOING_NOTIFICATION_ID +import org.jetbrains.anko.doAsync +import java.util.* + +class BatchProcessorService : Service() { + private lateinit var notification: BatchNotification + + override fun onCreate() { + notification = BatchNotification(this) + } + + override fun onBind(intent: Intent) = BatchBinder(this) + + class BatchBinder internal constructor(val service: BatchProcessorService) : Binder() { + private val notification = service.notification + + fun process(job: Job) = synchronized(queue) { + ContextCompat.startForegroundService( + service, + Intent(service, BatchProcessorService::class.java) + ) + service.startForeground( + ONGOING_NOTIFICATION_ID, + notification.notification + ) + if (!working) { + working = true + service.processQueue(job) + } else { + queue.add(job) + } + } + + } + + private fun processQueue(job: Job) { + doAsync { + var next: Job? = job + while (next != null) { + next.process(notification) + + synchronized(queue) { + next = queue.poll() + if (next == null) { + working = false + stopForeground(true) + stopSelf() + } + } + } + + } + } + + companion object { + private var working = false + private val queue = LinkedList<Job>() + } +} + +interface Job { + val icon: Int + @DrawableRes get + + val description: Int + @StringRes get + + val numberOfItems: Int + var numberOfProcessedItems: Int + + /** + * Runs the job. This shouldn't happen in a separate thread, as this is handled by the service. + */ + fun process(notification: BatchNotification) +} + +data class SimpleJob<T>( + override val numberOfItems: Int, + /** + * Provides the next batch of items, given the last item of the previous batch, + * or null for the first batch. + */ + private val provider: (T?) -> List<T>, + /** + * Processes an item. + */ + private val processor: (T) -> Unit, + override val icon: Int, + override val description: Int +) : Job { + override var numberOfProcessedItems: Int = 0 + + override fun process(notification: BatchNotification) { + notification.update(this) + var batch = provider.invoke(null) + while (batch.isNotEmpty()) { + Thread.yield() + batch.forEach { + processor.invoke(it) + Thread.yield() + } + numberOfProcessedItems += batch.size + notification.update(this) + batch = provider.invoke(batch.last()) + } + } + +} diff --git a/app/src/main/res/drawable/ic_notification_batch.xml b/app/src/main/res/drawable/ic_notification_batch.xml new file mode 100644 index 0000000..fc8b546 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_batch.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M15.9,18.45C17.25,18.45 18.35,17.35 18.35,16C18.35,14.65 17.25,13.55 15.9,13.55C14.54,13.55 13.45,14.65 13.45,16C13.45,17.35 14.54,18.45 15.9,18.45M21.1,16.68L22.58,17.84C22.71,17.95 22.75,18.13 22.66,18.29L21.26,20.71C21.17,20.86 21,20.92 20.83,20.86L19.09,20.16C18.73,20.44 18.33,20.67 17.91,20.85L17.64,22.7C17.62,22.87 17.47,23 17.3,23H14.5C14.32,23 14.18,22.87 14.15,22.7L13.89,20.85C13.46,20.67 13.07,20.44 12.71,20.16L10.96,20.86C10.81,20.92 10.62,20.86 10.54,20.71L9.14,18.29C9.05,18.13 9.09,17.95 9.22,17.84L10.7,16.68L10.65,16L10.7,15.31L9.22,14.16C9.09,14.05 9.05,13.86 9.14,13.71L10.54,11.29C10.62,11.13 10.81,11.07 10.96,11.13L12.71,11.84C13.07,11.56 13.46,11.32 13.89,11.15L14.15,9.29C14.18,9.13 14.32,9 14.5,9H17.3C17.47,9 17.62,9.13 17.64,9.29L17.91,11.15C18.33,11.32 18.73,11.56 19.09,11.84L20.83,11.13C21,11.07 21.17,11.13 21.26,11.29L22.66,13.71C22.75,13.86 22.71,14.05 22.58,14.16L21.1,15.31L21.15,16L21.1,16.68M6.69,8.07C7.56,8.07 8.26,7.37 8.26,6.5C8.26,5.63 7.56,4.92 6.69,4.92A1.58,1.58 0 0,0 5.11,6.5C5.11,7.37 5.82,8.07 6.69,8.07M10.03,6.94L11,7.68C11.07,7.75 11.09,7.87 11.03,7.97L10.13,9.53C10.08,9.63 9.96,9.67 9.86,9.63L8.74,9.18L8,9.62L7.81,10.81C7.79,10.92 7.7,11 7.59,11H5.79C5.67,11 5.58,10.92 5.56,10.81L5.4,9.62L4.64,9.18L3.5,9.63C3.41,9.67 3.3,9.63 3.24,9.53L2.34,7.97C2.28,7.87 2.31,7.75 2.39,7.68L3.34,6.94L3.31,6.5L3.34,6.06L2.39,5.32C2.31,5.25 2.28,5.13 2.34,5.03L3.24,3.47C3.3,3.37 3.41,3.33 3.5,3.37L4.63,3.82L5.4,3.38L5.56,2.19C5.58,2.08 5.67,2 5.79,2H7.59C7.7,2 7.79,2.08 7.81,2.19L8,3.38L8.74,3.82L9.86,3.37C9.96,3.33 10.08,3.37 10.13,3.47L11.03,5.03C11.09,5.13 11.07,5.25 11,5.32L10.03,6.06L10.06,6.5L10.03,6.94Z" /> +</vector> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 630db20..d57db8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,4 +140,5 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="emulate_conversations">Guess conversations</string> <string name="emulate_conversations_summary">Use subject to determine which messages belong together. The order will likely be wrong.</string> <string name="emulate_conversations_initialize">Group existing messages by subject</string> + <string name="emulate_conversations_batch">Grouping existing messages by subject</string> </resources> From eee1be873afda7a4d6554cd4964dde580548aeab Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 17 Apr 2018 09:42:25 +0200 Subject: [PATCH 15/28] =?UTF-8?q?=F0=9F=8E=A8=20Add=20new=20icon=20vor=20O?= =?UTF-8?q?reo=20and=20later?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/mipmap-anydpi-v26/ic_launcher.xml | 5 +++++ .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 5 +++++ .../res/mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 3158 bytes .../main/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4267 bytes .../res/mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1963 bytes .../main/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2576 bytes .../res/mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 4476 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6076 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 7874 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 9782 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 11251 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14287 bytes .../main/res/values/ic_launcher_background.xml | 4 ++++ 13 files changed, 14 insertions(+) create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/ic_launcher_background.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon> \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon> \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..ceddcc8c5d526ae12446fe438a69bd421d5fcb1f GIT binary patch literal 3158 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..f687951100a1cfc44b09619b9e88621e7a8e4dee GIT binary patch literal 4267 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 N002ovPDHLkV1m(gB(DGf literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..371f559c3eed1cd1b5c9c0e4898ff6e1739b1e25 GIT binary patch literal 1963 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 UvH$S+B81rt5T@`d9mlBu0l>G#RsaA1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..85764a16c6b4feae6734243da9fcabb002863496 GIT binary patch literal 2576 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97a8d35373a1edbcf61a38e25023a3dfc9760ff8 GIT binary patch literal 4476 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..3fa9125aea3db6695f96b29c2649d402be766cf7 GIT binary patch literal 6076 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 Cu%ZzF literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..3b34fcfa82146ca6cbbdd18152f7c9b167d4cead GIT binary patch literal 7874 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..e263a052ec7940889c32772f99b1f2eba4db74da GIT binary patch literal 9782 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%0Dle*C?c`(X}pQulYw0vjLM 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 Jt+ZwE{{gkV(`x_# literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..9b5c7b0717ddc92637ab1f9b44102ce6d4fda928 GIT binary patch literal 11251 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b910cdb9fea4c03d478a98bebed53d9d459ddc1b GIT binary patch literal 14287 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="ic_launcher_background">#FFFFFF</color> +</resources> \ No newline at end of file From 61e579c0d4416992c3a89df4a711d8b3ce5250b2 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 17 Apr 2018 19:55:56 +0200 Subject: [PATCH 16/28] =?UTF-8?q?=F0=9F=9A=B8=20Improve=20settings=20struc?= =?UTF-8?q?ture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/dissem/apps/abit/SettingsFragment.kt | 89 +++++++---- app/src/main/res/values/strings.xml | 8 + app/src/main/res/xml/preferences.xml | 143 +++++++++++------- 3 files changed, 160 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt index 1993737..9d61984 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt @@ -21,10 +21,14 @@ import android.content.* import android.os.Bundle import android.os.IBinder import android.preference.PreferenceManager +import android.support.v4.app.Fragment +import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider.getUriForFile import android.support.v7.preference.Preference import android.support.v7.preference.PreferenceFragmentCompat +import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.SwitchPreferenceCompat +import android.view.View import android.widget.Toast import ch.dissem.apps.abit.service.BatchProcessorService import ch.dissem.apps.abit.service.SimpleJob @@ -46,23 +50,24 @@ import java.util.* /** * @author Christian Basler */ -class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { +class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, + PreferenceFragmentCompat.OnPreferenceStartScreenCallback { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences) + setPreferencesFromResource(R.xml.preferences, rootKey) findPreference("about")?.onPreferenceClickListener = aboutClickListener() - val cleanup = findPreference("cleanup") - cleanup?.onPreferenceClickListener = cleanupClickListener(cleanup) + findPreference("cleanup")?.let { it.onPreferenceClickListener = cleanupClickListener(it) } findPreference("export")?.onPreferenceClickListener = exportClickListener() findPreference("import")?.onPreferenceClickListener = importClickListener() - findPreference("status").onPreferenceClickListener = statusClickListener() - val conversationInit = findPreference("emulate_conversations_initialize") as? SwitchPreferenceCompat + findPreference("status")?.onPreferenceClickListener = statusClickListener() + + val emulateConversations = findPreference("emulate_conversations") as? SwitchPreferenceCompat + val conversationInit = findPreference("emulate_conversations_initialize") + + emulateConversations?.onPreferenceChangeListener = emulateConversationChangeListener(conversationInit) conversationInit?.onPreferenceClickListener = conversationInitClickListener() - findPreference("emulate_conversations")?.apply { - onPreferenceChangeListener = emulateConversationChangeListener(conversationInit) - isEnabled = conversationInit?.isChecked ?: false - } + conversationInit?.isEnabled = emulateConversations?.isChecked ?: false } private fun aboutClickListener() = Preference.OnPreferenceClickListener { @@ -210,21 +215,23 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP val messageRepo = Singleton.getMessageRepository(service.service) val conversationService = Singleton.getConversationService(service.service) - service.process(SimpleJob<Plaintext>( - messageRepo.count(), - { messageRepo.findNextLegacyMessages(it) }, - { msg -> - if (msg.encoding == Plaintext.Encoding.SIMPLE) { - conversationService.getSubject(listOf(msg))?.let { subject -> - msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) - messageRepo.save(msg) - Thread.yield() + service.process( + SimpleJob<Plaintext>( + messageRepo.count(), + { messageRepo.findNextLegacyMessages(it) }, + { msg -> + if (msg.encoding == Plaintext.Encoding.SIMPLE) { + conversationService.getSubject(listOf(msg))?.let { subject -> + msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) + messageRepo.save(msg) + Thread.yield() + } } - } - }, - R.drawable.ic_notification_batch, - R.string.emulate_conversations_batch - )) + }, + R.drawable.ic_notification_batch, + R.string.emulate_conversations_batch + ) + ) } } @@ -239,11 +246,39 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP true } - private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { _, newValue -> - conversationInit?.isEnabled = newValue as Boolean - true + private fun emulateConversationChangeListener(conversationInit: Preference?) = + Preference.OnPreferenceChangeListener { _, newValue -> + conversationInit?.isEnabled = newValue as Boolean + true + } + + // The why-is-it-so-damn-hard-to-group-preferences section + override fun getCallbackFragment(): Fragment = this + + override fun onPreferenceStartScreen( + preferenceFragmentCompat: PreferenceFragmentCompat, + preferenceScreen: PreferenceScreen + ): Boolean { + fragmentManager?.beginTransaction()?.let { ft -> + val fragment = SettingsFragment() + fragment.arguments = Bundle().apply { + putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.key) + } + ft.add(R.id.item_list, fragment, preferenceScreen.key) + ft.addToBackStack(preferenceScreen.key) + ft.commit() + } + return true } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + context?.let { ctx -> view.setBackgroundColor(ContextCompat.getColor(ctx, R.color.contentBackground)) } + } + // End of the why-is-it-so-damn-hard-to-group-preferences section + // Afterthought: here it looks so simple: https://developer.android.com/guide/topics/ui/settings.html + // Remind me, why do we need to use PreferenceFragmentCompat? + companion object { const val WRITE_EXPORT_REQUEST_CODE = 1 const val READ_IMPORT_REQUEST_CODE = 2 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d57db8f..90bb80f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,4 +141,12 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="emulate_conversations_summary">Use subject to determine which messages belong together. The order will likely be wrong.</string> <string name="emulate_conversations_initialize">Group existing messages by subject</string> <string name="emulate_conversations_batch">Grouping existing messages by subject</string> + <string name="preference_group_user_experience">Behaviour</string> + <string name="preference_group_user_experience_summary">Change how messages are displayed</string> + <string name="preference_group_network_and_performance">Network &amp; Performance</string> + <string name="preference_group_network_and_performance_summary">Tweak network usage and protocol details</string> + <string name="preference_group_advanced">Advanced</string> + <string name="preference_group_advanced_summary"></string> + <string name="preference_group_experimental">Experimental</string> + <string name="preference_group_experimental_summary">Only change if you know what you\'re doing</string> </resources> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index bf163df..9ec7b51 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,42 +1,95 @@ <?xml version="1.0" encoding="utf-8"?> <android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> - <android.support.v7.preference.SwitchPreferenceCompat - android:defaultValue="true" - android:key="wifi_only" - android:summary="@string/wifi_only_summary" - android:title="@string/wifi_only" /> - <android.support.v7.preference.SwitchPreferenceCompat - android:defaultValue="true" - android:key="emulate_conversations" - android:summary="@string/emulate_conversations_summary" - android:title="@string/emulate_conversations" /> - <android.support.v7.preference.Preference - android:defaultValue="true" - android:key="emulate_conversations_initialize" - android:summary="@string/emulate_conversations_summary" - android:title="@string/emulate_conversations_initialize" /> - <android.support.v7.preference.SwitchPreferenceCompat - android:defaultValue="true" - android:key="request_acknowledgements" - android:summary="@string/request_acknowledgements_summary" - android:title="@string/request_acknowledgements" /> - <android.support.v7.preference.EditTextPreference - android:inputType="textUri" - android:key="trusted_node" - android:summary="@string/trusted_node_summary" - android:title="@string/trusted_node" /> - <android.support.v7.preference.EditTextPreference - android:defaultValue="120" - android:inputType="number" - android:key="sync_timeout" - android:summary="@string/sync_timeout_summary" - android:title="@string/sync_timeout" /> - <android.support.v7.preference.SwitchPreferenceCompat - android:defaultValue="false" - android:dependency="trusted_node" - android:key="server_pow" - android:summary="@string/server_pow_summary" - android:title="@string/server_pow" /> + + <android.support.v7.preference.PreferenceScreen + android:key="preference_ux" + android:title="@string/preference_group_user_experience" + android:summary="@string/preference_group_user_experience_summary" + android:persistent="false"> + + <android.support.v7.preference.SwitchPreferenceCompat + android:defaultValue="true" + android:key="emulate_conversations" + android:summary="@string/emulate_conversations_summary" + android:title="@string/emulate_conversations" /> + <android.support.v7.preference.Preference + android:defaultValue="true" + android:key="emulate_conversations_initialize" + android:summary="@string/emulate_conversations_summary" + android:title="@string/emulate_conversations_initialize" /> + + </android.support.v7.preference.PreferenceScreen> + + <android.support.v7.preference.PreferenceScreen + android:key="preference_network_and_performance" + android:title="@string/preference_group_network_and_performance" + android:summary="@string/preference_group_network_and_performance_summary" + android:persistent="false"> + + <android.support.v7.preference.SwitchPreferenceCompat + android:defaultValue="true" + android:key="wifi_only" + android:summary="@string/wifi_only_summary" + android:title="@string/wifi_only" /> + <android.support.v7.preference.SwitchPreferenceCompat + android:defaultValue="true" + android:key="request_acknowledgements" + android:summary="@string/request_acknowledgements_summary" + android:title="@string/request_acknowledgements" /> + + </android.support.v7.preference.PreferenceScreen> + + <android.support.v7.preference.PreferenceScreen + android:key="preference_advanced" + android:title="@string/preference_group_advanced" + android:summary="@string/preference_group_advanced_summary" + android:persistent="false"> + + <android.support.v7.preference.Preference + android:key="cleanup" + android:summary="@string/cleanup_summary" + android:title="@string/cleanup" /> + <android.support.v7.preference.Preference + android:key="export" + android:summary="@string/export_data_summary" + android:title="@string/export_data" /> + <android.support.v7.preference.Preference + android:key="import" + android:summary="@string/import_data_summary" + android:title="@string/import_data" /> + + <android.support.v7.preference.PreferenceScreen + android:key="preference_experimental" + android:title="@string/preference_group_experimental" + android:summary="@string/preference_group_experimental_summary" + android:persistent="false"> + + <android.support.v7.preference.EditTextPreference + android:inputType="textUri" + android:key="trusted_node" + android:summary="@string/trusted_node_summary" + android:title="@string/trusted_node" /> + <android.support.v7.preference.EditTextPreference + android:defaultValue="120" + android:inputType="number" + android:key="sync_timeout" + android:summary="@string/sync_timeout_summary" + android:title="@string/sync_timeout" /> + <android.support.v7.preference.SwitchPreferenceCompat + android:defaultValue="false" + android:dependency="trusted_node" + android:key="server_pow" + android:summary="@string/server_pow_summary" + android:title="@string/server_pow" /> + <android.support.v7.preference.Preference + android:key="status" + android:summary="@string/status_summary" + android:title="@string/status" /> + + </android.support.v7.preference.PreferenceScreen> + + </android.support.v7.preference.PreferenceScreen> + <android.support.v7.preference.Preference android:key="about" android:summary="@string/about_summary" @@ -49,20 +102,4 @@ android:action="android.intent.action.VIEW" android:data="@string/help_out_link" /> </android.support.v7.preference.Preference> - <android.support.v7.preference.Preference - android:key="cleanup" - android:summary="@string/cleanup_summary" - android:title="@string/cleanup" /> - <android.support.v7.preference.Preference - android:key="export" - android:summary="@string/export_data_summary" - android:title="@string/export_data" /> - <android.support.v7.preference.Preference - android:key="import" - android:summary="@string/import_data_summary" - android:title="@string/import_data" /> - <android.support.v7.preference.Preference - android:key="status" - android:summary="@string/status_summary" - android:title="@string/status" /> </android.support.v7.preference.PreferenceScreen> From be7a7f1af6010959a19ff8f42064ab2f2649ca34 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 20 Apr 2018 07:00:55 +0200 Subject: [PATCH 17/28] =?UTF-8?q?=F0=9F=8E=A8=20Identicon=20rendering=20im?= =?UTF-8?q?provements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/ch/dissem/apps/abit/Identicon.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt index 3dd417f..60a0219 100644 --- a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt +++ b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt @@ -95,7 +95,7 @@ class Identicon(input: BitmessageAddress) : Drawable() { } if (isChan) { textPaint.textSize = 2 * cellHeight - canvas.drawText("[isChan]", offsetX + width / 2, offsetY + 6.7f * cellHeight, textPaint) + canvas.drawText("[ chan ]", offsetX + width / 2, offsetY + 6.7f * cellHeight, textPaint) } } @@ -156,7 +156,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt private val backg } 4 -> { canvas.drawCircle(width / 2, height / 2, width / 2, paint) - val scale = 2f / (1f + sqrt(2f)) + val scale = 1f / (1f + sqrt(2f)) val borderScale = 0.5f - scale val w = width * scale val h = height * scale From e05d27bfbc13313fe311734f9ba441efd043faaa Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 20 Apr 2018 07:04:31 +0200 Subject: [PATCH 18/28] =?UTF-8?q?=F0=9F=8E=A8=20Conversation=20rendering?= =?UTF-8?q?=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/dissem/apps/abit/ConversationListFragment.kt | 5 +++-- .../apps/abit/adapter/SwipeableConversationAdapter.kt | 9 +++++++-- app/src/main/res/values/strings.xml | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt index 80f7427..4e9439c 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt @@ -80,7 +80,8 @@ class ConversationListFragment : Fragment(), ListHolder<Label> { if (!isLoading && !isLastPage) { if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5 - && firstVisibleItemPosition >= 0) { + && firstVisibleItemPosition >= 0 + ) { loadMoreItems() } } @@ -183,7 +184,7 @@ class ConversationListFragment : Fragment(), ListHolder<Label> { val swipeManager = RecyclerViewSwipeManager() //swipeableConversationAdapter - val adapter = SwipeableConversationAdapter().apply { + val adapter = SwipeableConversationAdapter(context).apply { setActivateOnItemClick(activateOnItemClick) } adapter.eventListener = object : SwipeableConversationAdapter.EventListener { diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt index d010fba..19f99aa 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt @@ -18,6 +18,7 @@ package ch.dissem.apps.abit.adapter import android.annotation.SuppressLint +import android.content.Context import android.graphics.Typeface import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -47,7 +48,7 @@ import java.util.* * @author Christian Basler * @see [https://github.com/h6ah4i/android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview) */ -class SwipeableConversationAdapter : +class SwipeableConversationAdapter(ctx: Context) : RecyclerView.Adapter<SwipeableConversationAdapter.ViewHolder>(), SwipeableItemAdapter<SwipeableConversationAdapter.ViewHolder>, SwipeableItemConstants { @@ -60,6 +61,8 @@ class SwipeableConversationAdapter : private var selectedPosition = -1 private var activateOnItemClick: Boolean = false + private val labelUnknown = ctx.getString(R.string.unknown) + fun setActivateOnItemClick(activateOnItemClick: Boolean) { this.activateOnItemClick = activateOnItemClick } @@ -172,7 +175,9 @@ class SwipeableConversationAdapter : // set data avatar.setImageDrawable(MultiIdenticon(item.participants)) - sender.text = item.participants.mapNotNull { it.alias }.joinToString() + sender.text = item.participants.sortedBy { + (it.alias?.let { 0 } ?: 1) + if (it.isChan) 2 else 0 + }.map { it.alias ?: labelUnknown }.distinct().joinToString() subject.text = prepareMessageExtract(item.subject) extract.text = prepareMessageExtract(item.extract) if (item.hasUnread()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90bb80f..d7fe9af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,4 +149,5 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="preference_group_advanced_summary"></string> <string name="preference_group_experimental">Experimental</string> <string name="preference_group_experimental_summary">Only change if you know what you\'re doing</string> + <string name="unknown">Unknown</string> </resources> From b1fd9d9ef9b32b239ca9c946b72289994dac0868 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 20 Apr 2018 17:49:37 +0200 Subject: [PATCH 19/28] =?UTF-8?q?=F0=9F=98=B4=20Minor=20code=20style=20imp?= =?UTF-8?q?rovements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apps/abit/ComposeMessageActivity.kt | 5 +- .../dissem/apps/abit/MessageListFragment.kt | 3 +- .../ch/dissem/apps/abit/SettingsFragment.kt | 12 ++--- app/src/main/res/xml/preferences.xml | 48 +++++++++---------- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt index ea9828c..a900abf 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt @@ -98,7 +98,8 @@ class ComposeMessageActivity : AppCompatActivity() { val prefix: String = if (subject.length >= 3 && subject.substring(0, 3).equals( "RE:", ignoreCase = true - )) { + ) + ) { "" } else { "RE: " @@ -107,7 +108,7 @@ class ComposeMessageActivity : AppCompatActivity() { } replyIntent.putExtra( EXTRA_CONTENT, - "\n\n------------------------------------------------------\n" + item.text!! + "\n\n------------------------------------------------------\n${item.text ?: ""}" ) return replyIntent } diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt index f4eac98..6597810 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt @@ -79,7 +79,8 @@ class MessageListFragment : Fragment(), ListHolder<Label> { if (!isLoading && !isLastPage) { if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5 - && firstVisibleItemPosition >= 0) { + && firstVisibleItemPosition >= 0 + ) { loadMoreItems() } } diff --git a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt index 9d61984..20d0877 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt @@ -20,7 +20,6 @@ import android.app.Activity import android.content.* import android.os.Bundle import android.os.IBinder -import android.preference.PreferenceManager import android.support.v4.app.Fragment import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider.getUriForFile @@ -173,11 +172,12 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP override fun onAttach(ctx: Context?) { super.onAttach(ctx) - (ctx as? MainActivity)?.floatingActionButton?.hide() - PreferenceManager.getDefaultSharedPreferences(ctx) - .registerOnSharedPreferenceChangeListener(this) - - (ctx as? MainActivity)?.updateTitle(getString(R.string.settings)) + ctx?.let { + if (it is MainActivity) { + it.floatingActionButton?.hide() + it.updateTitle(getString(R.string.settings)) + } + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 9ec7b51..bb4b957 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,105 +1,105 @@ <?xml version="1.0" encoding="utf-8"?> -<android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> - <android.support.v7.preference.PreferenceScreen + <PreferenceScreen android:key="preference_ux" android:title="@string/preference_group_user_experience" android:summary="@string/preference_group_user_experience_summary" android:persistent="false"> - <android.support.v7.preference.SwitchPreferenceCompat + <SwitchPreferenceCompat android:defaultValue="true" android:key="emulate_conversations" android:summary="@string/emulate_conversations_summary" android:title="@string/emulate_conversations" /> - <android.support.v7.preference.Preference + <Preference android:defaultValue="true" android:key="emulate_conversations_initialize" android:summary="@string/emulate_conversations_summary" android:title="@string/emulate_conversations_initialize" /> - </android.support.v7.preference.PreferenceScreen> + </PreferenceScreen> - <android.support.v7.preference.PreferenceScreen + <PreferenceScreen android:key="preference_network_and_performance" android:title="@string/preference_group_network_and_performance" android:summary="@string/preference_group_network_and_performance_summary" android:persistent="false"> - <android.support.v7.preference.SwitchPreferenceCompat + <SwitchPreferenceCompat android:defaultValue="true" android:key="wifi_only" android:summary="@string/wifi_only_summary" android:title="@string/wifi_only" /> - <android.support.v7.preference.SwitchPreferenceCompat + <SwitchPreferenceCompat android:defaultValue="true" android:key="request_acknowledgements" android:summary="@string/request_acknowledgements_summary" android:title="@string/request_acknowledgements" /> - </android.support.v7.preference.PreferenceScreen> + </PreferenceScreen> - <android.support.v7.preference.PreferenceScreen + <PreferenceScreen android:key="preference_advanced" android:title="@string/preference_group_advanced" android:summary="@string/preference_group_advanced_summary" android:persistent="false"> - <android.support.v7.preference.Preference + <Preference android:key="cleanup" android:summary="@string/cleanup_summary" android:title="@string/cleanup" /> - <android.support.v7.preference.Preference + <Preference android:key="export" android:summary="@string/export_data_summary" android:title="@string/export_data" /> - <android.support.v7.preference.Preference + <Preference android:key="import" android:summary="@string/import_data_summary" android:title="@string/import_data" /> - <android.support.v7.preference.PreferenceScreen + <PreferenceScreen android:key="preference_experimental" android:title="@string/preference_group_experimental" android:summary="@string/preference_group_experimental_summary" android:persistent="false"> - <android.support.v7.preference.EditTextPreference + <EditTextPreference android:inputType="textUri" android:key="trusted_node" android:summary="@string/trusted_node_summary" android:title="@string/trusted_node" /> - <android.support.v7.preference.EditTextPreference + <EditTextPreference android:defaultValue="120" android:inputType="number" android:key="sync_timeout" android:summary="@string/sync_timeout_summary" android:title="@string/sync_timeout" /> - <android.support.v7.preference.SwitchPreferenceCompat + <SwitchPreferenceCompat android:defaultValue="false" android:dependency="trusted_node" android:key="server_pow" android:summary="@string/server_pow_summary" android:title="@string/server_pow" /> - <android.support.v7.preference.Preference + <Preference android:key="status" android:summary="@string/status_summary" android:title="@string/status" /> - </android.support.v7.preference.PreferenceScreen> + </PreferenceScreen> - </android.support.v7.preference.PreferenceScreen> + </PreferenceScreen> - <android.support.v7.preference.Preference + <Preference android:key="about" android:summary="@string/about_summary" android:title="@string/about" /> - <android.support.v7.preference.Preference + <Preference android:key="help_out" android:summary="@string/help_out_summary" android:title="@string/help_out"> <intent android:action="android.intent.action.VIEW" android:data="@string/help_out_link" /> - </android.support.v7.preference.Preference> -</android.support.v7.preference.PreferenceScreen> + </Preference> +</PreferenceScreen> From 6585876b2572831d780c3caf7fe0e81ddf703df9 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Sun, 22 Apr 2018 11:29:39 +0200 Subject: [PATCH 20/28] =?UTF-8?q?=F0=9F=9A=B8=20Add=20number=20of=20messag?= =?UTF-8?q?es=20to=20conversation=20list=20item?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/SwipeableConversationAdapter.kt | 10 +- app/src/main/res/layout/conversation_row.xml | 125 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/layout/conversation_row.xml diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt index 19f99aa..ec2332d 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt @@ -82,6 +82,7 @@ class SwipeableConversationAdapter(ctx: Context) : val sender = v.findViewById<TextView>(R.id.sender)!! val subject = v.findViewById<TextView>(R.id.subject)!! val extract = v.findViewById<TextView>(R.id.text)!! + val count = v.findViewById<TextView>(R.id.count)!! override fun getSwipeableContainerView() = container } @@ -149,7 +150,7 @@ class SwipeableConversationAdapter(ctx: Context) : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) - val v = inflater.inflate(R.layout.message_row, parent, false) + val v = inflater.inflate(R.layout.conversation_row, parent, false) return ViewHolder(v) } @@ -180,6 +181,13 @@ class SwipeableConversationAdapter(ctx: Context) : }.map { it.alias ?: labelUnknown }.distinct().joinToString() subject.text = prepareMessageExtract(item.subject) extract.text = prepareMessageExtract(item.extract) + item.messages.size.let { size -> + if (size <= 1) { + count.text = "" + } else { + count.text = size.toString() + } + } if (item.hasUnread()) { sender.typeface = Typeface.DEFAULT_BOLD subject.typeface = Typeface.DEFAULT_BOLD diff --git a/app/src/main/res/layout/conversation_row.xml b/app/src/main/res/layout/conversation_row.xml new file mode 100644 index 0000000..84a8b35 --- /dev/null +++ b/app/src/main/res/layout/conversation_row.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 2015 Christian Basler + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/bg_swipe_item_neutral"> + + <FrameLayout + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/bg_item_normal_state" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground" + tools:ignore="UselessParent"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground"> + + <ImageView + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_margin="16dp" + android:src="@color/colorPrimaryDark" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/sender" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignTop="@id/avatar" + android:layout_marginTop="-5dp" + android:layout_toEndOf="@id/avatar" + android:ellipsize="end" + android:lines="1" + android:paddingBottom="0dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:paddingTop="0dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + tools:text="Sender" /> + + <TextView + android:id="@+id/subject" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_toStartOf="@id/count" + android:layout_below="@id/sender" + android:layout_toEndOf="@id/avatar" + android:ellipsize="end" + android:lines="1" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="Subject" /> + + <TextView + android:id="@+id/text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_below="@id/subject" + android:layout_toEndOf="@id/avatar" + android:ellipsize="end" + android:gravity="center_vertical" + android:lines="1" + android:paddingBottom="8dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="Text" /> + + <ImageView + android:id="@+id/status" + android:layout_width="24dp" + android:layout_height="wrap_content" + android:layout_alignBottom="@id/avatar" + android:layout_alignEnd="@id/avatar" + android:layout_marginBottom="-8dp" + android:layout_marginEnd="-8dp" + android:tint="@color/colorAccent" + tools:ignore="ContentDescription" + tools:src="@drawable/ic_notification_proof_of_work" /> + + <TextView + android:id="@+id/count" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_alignBottom="@id/subject" + android:layout_alignParentEnd="true" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textAlignment="center" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="@color/md_blue_grey_500" + tools:text="0" /> + + </RelativeLayout> + + </FrameLayout> + +</FrameLayout> From 60c4a4d8a0c92ef950fbde9e71bc6e20a3ddf9fe Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Sun, 22 Apr 2018 12:06:10 +0200 Subject: [PATCH 21/28] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Kotlin=20version?= =?UTF-8?q?=20bump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8e37169..6fca7cf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.31' + ext.kotlin_version = '1.2.40' ext.anko_version = '0.10.4' repositories { jcenter() From 0b432b6a67ad8f64605f5a4afe8dadb698114922 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Wed, 23 May 2018 19:04:27 +0200 Subject: [PATCH 22/28] =?UTF-8?q?=F0=9F=94=83=20Switch=20showcase=20librar?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Which, unfortunately, pulls along a lot of other changes (mostly for the better) --- app/build.gradle | 8 +- .../apps/abit/ConversationDetailFragment.kt | 66 +------------ .../java/ch/dissem/apps/abit/MainActivity.kt | 61 ++++++------ .../apps/abit/adapter/ConversationAdapter.kt | 16 +++- .../adapter/SwipeableConversationAdapter.kt | 2 +- .../layout/fragment_conversation_detail.xml | 96 +++++++++---------- .../main/res/layout/item_message_detail.xml | 14 +-- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 12 --- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 12 files changed, 107 insertions(+), 176 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 330ee5d..ce56baa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,11 +54,11 @@ android { //ext.jabitVersion = '2.0.4' ext.jabitVersion = 'feature-refactoring-SNAPSHOT' -ext.supportVersion = '27.1.0' +ext.supportVersion = '27.1.1' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.anko:anko:$anko_version" @@ -96,12 +96,12 @@ dependencies { implementation 'com.google.zxing:core:3.3.2' implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.0' - implementation 'com.github.amlcurran.showcaseview:library:5.4.3' + implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0@aar' implementation('com.github.h6ah4i:android-advancedrecyclerview:0.11.0@aar') { transitive = true } implementation 'com.github.angads25:filepicker:1.1.1' - implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.15.0' diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt index 3030c57..87f9cba 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt @@ -16,22 +16,14 @@ package ch.dissem.apps.abit -import android.content.Context -import android.content.Intent import android.os.Bundle import android.support.v4.app.Fragment import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView import android.view.* -import android.widget.ImageView -import android.widget.TextView import ch.dissem.apps.abit.adapter.ConversationAdapter import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.util.Drawables -import ch.dissem.apps.abit.util.Strings.prepareMessageExtract -import ch.dissem.apps.abit.util.getDrawable import ch.dissem.bitmessage.entity.Conversation -import ch.dissem.bitmessage.entity.Plaintext import com.mikepenz.google_material_typeface_library.GoogleMaterial import kotlinx.android.synthetic.main.fragment_conversation_detail.* @@ -78,7 +70,8 @@ class ConversationDetailFragment : Fragment() { item?.let { item -> subject.text = item.subject avatar.setImageDrawable(MultiIdenticon(item.participants)) - messages.adapter = ConversationAdapter(ctx, this@ConversationDetailFragment, item) + messages.adapter = + ConversationAdapter(ctx, this@ConversationDetailFragment, item, Singleton.currentLabel.value) messages.layoutManager = LinearLayoutManager(activity) } } @@ -122,61 +115,6 @@ class ConversationDetailFragment : Fragment() { return false } - private class RelatedMessageAdapter internal constructor( - private val ctx: Context, - private val messages: List<Plaintext> - ) : RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder>() { - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): RelatedMessageAdapter.ViewHolder { - val context = parent.context - val inflater = LayoutInflater.from(context) - - // Inflate the custom layout - val contactView = inflater.inflate(R.layout.item_message_minimized, parent, false) - - // Return a new holder instance - return ViewHolder(contactView) - } - - // Involves populating data into the item through holder - override fun onBindViewHolder(viewHolder: RelatedMessageAdapter.ViewHolder, position: Int) { - // Get the data model based on position - val message = messages[position] - - viewHolder.avatar.setImageDrawable(Identicon(message.from)) - viewHolder.status.setImageResource(message.status.getDrawable()) - viewHolder.sender.text = message.from.toString() - viewHolder.extract.text = prepareMessageExtract(message.text) - viewHolder.item = message - } - - // Returns the total count of items in the list - override fun getItemCount() = messages.size - - internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - internal val avatar = itemView.findViewById<ImageView>(R.id.avatar) - internal val status = itemView.findViewById<ImageView>(R.id.status) - internal val sender = itemView.findViewById<TextView>(R.id.sender) - internal val extract = itemView.findViewById<TextView>(R.id.text) - internal var item: Plaintext? = null - - init { - itemView.setOnClickListener { - if (ctx is MainActivity) { - item?.let { ctx.onItemSelected(it) } - } else { - val detailIntent = Intent(ctx, MessageDetailActivity::class.java) - detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item) - ctx.startActivity(detailIntent) - } - } - } - } - } - companion object { /** * The fragment argument representing the item ID that this fragment diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt index 33d1bf5..2ed025f 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt @@ -17,15 +17,14 @@ package ch.dissem.apps.abit import android.content.Intent -import android.graphics.Point +import android.graphics.Canvas +import android.graphics.Paint import android.os.Bundle import android.support.annotation.DrawableRes import android.support.v4.app.Fragment import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.View -import android.view.ViewGroup -import android.widget.RelativeLayout import ch.dissem.apps.abit.drawer.ProfileImageListener import ch.dissem.apps.abit.drawer.ProfileSelectionListener import ch.dissem.apps.abit.listener.ListSelectionListener @@ -42,7 +41,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label -import com.github.amlcurran.showcaseview.ShowcaseView import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.IconicsDrawable @@ -59,6 +57,9 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu import kotlinx.android.synthetic.main.activity_main.* import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread +import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView +import uk.co.deanwild.materialshowcaseview.shape.Shape +import uk.co.deanwild.materialshowcaseview.target.Target import java.io.Serializable import java.lang.ref.WeakReference import java.util.* @@ -150,33 +151,33 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { SyncAdapter.stopSync(this) } if (drawer.isDrawerOpen) { - val lps = RelativeLayout.LayoutParams( - ViewGroup - .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT - ).apply { - addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) - addRule(RelativeLayout.ALIGN_PARENT_LEFT) - val margin = ((resources.displayMetrics.density * 12) as Number).toInt() - setMargins(margin, margin, margin, margin) - } - - ShowcaseView.Builder(this) - .withMaterialShowcase() - .setStyle(R.style.CustomShowcaseTheme) - .setContentTitle(R.string.full_node) + MaterialShowcaseView.Builder(this) + .setMaskColour(R.color.colorPrimary) + .setTitleText(R.string.full_node) .setContentText(R.string.full_node_description) - .setTarget { - val view = drawer.stickyFooter - val location = IntArray(2) - view.getLocationInWindow(location) - val x = location[0] + 7 * view.width / 8 - val y = location[1] + view.height / 2 - Point(x, y) - } - .replaceEndButton(R.layout.showcase_button) - .hideOnTouchOutside() - .build() - .setButtonPosition(lps) + .setDismissOnTouch(true) + .setDismissText(R.string.got_it) + .setShape(object : Shape { + var w = 0 + var h = 0 + + override fun updateTarget(target: Target) { + w = target.bounds.width() + h = target.bounds.height() + } + + override fun getHeight() = h + + override fun draw(canvas: Canvas, paint: Paint, x: Int, y: Int, padding: Int) { + val r = h.toFloat() / 2 + canvas.drawCircle(x + w / 2 - r * 1.8f, y.toFloat(), r, paint) + } + + override fun getWidth() = w + }) + .setTarget(drawer.stickyFooter) + .setDelay(1000) + .show() } } diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt index c07ec0d..2523365 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt @@ -24,11 +24,14 @@ import ch.dissem.bitmessage.ports.MessageRepository class ConversationAdapter internal constructor( ctx: Context, private val parent: Fragment, - private val conversation: Conversation + conversation: Conversation, + private val label: Label? ) : RecyclerView.Adapter<ConversationAdapter.ViewHolder>() { private val messageRepo = Singleton.getMessageRepository(ctx) + private var filteredMessages = conversation.messages.filter { label == null || it.labels.any { it == label } } + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int @@ -46,7 +49,7 @@ class ConversationAdapter internal constructor( // Involves populating data into the item through holder override fun onBindViewHolder(viewHolder: ConversationAdapter.ViewHolder, position: Int) { // Get the data model based on position - val message = conversation.messages[position] + val message = filteredMessages[position] viewHolder.apply { item = message @@ -83,9 +86,9 @@ class ConversationAdapter internal constructor( } } - override fun getItemCount() = conversation.messages.size + override fun getItemCount() = filteredMessages.size - class ViewHolder( + inner class ViewHolder( itemView: View, parent: Fragment, messageRepo: MessageRepository @@ -114,9 +117,12 @@ class ConversationAdapter internal constructor( Singleton.labeler.delete(item) messageRepo.save(item) } + filteredMessages.indexOf(item).let { i -> + filteredMessages -= item + notifyItemRemoved(i) + } MainActivity.apply { updateUnread() - onBackPressed() } true } diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt index ec2332d..3520fb7 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/SwipeableConversationAdapter.kt @@ -181,7 +181,7 @@ class SwipeableConversationAdapter(ctx: Context) : }.map { it.alias ?: labelUnknown }.distinct().joinToString() subject.text = prepareMessageExtract(item.subject) extract.text = prepareMessageExtract(item.extract) - item.messages.size.let { size -> + item.messages.count { it.labels.contains(label) }.let { size -> if (size <= 1) { count.text = "" } else { diff --git a/app/src/main/res/layout/fragment_conversation_detail.xml b/app/src/main/res/layout/fragment_conversation_detail.xml index 64a8b25..0fb1a5d 100644 --- a/app/src/main/res/layout/fragment_conversation_detail.xml +++ b/app/src/main/res/layout/fragment_conversation_detail.xml @@ -1,59 +1,53 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent"> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" - <RelativeLayout - android:layout_width="match_parent" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true" + android:focusableInTouchMode="true" + android:orientation="vertical"> + + <TextView + android:id="@+id/subject" + android:layout_width="0dp" android:layout_height="wrap_content" - android:fitsSystemWindows="true" - android:focusableInTouchMode="true" - android:orientation="vertical" - android:paddingBottom="64dp"> + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_toStartOf="@+id/avatar" + android:elegantTextHeight="false" + android:enabled="false" + android:gravity="center_vertical" + android:padding="16dp" + android:textAppearance="?android:attr/textAppearanceLarge" + tools:ignore="UnusedAttribute" + tools:text="Subject" /> - <TextView - android:id="@+id/subject" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:layout_toStartOf="@+id/avatar" - android:elegantTextHeight="false" - android:enabled="false" - android:gravity="center_vertical" - android:padding="16dp" - android:textAppearance="?android:attr/textAppearanceLarge" - tools:ignore="UnusedAttribute" - tools:text="Subject" /> - - <ImageView - android:id="@+id/avatar" - android:layout_width="40dp" - android:layout_height="40dp" - android:layout_alignParentEnd="true" - android:layout_alignParentTop="true" - android:layout_margin="10dp" - android:src="@color/colorAccent" - tools:ignore="ContentDescription" /> + <ImageView + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentEnd="true" + android:layout_alignParentTop="true" + android:layout_margin="10dp" + android:src="@color/colorAccent" + tools:ignore="ContentDescription" /> - <View - android:id="@+id/divider" - android:layout_width="fill_parent" - android:layout_height="2dip" - android:layout_below="@id/subject" - android:background="@color/divider" /> + <View + android:id="@+id/divider" + android:layout_width="fill_parent" + android:layout_height="2dip" + android:layout_below="@id/subject" + android:background="@color/divider" /> - <android.support.v7.widget.RecyclerView - android:id="@+id/messages" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@+id/divider" - android:animateLayoutChanges="true" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:layout_marginBottom="16dp"/> + <android.support.v7.widget.RecyclerView + android:id="@+id/messages" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/divider" + android:scrollbarStyle="outsideOverlay" + android:scrollbars="vertical" + tools:listitem="@layout/item_message_detail" /> - </RelativeLayout> -</ScrollView> +</RelativeLayout> diff --git a/app/src/main/res/layout/item_message_detail.xml b/app/src/main/res/layout/item_message_detail.xml index 203903b..0c6f5e4 100644 --- a/app/src/main/res/layout/item_message_detail.xml +++ b/app/src/main/res/layout/item_message_detail.xml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:fitsSystemWindows="true" - android:focusableInTouchMode="true" - android:orientation="vertical"> + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:fitsSystemWindows="true" + android:focusableInTouchMode="true" + android:orientation="vertical"> <RelativeLayout android:id="@+id/header" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 5d61d98..b397dc8 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -15,6 +15,7 @@ --> <resources> + <dimen name="action_bar_offset">66dp</dimen> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7fe9af..c4cdb65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -150,4 +150,5 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="preference_group_experimental">Experimental</string> <string name="preference_group_experimental_summary">Only change if you know what you\'re doing</string> <string name="unknown">Unknown</string> + <string name="ok">OK</string> </resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 49a7b0c..2f56be4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,18 +8,6 @@ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> </style> - <style name="CustomShowcaseTheme" parent="ShowcaseView"> - <item name="sv_backgroundColor">#eeffc107</item> - <item name="sv_showcaseColor">#ffc107</item> - <item name="sv_buttonText">Hide</item> - <item name="sv_tintButtonColor">false</item> - <item name="sv_titleTextAppearance">@style/CustomTitle</item> - </style> - - <style name="CustomTitle" parent="TextAppearance.ShowcaseView.Title"> - <item name="android:textColor">@color/colorAccent</item> - </style> - <style name="FixedDialog" parent="Theme.AppCompat.Light.Dialog.MinWidth"> <item name="windowNoTitle">false</item> </style> diff --git a/build.gradle b/build.gradle index 6fca7cf..8aa1c51 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.2.40' + ext.kotlin_version = '1.2.41' ext.anko_version = '0.10.4' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.1' + classpath 'com.android.tools.build:gradle:3.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cefb6bf..5c92cf6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip From 9cc07f73ae492480e6b93119db404883df23c88d Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Thu, 24 May 2018 21:08:06 +0200 Subject: [PATCH 23/28] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20ANR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ch/dissem/apps/abit/service/BitmessageService.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt index 2ce3b3f..523e832 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt @@ -28,6 +28,7 @@ import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NO import ch.dissem.apps.abit.util.Preferences import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.utils.Property +import org.jetbrains.anko.doAsync /** * Define a Service that returns an IBinder for the @@ -87,7 +88,9 @@ class BitmessageService : Service() { running = false notification.showShutdown() cleanupHandler.removeCallbacks(cleanupTask) - bmc.cleanup() + doAsync { + bmc.cleanup() + } unregisterReceiver(connectivityReceiver) stopSelf() } From 90bb538692b981ac35ad0adca9dbe771f7111e89 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 25 May 2018 20:48:42 +0200 Subject: [PATCH 24/28] =?UTF-8?q?=F0=9F=9A=80=20Improve=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/dissem/apps/abit/ConversationDetailFragment.kt | 11 +++++++---- app/src/main/java/ch/dissem/apps/abit/MainActivity.kt | 4 ++-- .../java/ch/dissem/apps/abit/MessageDetailActivity.kt | 9 ++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt index 87f9cba..f611ff1 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt @@ -26,6 +26,7 @@ import ch.dissem.apps.abit.util.Drawables import ch.dissem.bitmessage.entity.Conversation import com.mikepenz.google_material_typeface_library.GoogleMaterial import kotlinx.android.synthetic.main.fragment_conversation_detail.* +import java.util.* /** * A fragment representing a single Message detail screen. @@ -38,17 +39,18 @@ class ConversationDetailFragment : Fragment() { /** * The content this fragment is presenting. */ + private var itemId: UUID? = null private var item: Conversation? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { arguments -> - if (arguments.containsKey(ARG_ITEM)) { + if (arguments.containsKey(ARG_ITEM_ID)) { // Load the dummy content specified by the fragment // arguments. In a real-world scenario, use a Loader // to load content from a content provider. - item = arguments.getSerializable(ARG_ITEM) as Conversation + itemId = arguments.getSerializable(ARG_ITEM_ID) as UUID } } setHasOptionsMenu(true) @@ -66,6 +68,8 @@ class ConversationDetailFragment : Fragment() { val ctx = activity ?: throw IllegalStateException("Fragment is not attached to an activity") + item = itemId?.let { Singleton.getConversationService(ctx).getConversation(it) } + // Show the dummy content as text in a TextView. item?.let { item -> subject.text = item.subject @@ -104,7 +108,6 @@ class ConversationDetailFragment : Fragment() { item.messages.forEach { Singleton.labeler.archive(it) messageRepo.save(it) - } MainActivity.apply { updateUnread() } return true @@ -120,6 +123,6 @@ class ConversationDetailFragment : Fragment() { * The fragment argument representing the item ID that this fragment * represents. */ - const val ARG_ITEM = "item" + const val ARG_ITEM_ID = "item_id" } } diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt index 2ed025f..e7d5b88 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt @@ -471,7 +471,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { is Conversation -> { ConversationDetailFragment().apply { arguments = Bundle().apply { - putSerializable(ConversationDetailFragment.ARG_ITEM, item) + putSerializable(ConversationDetailFragment.ARG_ITEM_ID, item.id) } } } @@ -508,7 +508,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { val detailIntent = when (item) { is Conversation -> { Intent(this, MessageDetailActivity::class.java).apply { - putExtra(ConversationDetailFragment.ARG_ITEM, item) + putExtra(ConversationDetailFragment.ARG_ITEM_ID, item.id) } } is Plaintext -> { diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt index bb29b16..e1c449c 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.support.v4.app.NavUtils import android.view.MenuItem import ch.dissem.bitmessage.entity.Conversation +import ch.dissem.bitmessage.entity.Plaintext /** @@ -36,10 +37,12 @@ class MessageDetailActivity : DetailActivity() { val arguments = Bundle() val item = intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM) arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item) - val fragment = if (item is Conversation) { - ConversationDetailFragment() - } else { + val itemId = intent.getSerializableExtra(ConversationDetailFragment.ARG_ITEM_ID) + arguments.putSerializable(ConversationDetailFragment.ARG_ITEM_ID, itemId) + val fragment = if (item is Plaintext) { MessageDetailFragment() + } else { + ConversationDetailFragment() } fragment.arguments = arguments supportFragmentManager.beginTransaction() From 2ddd78dfe24d60ff7271e1da86a847fc1b1e85ec Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Mon, 4 Jun 2018 12:55:00 +0200 Subject: [PATCH 25/28] =?UTF-8?q?=F0=9F=93=A1=20Improve=20and=20simplify?= =?UTF-8?q?=20connectivity=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../abit/service/StartupNodeOnWifiService.kt | 12 ++--- .../ch/dissem/apps/abit/util/Constants.kt | 1 + .../ch/dissem/apps/abit/util/NetworkUtils.kt | 51 ++++++++++++------- .../ch/dissem/apps/abit/util/Preferences.kt | 24 +++++---- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 6 +++ 7 files changed, 61 insertions(+), 36 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c41ccd..a9d4f55 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> diff --git a/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt b/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt index 5a3ee01..372393a 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt @@ -24,11 +24,9 @@ class StartupNodeOnWifiService : JobService() { return true } - override fun onStopJob(params: JobParameters?) = if (Preferences.isWifiOnly(this)) { - // Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes - // depending on Android version. - Preferences.isFullNodeActive(this) - } else { - false - } + /** + * Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes + * depending on Android version. + */ + override fun onStopJob(params: JobParameters?) = Preferences.isFullNodeActive(this) } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt b/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt index a57a4b2..5fd22a9 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Constants.kt @@ -23,6 +23,7 @@ import java.util.regex.Pattern */ object Constants { const val PREFERENCE_WIFI_ONLY = "wifi_only" + const val PREFERENCE_REQUIRE_CHARGING = "require_charging" const val PREFERENCE_EMULATE_CONVERSATIONS = "emulate_conversations" const val PREFERENCE_TRUSTED_NODE = "trusted_node" const val PREFERENCE_SYNC_TIMEOUT = "sync_timeout" diff --git a/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt b/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt index 7dd32cb..efe559d 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt @@ -19,30 +19,37 @@ object NetworkUtils { fun enableNode(ctx: Context, ask: Boolean = true) { Preferences.setFullNodeActive(ctx, true) - if (Preferences.isWifiOnly(ctx)) { - if (Preferences.isConnectionAllowed(ctx)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - scheduleNodeStart(ctx) - } else { - doStartBitmessageService(ctx) - MainActivity.updateNodeSwitch() - } - } else if (ask) { - val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java) - if (ctx !is Activity) { - dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) - } - ctx.startActivity(dialogIntent) - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (Preferences.isConnectionAllowed(ctx) || !ask) { scheduleNodeStart(ctx) + } else { + askForConnection(ctx) } } else { - doStartBitmessageService(ctx) - MainActivity.updateNodeSwitch() + if (Preferences.isWifiOnly(ctx)) { + if (Preferences.isConnectionAllowed(ctx)) { + doStartBitmessageService(ctx) + MainActivity.updateNodeSwitch() + } else if (ask) { + askForConnection(ctx) + } + } else { + doStartBitmessageService(ctx) + MainActivity.updateNodeSwitch() + } } } + private fun askForConnection(ctx: Context) { + val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java) + if (ctx !is Activity) { + dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + } + ctx.startActivity(dialogIntent) + } + fun doStartBitmessageService(ctx: Context) { ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java)) } @@ -56,8 +63,14 @@ object NetworkUtils { fun scheduleNodeStart(ctx: Context) { val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java) val builder = JobInfo.Builder(0, serviceComponent) - builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) + if (Preferences.isWifiOnly(ctx)) { + builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) + } + if (Preferences.requireCharging(ctx)) { + builder.setRequiresCharging(true) + } builder.setBackoffCriteria(0L, JobInfo.BACKOFF_POLICY_LINEAR) + builder.setPersisted(true) val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(builder.build()) } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt index 7b75161..b7249fd 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt @@ -22,6 +22,7 @@ import ch.dissem.apps.abit.notification.ErrorNotification import ch.dissem.apps.abit.util.Constants.PREFERENCE_EMULATE_CONVERSATIONS import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK +import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUIRE_CHARGING import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY @@ -78,17 +79,13 @@ object Preferences { return 8444 } - fun getTimeoutInSeconds(ctx: Context): Long = - getPreference(ctx, PREFERENCE_SYNC_TIMEOUT)?.toLong() ?: 120 + fun getTimeoutInSeconds(ctx: Context): Long = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT)?.toLong() ?: 120 - private fun getPreference(ctx: Context, name: String): String? = - ctx.defaultSharedPreferences.getString(name, null) + private fun getPreference(ctx: Context, name: String): String? = ctx.defaultSharedPreferences.getString(name, null) - fun isConnectionAllowed(ctx: Context) = - !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered + fun isConnectionAllowed(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered - fun isWifiOnly(ctx: Context) = - ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true) + fun isWifiOnly(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true) fun setWifiOnly(ctx: Context, status: Boolean) { ctx.defaultSharedPreferences.edit() @@ -96,6 +93,14 @@ object Preferences { .apply() } + fun requireCharging(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUIRE_CHARGING, true) + + fun setRequireCharging(ctx: Context, status: Boolean) { + ctx.defaultSharedPreferences.edit() + .putBoolean(PREFERENCE_REQUIRE_CHARGING, status) + .apply() + } + fun isEmulateConversations(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_EMULATE_CONVERSATIONS, true) @@ -111,8 +116,7 @@ object Preferences { fun getExportDirectory(ctx: Context) = File(ctx.filesDir, "exports") - fun requestAcknowledgements(ctx: Context) = - ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true) + fun requestAcknowledgements(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true) fun cleanupExportDirectory(ctx: Context) { val exportDirectory = getExportDirectory(ctx) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c4cdb65..bd1f3a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,6 +149,8 @@ As an alternative you could configure a trusted node in the settings, but as of <string name="preference_group_advanced_summary"></string> <string name="preference_group_experimental">Experimental</string> <string name="preference_group_experimental_summary">Only change if you know what you\'re doing</string> + <string name="require_charging">Require charging</string> + <string name="require_charging_summary">Only connect when device is plugged in</string> <string name="unknown">Unknown</string> <string name="ok">OK</string> </resources> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index bb4b957..32629e5 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -31,6 +31,12 @@ android:key="wifi_only" android:summary="@string/wifi_only_summary" android:title="@string/wifi_only" /> + <SwitchPreferenceCompat + android:defaultValue="false" + android:key="require_charging" + android:enabled="@bool/is_post_api_21" + android:summary="@string/require_charging_summary" + android:title="@string/require_charging" /> <SwitchPreferenceCompat android:defaultValue="true" android:key="request_acknowledgements" From ec4615b63924f30e5dc732aed3b2de39d2ba9b5f Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 12 Jun 2018 16:52:42 +0200 Subject: [PATCH 26/28] =?UTF-8?q?=F0=9F=90=98=20Tweak=20memory=20usage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt index b311064..e03beec 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt @@ -32,6 +32,7 @@ import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.payload.Pubkey import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.factory.BufferPool import ch.dissem.bitmessage.networking.nio.NioNetworkHandler import ch.dissem.bitmessage.ports.DefaultLabeler import ch.dissem.bitmessage.utils.ConversationService @@ -101,6 +102,7 @@ object Singleton { fun getBitmessageContext(context: Context): BitmessageContext = init({ bitmessageContext }, { bitmessageContext = it }) { + BufferPool.setLimit(4) BitmessageContext.build { TTL.pubkey = 2 * DAY val ctx = context.applicationContext @@ -117,7 +119,7 @@ object Singleton { labelRepo = AndroidLabelRepository(sqlHelper, ctx) messageRepo = AndroidMessageRepository(sqlHelper) proofOfWorkRepo = AndroidProofOfWorkRepository(sqlHelper).also { powRepo = it } - networkHandler = NioNetworkHandler() + networkHandler = NioNetworkHandler(4) listener = getMessageListener(ctx) labeler = Singleton.labeler preferences.sendPubkeyOnIdentityCreation = false From 9e7f24776336d20e6b841742ec93a96b7dd5302d Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 12 Jun 2018 16:54:00 +0200 Subject: [PATCH 27/28] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Bump=20android=20?= =?UTF-8?q?build=20tools=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8aa1c51..86e3333 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.2' + classpath 'com.android.tools.build:gradle:3.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' From 85f114a33d3391e7c57d03c9efea17406e4aeae8 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 12 Jun 2018 16:56:15 +0200 Subject: [PATCH 28/28] =?UTF-8?q?=F0=9F=94=8C=20Add=20preference=20to=20co?= =?UTF-8?q?nnect=20on=20charging=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ch/dissem/apps/abit/SettingsFragment.kt | 20 ++++++++++++++++++- .../apps/abit/service/BitmessageService.kt | 6 +++++- .../ch/dissem/apps/abit/util/NetworkUtils.kt | 2 +- .../ch/dissem/apps/abit/util/Preferences.kt | 20 ++++++++++++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt index 20d0877..da61c5d 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt @@ -18,12 +18,15 @@ package ch.dissem.apps.abit import android.app.Activity import android.content.* +import android.os.Build +import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Bundle import android.os.IBinder import android.support.v4.app.Fragment import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider.getUriForFile import android.support.v7.preference.Preference +import android.support.v7.preference.Preference.OnPreferenceChangeListener import android.support.v7.preference.PreferenceFragmentCompat import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.SwitchPreferenceCompat @@ -36,6 +39,7 @@ import ch.dissem.apps.abit.synchronization.SyncAdapter import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE import ch.dissem.apps.abit.util.Exports +import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.Preferences import ch.dissem.bitmessage.entity.Plaintext import com.mikepenz.aboutlibraries.Libs @@ -61,6 +65,11 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP findPreference("import")?.onPreferenceClickListener = importClickListener() findPreference("status")?.onPreferenceClickListener = statusClickListener() + connectivityChangeListener().let { + findPreference("wifi_only")?.onPreferenceChangeListener = it + findPreference("require_charging")?.onPreferenceChangeListener = it + } + val emulateConversations = findPreference("emulate_conversations") as? SwitchPreferenceCompat val conversationInit = findPreference("emulate_conversations_initialize") @@ -247,11 +256,20 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP } private fun emulateConversationChangeListener(conversationInit: Preference?) = - Preference.OnPreferenceChangeListener { _, newValue -> + OnPreferenceChangeListener { _, newValue -> conversationInit?.isEnabled = newValue as Boolean true } + private fun connectivityChangeListener() = + OnPreferenceChangeListener { preference, newValue -> + val ctx = context + if (ctx != null && Build.VERSION.SDK_INT >= LOLLIPOP && Preferences.isFullNodeActive(ctx)) { + NetworkUtils.scheduleNodeStart(ctx) + } + true + } + // The why-is-it-so-damn-hard-to-group-preferences section override fun getCallbackFragment(): Fragment = this diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt index 523e832..edc3f87 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager +import android.os.BatteryManager import android.os.Handler import ch.dissem.apps.abit.notification.NetworkNotification import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID @@ -61,7 +62,10 @@ class BitmessageService : Service() { override fun onCreate() { registerReceiver( connectivityReceiver, - IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) + IntentFilter().apply { + addAction(ConnectivityManager.CONNECTIVITY_ACTION) + addAction(Intent.ACTION_BATTERY_CHANGED) + } ) notification = NetworkNotification(this) running = false diff --git a/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt b/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt index efe559d..c401486 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt @@ -61,6 +61,7 @@ object NetworkUtils { @RequiresApi(Build.VERSION_CODES.LOLLIPOP) fun scheduleNodeStart(ctx: Context) { + val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java) val builder = JobInfo.Builder(0, serviceComponent) if (Preferences.isWifiOnly(ctx)) { @@ -71,7 +72,6 @@ object NetworkUtils { } builder.setBackoffCriteria(0L, JobInfo.BACKOFF_POLICY_LINEAR) builder.setPersisted(true) - val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(builder.build()) } } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt index b7249fd..44265b1 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt @@ -26,12 +26,18 @@ import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUIRE_CHARGING import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY +import org.jetbrains.anko.batteryManager import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.defaultSharedPreferences import org.slf4j.LoggerFactory import java.io.File import java.io.IOException import java.net.InetAddress +import android.os.BatteryManager +import android.content.Intent +import android.content.IntentFilter +import android.os.Build + /** * @author Christian Basler @@ -83,7 +89,19 @@ object Preferences { private fun getPreference(ctx: Context, name: String): String? = ctx.defaultSharedPreferences.getString(name, null) - fun isConnectionAllowed(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered + fun isConnectionAllowed(ctx: Context) = isAllowedForWiFi(ctx) && isAllowedForCharging(ctx) + + private fun isAllowedForWiFi(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered + + private fun isAllowedForCharging(ctx: Context) = !requireCharging(ctx) || isCharging(ctx) + + private fun isCharging(ctx: Context) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ctx.batteryManager.isCharging + } else { + val intent = ctx.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1) + status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL + } fun isWifiOnly(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true)