From 3d68b8e62984ba28e27f45f6c6858f39bfc6bf8e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2020 18:36:13 +0000 Subject: [PATCH 001/110] Bump gradle from 4.0.2 to 4.1.0 (#996) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 608e3ce5c..767e47909 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:4.0.2' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.4' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0' From cb09ca13dcaeee798ce7ba97ad84bb2426a4bc9b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2020 18:36:31 +0000 Subject: [PATCH 002/110] Bump junit from 4.13 to 4.13.1 (#995) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b1ba7e62f..363eb74bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation "com.amitshekhar.android:debug-db:1.0.6" - testImplementation "junit:junit:4.13" + testImplementation "junit:junit:4.13.1" testImplementation "io.mockk:mockk:$mockk" testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9' From c00b5edaf78ffc7ba51f63945c6df889988e62e9 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Thu, 22 Oct 2020 16:17:52 +0200 Subject: [PATCH 003/110] Make AppGallery badge background transparent. (#997) --- appgallery_badge.png | Bin 56482 -> 62865 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/appgallery_badge.png b/appgallery_badge.png index ce8860758169eb6c425b9dbd8ca9e05a7d9381ea..ecd154ea91ca28b1d8af52ec1f47229bd33ce92f 100644 GIT binary patch literal 62865 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 Q1_lNOPgg&ebxsLQ0B`S%3IG5A literal 56482 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Üg*l+Q}ox`oYGfH{;n+cvf0?W2qm?I@iA85O$H{C-S$3Vt z9}i8kPSRSM?RqJf-<t33@3k^44X+bVz4!|<)2g<^M45HmMjn*344JT=)a`sPOAg>%A< z0_S;5DKr=APrg{7H~V4QQ$7BP_EV?&o;ArUxnMVY?yq9w8m;>0s?*h9Uy5C|>J0B( zO&8}tr=;u*)@hvGualN#?DNQVJR^3{KJoL~JcsDdi}W^YCkDs}{PXtXJ(1p7c6~}U zTREGfw$HV$K7kKU&po(&D~W5BmFj_?)>c^=D;MqlBCNLdfsupSN|*g6hs!rl@MBxl zDcE>SDXVJ%XX15k+Yg~j^;up!#~)EqP`zNn$m``fL4f^ih@eA{+RKS)UOujFv%FI? zm;_cS-BEktDn3QQC-l>+8)_z8OVzlyC=^&Y8)#1P5S4Kd{f-5@&UM(t( zP`fZO(1b-}>m046XBy7$NnvgdI<#>5kCx~Zm42R@2hUo2<|#P@v}!JB-s==@O#j)Mq7pBw)l>bdb^ZU^_2HKvs4iBw`S3n&cmDU6cWxR>2a8Uf>)aZl zA#=KQ(bt=5lLgxUh9uVp2P<0^X^7qG47};=1?slh@t<;r{zw?b-Kxz24~A z{BJ*DF8}iSD42 zPmLPi1+n%DrK@i-IlWMs;YsVniJnHxhP{5A4_0uuyh*I}x*D`gaIW0NRZK?$yhEKA zu=nL0xSQN2w=wJN)w?~pQ-hbTZ;3vR7iitT} zbfzeTf7=viC-iM{XNHZWy6HOYCq^49f^97Ah31NVeO|ii*8elXW&1wF=;zdFJZYYt z|L?=ciOkkzU!-46PVLOGOK(cfSUCCDyM%>X!uWJ1M4B7hADE|kEV()DR6+NXBBw?! zepPWc1Lt+CSQ!gb#$hyTc@!Se4-w7T575KNdPvdzFj zOsYT6)}mO%{g4BTT4J+BN1&DMqu@h=lPAT!XiUpG;k|Q}5JN%8)uPUPcRe@vo6ezY zrJPKB!`!7W?3l&c^60{(>3oT2ohEupDdap>YT3HZ!jxlqMQc?_jczN&N2;cPd!HO1T54L5@&KYyJepGq<$h_*#tCu}WlbP1!YTWyO zzdR#)yTeP}gL|Dc`)avw?(5D;e3UgYChCIe8~3;QJr7vFPH%HA{LHNqXE*1x>*a?5 zT7q6vt5?dk-sujHwSPG6f5G|6yQ|IZez(lOQ<1t{cKf^tjT+NpzgV+)50~VWjD>6F z`#)7`jj_2Tz;y8xH zpYG33cwkJCAgoHKd1^c?iaNX{!>Qeqd|awJ9CXoidC z=OgY63jFb}mHUJp`rj;n#iEhubb$SXrfK{~%~S@NRj)WAD%qNXcdg2Gx+`k)Vx6MM zjqm+4#=wT+sqme^I8y%&>Ap&9#b0S+~4NZFsG8s5iB;EkP&JDRiMo zl((*N%8VaVUNJqqb|EW@W2KW%cU!|32ZkTBl#?z@aqQYtw&=x=Fx#+K>&qEG|J2Rf zP_yICXY1;pu~uI#SDZhv>F3tO-yii@?i|kg%|7wa=`XKi*`95;^C?m1akmUAzW+%6 zaAi}m-sDwU-Lw6fgsnbL+*MQ3HUHi_arfNXZ!hDF(&R+WXMeE&^=9it|JrZQW^5Q&PV6oI>nH%i#zk*^KQRYwR>^@r)#mt|Jb=(y}o+Iw)o}K`8JkW&3bn~ z6^n(qoy|PZ?;P^0Z<`!G$m@PHiDP@%QYo*e-n|ohqMu)~-Qbh4PCSTdf6wLrO_^_%yf^Qz(#nz}|Jc;szwP?so2;_i}s9C7ykK zo85L!ILy~8zhvh0pR&TA%@-Uva?;pd_VcsD{r>gO7q2=NGr!Ag{l=4j$pGKn*Hr;|opq^0%M0QpnxtF239Z`99CbpFjFcO!SNV%+v~ag{U0 z`zvqm36Fn%c7|>GotBsJ{y#o^H8(1Lu3lVMsTVHUSFv*0rV5+<#ifdiGH3lrms>Df zzhdnSfru?P*QJ0DeB3Zif#YO$n#VDQ&t-wd9%eGX=LKk_*y&GJX76*IwW%&xaXMG^ zOAn23!SmCPJExw^P@3hxw78V_qL1CQQ>w;wrAmU%*=E8Y|7>zulTa_Gk?w4?_{POA zTUCx-x}mMWrKhN3Z~1Zca--sA@ad7J{BKoM^!-hK$sIg0Z7Q?L_5iM0-~`S+3dZZDzjbciMi!ubod-9d zNw^?H_^v z&WfIi?=-3RI8|~|3uJMHr94 zfvHN<_RL!?d+tiXXMg>jnjONNbu&KfZk(zU>-_hISOdQc_tH@J?hM_R>#k?HZ29Sa zN@2tDlGVYY?P|sB(IU6STE*BCLs?gS&@^@FzQ({P*1olH@tS2Ehl6JGdt@Kw{CuG$ zn&VLJkv|W)f=!ve37_7w<4ic`#<;T|Z%kqRyy&jYu4zxASYwvlS=!X{N7KhL!0Ydi zi{(EJ>KR&9uT4-5?Kx@o|AKN2gS^^O3niEC2Ii2~fPzJ*u15AYa(U12JNGX|VmC+f zjKyy})h3%9WX#chwmN=kp0LLyK||kwg|Yiru1rnj@LH9%buJT2gihFP-HUY>lsMx& z6&|^LR-CLR?)2bmRPG=VtH!BXO=x&^+FzOuscT>a2f{ z4$t|cf!E|7a1}OLz78s!RaAdLPl&<7!u=z&j(ki;Vq)xtq|m!f%(L!n>Ru2!iGerC zi!+o%Z~m+`C$buJ8ZWH$yF6vCudnYJ5$=t_M(@H`YDO~~*3O@tn&^C~b=3^3Qx_%* zW$b3-*uWyzjBu3$MuS5`D3Pv)(339z3}gK{n?uj{5iJO)90i3 zQIW}OLi%eXIezC0E?v0m%gzlqmQM0!`?f&Khe6NHk7w(_t6>bw3NDFd&UiSX^EgY^ zmh3 z+!sH9PPgc{|969HTZ@&EqlnU-mBGv3-QE5D&CSXCE)-;CW&QiMeZTJ33yJj4`)?_eS@jy9) z_8QZY*P2e<{QHk8S^pF(Sm=HADqs5YO(}oQ+x>o1eBSnZ$(tL3YBFc}9xUd!{c>UX zyehA&Vje0&FH58za_jH;&|m*YnPq}`@~I1tkN4lt-~V^tuUFb9*)z6hs4L#Qqn>RO zwY%~CI|+_;xx40>9h~d=!^GYvSBqy~K~2a9E#9}>kGHrfF3l4&*604`*l=xw{8NvE z-gCRwYK!9~DB#h>Nhej* z-*|B+P5q&@GiX=DE}1?7-4(k|EfD;;e-`U*5C3_JDQh{r{#w2@Tc{TD@RaL<-lYr| zwk(=3No(!FFrE+g8^cmg&OJHFdZzr9lE_UbpLFeCyU`}n$oYoP^$(^R0`XVHsstt* zRlV#q4OQBr^T5n>{$aD2-x*dp_IF=xRxtYECcNs3a3;;Mi5W|v>QY46<5x3neA`e1r|;G;OtfMq^2AE|WPGV7S`_|>rC zh*Xf)8G+<45sSR1>oK$Q$rvO!?63P<_5I!5z18JK+$>veb(or%fHu<~YT=x$<~z$Q z_tue4;q0ueu2pYEUDQ7vtuhXKI`t46&xFr6llyJo?S8-S+}w|1Y_m*{?5X^GVv>8m zoNgWW1zClfcRQcQm0k@!Gi%FiA+^)2^L=-`-}gJnNPk92me}633?COl%ex?(%`15CPpLv$EIr3NJ zqN>k3IwXyA^kq5D)HFVw(Q^09^$TTvNB!F~uPJ_>XTI6s@Qj(aUYfKx^NK!Qtfr+h zJ6^-^SGS_%44J!^(yF$tmb_*VW^}{8rNv#oc1!sBxH}#(RWeF-6V`%`1C7q#`?ZN% zFC!~U%Zok2tzatSYfqlE+{nZkYd);o{jTbWpgWJGQOn_gc0SpRr%o4Imh5tPZ5C3V zWZakwI>ff_$HNm7l{xb*?7rx{Tc>~TkhY{%gu{!14Zq**w*T><`N{WL+`b$K)~{c0 z|6)ONT+K(<+beS~Jnb<)x8vEY>^pZ9r|8vxy&C@e{r>+qHYPvp&}Y1@?_kX&u3b^E zH|arMT_~%tL|6WWTNhTGk9c}^`CR8`XZ0^1@0gKur9-giuaDw9{`O6F4=>a_HBYxw z_Pt@4nLR~XNBX1F#69wl#lCSrToK6JED?4hEs14QjG554LmM-;O*sGCGV1K{)n~U) zID5ZK%{OEoDnHE7kvUo` z;*)l22G_lo&oM_{9L%$yq}{UVjQ#Njvx9u?%0GGD3L2*}mHTlYP+7FcV~z5)HxU*| zc8}+NUF70FdrEYA%X5yK=j#^F7C2pD_GLRu?W~z8b!q7?n*vp%n z)ma>ymaJX0h{@k7=f(yxY2^ctE>taiz;Q(V=wr}WyUXqy*=ccqe-TEP+KV99B=P7sOyLHQa+9ss` zdlqaV)a`b87s61l%w5Q)eZN75d?|nNn&z_Z3ZV{U9+!=An<=LK{ zr91P_&JJq%A|Yu1FhjJ_J%oPCdXXEmA%1Z%8zEpEncTY{Kc}K^_3F42R zcrTXkI_jMI^rwrGIB)eu4n{qpZuex*N12U1rt+PVc{4PkW_Ab{?e$qVLDR*zVtL1; z{l9M3+;Qn!Cg5p0?~M8$wIAw&oi2ZCwoF*r@TrzrkLhfHm@(VMY^~&PD5P3+hua+oRiNj zSO0!G*XoIKo%6bL{m(r2aZe2JT@fJ8ebsHxma{8_13%A}cI*FZFe`S=(RDYn*e51j z_BEHasW4!vo8!Ttz`@4F@Z(ookYHJtP{#jyU%V)k@2N3Ry(@&%HNc;q-HE84Ip9hRs?sIc%HFa?b^77BLHSx7~QeZvTVP$H|n*ppk`% zCBk~6+KyFEceMMqd1n3&oTRsWuT$-j`7fWQKl%K!Kt-n8{OP91^A(a&ulM{plT+oU z7wmk=$8EOygI`;BzWFo3{`C2)6MWQ#J03O^gw|P1>1q?NXl0!k5PKv4Osa+a1l9?c z9@TDoa)}-N%G_S@Y{o z%$DxE(sFof-Mla6Qe4Fe4$;XLLDIeF`j0((_SyHyVhNYDyd$$;rSI>pn7rr9m4LD$ zJKLkD`%ho`_|W0N&j-!?ZhbPIQ-3q@Ni}R#b7(<&9d` zw`QMGPEdF5tVoVrv#xykPg`q~vX=(hm#1)vU#im!6I=hesQ9$f%tqVe>VHGd`%Ucn z6k|32bB)k7pWtAduzM1G=RJ=+`}W3Ub8%&D$XF8lN6^ZE31b2=GT2`tbrDVWK$=4^zp)2B;!59hLr?%nOfvg+isZx)X>Tz0c7 zx@GMD?%!kQ?BdL(46V6+>WjY4T$XpZUFQE2=jD41ULAivXKJs?tJy}IU%%RRD}DWB zxn0kn1oq1oev@43(P1-NW1ijE^fc`!{}0 ztGqbl=jDK@qRG$CT0T5q|KsRm{(pZC{?=muK7VpnhuWiuDK%lM7S>#7n18?i`%7hc z>xYf(n=U??!n&1xe%Y&+i{1XFec|3;{qg2~JL6C0{`Pg>4wuLOxbt{=Z`Gqa?s7JN z5B@*5D*r_Lq0d3RzyH2D5j?-@O^dqR&x3Ni#gd-wP0^iPw_JFJ`GeW^s@K=msM`JA z^Z8u!ftT858eI=AU*!_f`gS9k-_Y^FyXg!;p-00rPPNO|{g@K$r`R!V>eP)h)+h!B zPn=qNXp=ItZRx8Jqr1s{mT4CjI9|PabKtccZa*de#8YkSvXe(1e_qVG-sas@b)$@Chpj@LtH(n<@xg>!Osb|R(pk|eSR3Z^2>y~W#8O*J}# zu2Kc97gHile*P@huj-s3-p_MC|NE^?9>F*MT>APaM{TFy{U3U_E2kE&cRwySyJrRO z^I$ps{c0gE_Wf*&d%rGj=JdZ${^}pAjlH+^bPW@~S4FF{M8nOv$}4}2B1B5<>I}m_ zC||6IwQ@c8PW6m@=Dg{rO+>jQ4i*VCdfRgy4>`2fPI7zI!V^_j0^K*>uqd>#Xfe`N zJ-aki#q(>9eY1krcFxWDxn`LjQB`>d|4?x+lXI&c2xYj5pqw$DvpE|o1MwX6Ky zr-#4aSAWiWA^rED*v7cK(lzS^{A;4)_aE%LEu6I3?8v)<6aPMQM}GPga$^2`-~8$e z|Gueva4nu!u_b%^Io_4WYINQ_ycP7aFn{0gOZIuS|EF0WyHomp`S$q~A>c>yZIFqXMQR1YbqBrA?ADdOyOk27A?~KH&%~O}$sr`PJ zokxP9S6JQV#M;e2S_D>|`j#NHwo@C_>i@+SVRRxd^6+7P`#;n7|53GOky7KH;h_={ z#Boh*rvCmvpVIArOB&8L*}iZgqao9(b=^}o{17NOvSjPVjUN}b%UOLqBFuS)R@+^NH4y_a9D>H~qEHBGE!!<=f7)a{G2s;`@ai%&Fp8-KYztt_k3S%vh}U41EfRqw+&hn6x5PCvS4#flxD&smFB1!~V0HHx%a z1==n6@ArHEsGgp;=?P&s_4JjsigqtlWLP_ATELH=&*uxD_x7R zXPbjx)}AwaVxHZON%OM%AG#_r%Eab0_ti%_PJw$^EIqNL_|NRmt5)sx(GxMW zX9x-lbGyGn{*-xYPju!fp=Vr?pYKK7JD<8Y#!hl=XFH$GyNLBV^H#5}|Fy0S-)^tzbGkB?QZT1f&3QGXKeqOhF;FH2_e^u!s6hI^cwaNV}YuQulf&jyJL zu7XY`OJ8fx61@1{!r_zFRo1A*E0(;H5vYCH@qCeI*`X^biB1dT`L--;o58kqZ?97b zWA8fkywfL`yT9LTKL6w6m<{;oF{AG7~{Gt;R52sfv_{lmN8_h<(1aWcEOKYqJo z^RlZ~bN4-87w5V&{IU`XKCrr4m|O;;y_)T;`^&t>VLeteRs0# zY2~LcL(~p4HuUVzewyYkSMViX-}=idd$}*y?dN=)KfCSS*2g_1hwrrAI>k3t<-?M@ zKXoSFK4T^3FSM|j^{?BqpNl6L-Trsz#j86icdGBS?BW-dOUT=LHO#r4PxYOU>y;(9 zzfN?Q`+3Hx?x2=Nlb&?`9>YT{$*X=9t(|p?WgZhRL%%}&;lJxw?ObwAJR>8+Yu1tt z!OQ(Xi?#E1zvZ)d(9q0pHzTKRcJ4Ms-{qdGW-uN4(%SsIXR9Ba2zeiuK4rj}GGuGgD!WvTacMrO90 zO()f?-)vy!H21E(k=P#iZrAH|YYu;Sc-Xr9olp4_&6lT(E(SI)5Kr3U(l2fh=SxT;7&^?yL80+K?tW$H@&*flA8?H<#@@!Mt@{rNQ)t9_ymkyt%)lZOi|*$=}S^mA|?1^+es_(`jeT zuixwvwJ}aQq3^QSBUr_3+sefK@pCOdTzTwk@L6e_VC;!j-wOYh506^b%~YOW`K7%6 z&4kmsABtzo);tK?Z++i;%K6>DC+^nYmy%!3^P~KEsZCP-dc(iJmR`62c%ga!zPI^v&F){BZTWtdw%-4Xh4zwLo=&^> zMDqHa%Cg$n!$-^SKK%M>((j}*wdw!#Og=_$dTeP}zr6cgeJ2Twmc6Z|9Q z-f8#ecMFa`zJCACYG>D73l*ih>qFy3=Gp9rpijSYNS9K(E5E ziK#d}_N;s-&r+p@zgp|(80LJ+-@LD5M)c8t>q@WWEuOPfk>ka-OxK3l`>(VYt+xO5 zV(||3j34h772ltAoOxdP_1JRRx*reUXlysUkv0Xiw_G$lhOzPYfx-hZht>s)cles$ z{j%rtIpx*LUBWA-)D(+Ud^)KPI+ctiMJ`R?>&d&E$Nv8QE^m?{a5vG&B{})=kxt>x zbbtH5CescxY8*_6Ok$6DF;C*xwCFs|x#Fq|u4^uzla$IUu|50xItwqQj~fyXUyCh| z%~Lf^Jio+qvc;@DU#~@j#`AA%Og5FVx~M!;EFnPb+d+1D6;8JZ z;dcIh*Bw{hEN!11$Q>ncyix7u8$IE`X&a6NHYc2^v3x2p$^|XK8^z~s-|v2J=NIA@UOb^WVnYHW`&`vO$F`cx zDL!Wz9$&jvBx~i-^p7`JzuVa~LrFuHS8?^7y!5gXm0v}_o}35`4L#~(8Y$M|*(+_{ zS8H1JhaC^RE!`Zf{{?io&DHSum$!8J zG#0w>%PxHRb^HFmyup$jCwOLA#Qghx|Nk?gjOzy@H{E*5oOSzfe$4Er5xNzBzg~~8 zdbu<_zV_?i@AuPnyRPYo-zeL)Wr~tcuD9@{z4zvoyBuHFpS$JpW?S=`eb?RezucQ~ zy?C$thpK(oKU~~vR7L{4`0M%2+v{pK+GnqezijgT*zfgkZE8~PpPj!Z?)}z#(SNpISKI!+ zYQq1cEc?yoO8vdIH~LLkdH+|rH~UY%o&7fG`zPx<=Uek_{?75;zH`H_|1&%1$=|P< z9zXxv^J{j$S9kxvS%2C6cWzbo^~Cq{s^;0J_s!4x_3T@_{@tGr|5xY#nf2?B@%q1u z{%Aw__DRH8HPpI=q^ z>dgH=yWcN6|Lx|Nm!)QP%ibSbT%GeN=(}&+t4#4#*WLQR-u*G}>$bm{d%AyWTehU= zZaqEktok8~jMrCZl-ky59Eo0$cmH6iTOINSoKE~qYbwpqq zgY{bHcD}sr40&~e-VIwAe{wMX+WCCmV$s7Fu5C;{K0AM3WZuM$DKpquL#}S$w{PBL z4fcS|RoAY2FsnJP5HXPcc}bARlvu}uye9SkY9{5M+`^fuprX8H>!S71 z&WA2NIM}Spd8a^Nk!yxA!&^_io6qOhzsqsx7qMEgs?nlaw*Jq@rL!e-@911QU*pOE zI(K7UU*66+x9%OStE#O3@9zrxm`?|eirZKG{hA}WYa&z8Hl6;N z%`waNUEH%-np5X{m7n-6Q`H_b<&%Ey5P_1kIsM1@9ijh z+`A`Yica!#`FGW4EYs%|+>1<|8u~2u)uyhw^?x28%g_nt2yJtozxQ98d97Mz#`@m2 zx1Sz6?|B!?*&^Rxw=-Dl^HJ{g6}*z`7Zn}0Ex%bXU3TxMmhkxR2fOt{s#-JblHaN4 z2B*#NyI=aX^!dBbi|yYQf4Isn^Y8m#{;Gc$-Gu{ZTRi(yVIib93vd*`>g%rtx5R}OIz5E`f>0)FSGsqX0vN?eOGeYli%TSm8z=qUi|z0{`hvG z#uY{LgS|L^zB9X>vv^-eA$!s4+m5%|e*M3no4V9YJbET+u_#;>UZ~dU)?rus%i+tbtKB_~ zK2q`PIVT;w%++XSF~#rmfkx(&HWrM=ySC`^RlnUT?k@JGWm=^bvtz%E<)j5CYL+&t z-A=yQAa`L_p0GqegQfO2n~z6?pOn2`w|iZk^iDu){L`wtJd-!HqJ`$oIS z+Tx9KR3ytRuk|9Emsw?AQn|S6f>5&O^~iMDsD+H|P2zDCjk#>?YFtky|bf`rzNdz z1J@am6||>Qt^zIgz4WRJTL#=~eTJi8V(b+$%n{cjw1LM}GY@ z4a|?zxL;V1zdo3?(enC@DY+g;g;Rr#eRs>1F8LWaspoy@)c5rlE1m00FSYHrt3AR0 z-K_8~|Is$y`x(n)pQRREof`UC&2Qd^d+HH$WsC}L&zE~PRekTQUHtRv^6t7^-BND% zMa@U;UjL2M<6rAG>=f^j5>$lxg3pc6vO}9M9FfrrO8Qp1T&aYlQBkuD4Wf#8Q zo~38{GA1^+y)X1-h}D}`LGJ2f&u1-W+g^L^k5Jj2f5*1gtvS7}r*iV%9WJXm@09Mc zS9~H1IzTC}Zbre5Ud0{ZuV1}#>y>&cb5^$e4&!`>);(1hS?#{tY&Y@k_Z}FV6eQ#e zj|h59tFj8~RNT;2amHoKttnZtb~9Wz3h1V3n(R6=!@7JOhi`k(_31ND2~G2O7}?Fq zKFf6hmwc1n3DEqYRn*0alfq7wvA#L7ZSVJczhAH4|4b-baE8%^X$g`aQM;d0;E*A~0;>sfWDZJj0R#-Pw|Ahku|jhGnw+>GW*nUiQ`UG!FfYF%^=_Xzr~GUOnX;7+XEMo8{#x{G_VK_+Q?8pvn(bKi zc*Cbtyjt4kD^^LxMBd!IXU^W{6caf^`Q}q8)~ctL*BBmKovX50N7^i7e!R@*T$4Y( zisq_<-aL|fUO#NF6!qLR`{radQ&W?(E7nHWKbPGrc=#;)!eGty8!yf_JsoXufA;yF$m?(Dj3)(A## z@#-}SFa903`EXeNpMdht>fYHK^BoI~l|Rq_|EGPDnrB@7->+Yk0uDZ5YfJ0;u>F2r z^(Ix0m7*8bGA<EpIt_cU7@Tbb6f-5I8gTf54P)^NU*s^KUv z-01j`vwFv@DESNraW!vklhwzqF1VVNzrOZ%|Np=GYyCFuefZ%pKj_wqncQio{d|MJ z8T-1rEa-JS`9?sQO`~7gS!rsA`f4X+x~3Rq1af#T?bBm61(^DY|Z^6TA%%onAabe zUorW+Q_lRFBmT4ZZw&cx=Gv*BPiFnBnIycza{6-TjVn&3IzP+zNJ+X_bLNlQi9ec` z)AxQ|IK98>Gtcir~y@AZED+-T2dx}Eg5Zg&5S!1(BC z8uI`DvERE>8E2OKC~n(_Z!6OO?3(pz#dTA$e_yU%-y5Pf>0rp`l`p@$+1@vv#Uoy0 zl=k_!s^{0466!she(1T&Rvo!of9LV}{eK@${lERXs<_;r>E8T%o;*taSCbk0e3fOL znY?km%B_?`EZq~P8`gIkh5WfPTk*{2>{Bah54xB3 z4HZ?{vG4iZa=CKl?`vZ?eRitX%w$Oj@|$Ind1i*;g&42Q2{uPq#bX#8LqDbUJS+5e zoXl4Gk@LC&Z^%tymgdR+c9KqP7rexO`m#Dz+U`2szIbECQ5CO{yCs)>Wv$Cv{Nrbt z2(ZXJ$WU=@Ufge2HQCo};qIP`3nG}-YE61|+o@ra>+RC(vFB|*`^;2d5WE*OQ{+|Z zX#YvTsB=lo{0U8+d#ZK@HI(@{{t3DuyJZ>cu8zp6OoIn&x8GaD)o2@)>li%SIQ<-` z~MV?S@`zf9b4+pIz7f2aDq9k18zo>%k9vq`BYbAjhxljDhp+wK+|=H2yZSIw4P zKbby7&Dziyrpb6>0lVs)y%JKDcl9-<)TN}Dby|3H2}|(`oVWchbNg^4-~7H;n;!RB zFLK|q>&2rk<_Y2tic23jC}<`!GYytYHH zE%d|bn;I{9Ty$nXG??aZKJ)p`H>KYkl23W3e>@U8QKB?Q79_za}fU$rm})++~cL79d~y{nqQWnqQfbDn?Pan}5pfPS)7jeLfk!`F_R!W_NjW`QCHse72tq`ZzfCC#~^6Tl33O|9!QuWq#aQ zb^U#JwiiFIS+z0Q#_aKD{bKn#DWz*mF5P>wQrP~_j${on#hRjdTkp@)Q$8TnWwi64 zbncFyitKC3FYJ5$X8ZR8&3rHRo{y{Lzhl4Yx86?|o2jQKY;vEWANG_hwa!9J(Qk&m zS>PV&2qEv8>Hp5!7w0C_eOgm;+vKuhXYWS0JdxXMzKnC}lpV}bUtO0SJLYz0 zU3cr2M_UU|UEok=Yo8<%_I*D?yLYF)#nUOl3wACoj9$vLwo%S&&!p8%R+qd3d2J(< z*jIFncK*K3C0_!vwlaoym0Ewj5?uZ5jpo_BjK(bPbEg_+ zOSMR^TePU@ZB$&`xzwWx(_&}O(e8U99$&NZFrPJ#yxkm|Nyaz-{eEv>^5TN+_dCUZ zetgupn4EQ><|Py843$48{p*^dz8tw#vEB!}i0D?^n(5ZNI$d z7wf${k@s$Vychj`f7P}1^Y=fTR{A(}C+p{ayDtX$*KG1%y~%%9yXfbtY`}#ApZBEZNdwzKLyZ2IEQ!5vG*omKd{^7_|R(-Sm*JfMZpKWk< zw%PUB2CQ4}UCG~@uT~wLR&hDK+BiMb{jezO`e>cV$jeWB%|CaYTkH8?tFw-)qrPw_ z=h4Ld2}v71cCKcg;Z$6^~rkz0Zh^yoOc5Sp^)M$5@A*2y{0&yzu%@D@a1CSR&?u^^VK|T!p*@r>zx5(=t+HHFVRI_Tsx--?8>@d^O-kshRO_; zqSLzD*Q{BiBK7z0`}*`gnV1?#+FSYQHMb%eANH{XYkNNF*tZ7zxt}~XWDks2ma2R^+V_V z+wJ%Fz2Eoy+uPgXYRmW}3?4k0?B90&+99?nhuuHL|Nk}pn~=Q~`*m|xsCce!*%=p;`fS}Qh1=H>MELhWmvy(y>R5dETlLA51D@+I9Q|Cj z>RnaJf8M0c&u89Eu(LQDW;Av**svpRY5)@Oje7 zBTX^O6a%l-|9!Z%l=tDXlC)brUYoWD?9;nCWz9RY?JEn*RxNwJfl>DNcfQam?u(~x zHBR&Y{owK^pWx{p8ghLpe+x?YH*%kP^f6*n;v1Ir%k(Dl%sjIE!EsL} z@2?(R?KQ3VvCoW&^TWlpy7fDLh`#>zIeB8+%ge&NzNueVMwp5Bsb+J>?F(N1>7MQ9 zlP}hE3R_>l7XFlL;)X)oln2Qay%Df`$La2-UA1HWN0D~z(`&qv= zkxW>(Ko!(vU%UO@tc?>=`jS?;{Iq_*r}$cAI_TU6DGq*)gBPXjmcu z8D8`$P}17-{a&@Oy5AHY)tabA6-KkoeCLXkj+b7KWp|&=Vz~PDm%rcl|3BL_xh;z; zPpSIN#^bf$?=IgS!gxxsEn?$+Z?>*=5R+S?DcXnUDyIp6kUn)ZMq z)X&&_*34OM>zAchlnSgsL+48atbkkIrLE@ zRqcvp?24ZsHlMlv_|ko!mER6u%u$@8JwYQ~N?Ob5-h)-Ieq4#X?6%+R=gf6EtuvdH*X6!ARdnjbf+b2KeOIrV z)qMTh#BaA|-P*@-oyln(852*Z%sSOM?Q+^uyPsBnj+(#RZuiyhm)75R&s)zQ`yJo2 zy88R&W%|d?{eQFCuQlL!#G>j{*SuqEZoaQK`@GkG@#pNxbsE#p3kX--Tz>i4+;0=& z|2SP))&9;lk6%yVh}rF&%}2$e9Ri+SYh7Rc541k@<d<2+IGqY6?Rc@MTOr`-9-(Oo z{F5w9PlFgnY}}uGLKEESc!!^)e(uKv=+f z=BFzwgG=`?m}D-@n5r`Q5QA5W!i1m4?f)%|Z4BL|=fJYtV?Vo7{6RCZ)r-RABn}-l zdvkElY5n~&)^}$qGtBQa(ElEtdF0Ba)}%fMVf&X$Cco%lkYV*Jagr}SV;FKZ_PUhA z< z^rC=}^VC&$ca=V_6A50>DY&z8e)T)cmyVlmY+IJB%XKnOdrN0baPaj{r{YpYZG}>0 zA8p+A^D{?XqW&b$r>h>FH8m1z8J9U!jzNd zYR+-rEB55?*^_KBspr^@AJ4iEz0CQ)YSlXx+m&jc8(%psG@N?w$tTWRpLY6v%;+!J zvRM3BTidd>61(Te7k&Jca)ev8sP)~%3UmEi>SFI>K3vn(el5mTdg|xgPp$VpZd&&+ zLuYR8s`Z~Y&*ZYdvV4*A`%Ry;?4SI+Gfhp%!sPjW*ELsmH??Z*b?908@JYu`*YCHo z^?$M+kX<)h?AO!j@zb^+GJU|UF)=(oD18pAOvbycBS+p}U48vt^?Tk}t4EI-(gcs~ zdnUs=A$PLdgvVa$=e(UA4krdgcKj_kp?Ko+97c~tT#ROyIs}zLXI8&gllPju+4oag zg7udjg^&5H-)y+KIel@Xo`IytalV_H&%H%Y*8lrF|CQe&&-fNgHv^3`y=J#OP9N$h zS+inALmRViJj;Ts(<4sk#Ay5QaW-EEw9Ql4qY`iW0_Mk)s%Os zr7Lbp*)ngFSdWaQP;=umzJnS(<~2VGT2nHMGXi5h3oc*T%Em5Jzz`ZMm2;p@SASvD zN}C!z?P-Uf2W71_i9Z55Q4Mso%Io!(SxUF__wQBfQ<*L7RP&4LLei!Tk4%ia04-y)YS$Zk)Ja1uULRrYpQM&F5K(X~l@QYIb6LZKNd zrusQ)^X)&KP~O$tkQ4WKqgd+`Rpm(?&;Q1(+IUU>iP2A=(C2+D_P=jF4KT}p_xzJl zncKp38<%ZcX%`qeO|9$qv%s}Kxp`m4w148VpS?Ir%v+!^xpKXd)cIwbdwG33Q;g=H zH#57I&OOt^b&ckf!^Y_z-J74SH*n?n$f@MDY~{jbGV|wowI@$xUD=jVsU*~Y<=o84 z2K7^J)=W}zj+^0gZ7IKM($}9c%Iwc){G2@TrIXrR z|LTt$hk^8CkWX|8w8Z)^*&%Rc;OomhR67SX)qtEK5Cp?UvEtcf-Id1o) zJtp_Ji}|Yu2b(Rw7E2fNGwZRN+vhFXx9?w<_PPgVZfyHL3l=E_FY{3hT-MbvOW_4~ zyUM2W_xA+68$t|MtY+GE`tbop{>7qFc7BrupH@96*t=m)*c}W$_%a)v4qF^GXYLxb1(yJw&lb>vmaH;cXlIFH_=hujY1`Ylo^4+9GDPi7>*IwH%M{-}J#k}^ z$ArE|Kcu9klol3*rb>Qya$aWmd}gx3hr@zRRa&!8R<~~Wxp2~q`RBrpofVH-)R~by zi+NLgXyD6-m2d86YI&=an5&(d^d^MOHP<$z)4ES^;3-N z%;Ln(|J<3w_&<9eldp*Xa|sLau0@Bt-8)|2+%x@qlHl)Un|lwH72mL*DPvZC-m5M0 zj8J>u-=@5~pqa(Aqd7iTdHH$&&rF|JSguoZ(tE1R9cO;qBM|@wkWVl2p=~7o^Plx})sX|hH8L5iz_x*mir}FcLX_^;Y^nW>BE^p>Fo1oHQ zkQ^~LM3sTDeUjcReV?`lhXWqbxv#YSc?%{AtNXS1yYpOFpk?{U;X=!dmv?e{R2Dw; zFg}*uoEg)$pqqukL`LPM>$?5dWQ5$^=@)%L*N`w2j)Pdu64>Rp(!eAQk#zk zu3py{Hd}4Gi%(?twe9)wg;!Ra?hR_3I7Py3=UKDc2}_#fH#aX9m9VWcX;|etCAHdO9IN<+uAJNY&d+!bdkQ#GKc%o zc{^M6OfH76)!kt6@4HlXg@@kWv_+cUGtX)WtlU&}-$W#l+a>AQUdi*v@6L#IwVVCb zX-+PesW5 zeCgqz0V?8Nzb;nT37v1BT&E+bl>Ava&{$@>+#cauq9 zKP4t@j-1Y6Rya+gWyfNk_P>pLBD-7@TW?1kf6BI*!7cl9K^n*FNfBG(|9zTXu*TVd z|KkH=&1sL?r1KWEEStKtICcfs`hCA%U5~HlefUWA))}d)dH-6PWw*U}w3OYUo54?d zZHB;+#(;RQO8LyN$WsT`_DCA19c*INNaT`m7T=Yv@JS`o^{OU&i0T40j?#tt8zS(oR<#>)EoF;9rH-NE)syXvY#Hiv|G)sGJkqoP;cIH@{$tIPwDNo&_M zuH#O6$7Gm#an~uC$R#prp=nl0M>x!P2Zdgk6tYs6(YG#VmTr5WpZ#hrNsa{9uGjke zI)1;~&94@`fQ#|Yl|HLi8eUrz+Y&EaV&3@k+3fPy*Lp85>|K-=tnM?zpzcpay1H!R zWWlJA>pUXgkI(9Don6?eU~W>l=||AjJq`IG8^WHksynF|uQX-6>$(gua^ zx_NIkV;=8Jr}y{v?k<1-ZpMwy#90=d%FD(047(3s_P0Nq*)}Qp!rJZkyu_zX+2<1w z{H*Ln>i;gKg40)}PAGdG|MjctyISk4X>3am&1pQb`Q}L;_W9bZ!R)L~MoZrX=xKyc zQ0vsxU+%Sf2E(KH|G&f!Nw}4=-V=W--@P{^o8gVTs@AkaLS@p4I$<9?3?Kgtun})E zQ#43fq}kHDRWWX3jD=s)$}r*69@Z-h+(qA;96Zi>L!VWUk$cIqxIVoS7hT3lJ7#Qc zoNypVpV98Hi(|;IrEIr4y7{ab7^B%+n~c?xmK;73QX{$~Fhi|-+XSVoBm9%&j(=jw zEIzm4Im2fqPPJ6C$jx(Gd$wnFH{Ln8WntOV`EEN+9GG`LEZGY>-Ph|~L}+HH)R*b| z|FmAZyRI+5inY;t!RgfLv1;NA(l?lB-gxi`)aT!`P-aVvS0!Yc00u%cp}OQ;Qi?Upq|A2|E2ctl?^a=ytxus%=a)RVN#j zGmoRw)L-W|Pbq9D>3U*V^4>o57^&v-U8^HJt9#bKV|Q z`d5CIIed$UuHexZ^n#cY7+0ko85AS4L z6S_RTA>C8@$(!PQ>p7gqSe!VNmiq4C{%LS`twvE;`_Ju?X&2HK9CWEX`Zy`X?7&}z zBd2dQ{9AO^b@D8A?!+0}vW$K_t6X?0qdBSUXmrxU1=^fEPx9{R?a(6Li5{C z-h5zvas0Q#3vaa@{MKoP`csw*Jl}o5XPG6>3GY|CgI;WZn^w%k@z=(*<;XPtJd2B3 zk>-Y``i3(tR2wuaWea73TP|$RzkhAp!RuOufoEiNOj_QC$lP_@Tk=yvR)IhM?$s!+ zSvwqJbQWzoa)Wu!fsP&LZNJ}HU;p>@-Ll(f_sa8_tUEMGrfRw0+#5V9ecNgu261;w zi->4z3rn%iUcGA7mqL~|iN05s?2I`0Nc;oC9I=dpDGQ6v?yq8V)aF`v^RrGz-;!J> z??!`34O(7Tw)iCIc;%S{<$ft>U*vu(!|8wK^0{s&6Fu4#dqU@^uUfr&>)$O0HEyc& z+kO$KTG06A!j@?XKVuJk=lEaTtvOFIO-ZTxfL-^w*1bMU92#2A^C>+Sy>Ig}_4$L{ zv#Nz_xpyosP!!j^aP&)7-8*lEkU7eK7ETbC%3;ctOFjPC?7;0Cxkr0H`5ft(*^%*G z@zljM=dFpeBr-Yv@KuSQJM-t%w3~aLIRqG_GjZsP8}2o5?O(aLvR{1WuF9^w;DRqR z)!)vMdvR{RYw7`?Q!-w&<_oXMnPSD17gcWl?}@to!CXeuPR*I1!&9r8j`RMx671hA zWePgKwg5Ea*5NK+D{}7d_xt}poz|aRTo5i?^^s`@v&~$&^%LD>s^4tOvQ~c^%9fA`?UWy9`l=@sW&=l8+Sdg$xYc0cU>+*MNwgydZ zUMI-1^s=WY(}Gp3QyMS2XjWP+YFX9HR@$N`wm81-=hK+`#pf*D8F_MJ9Df!%h=ywI zJn|w%XH`ak$K=Ei&D6Cox6Nu`^zk|-uxrc5EjpVbgVVSgw61OA{I+VBhkA?H2sQl%5qSwn(qSnT?NnY)X?e{y2 znde+t7n)JIeC@%AM_0GLpV#JL?t38l|1F0M=Y)+ntMr7`Ixnc5ce%0ebT*T8;dB9? zJ3%~;Q-uP`ww-KP9CoiJ;k4ZrGp`KOg;Iv!;3uw=L}gz|h?@Kds(xo3)2UYN$s?VBFTG_Dibp4ZMN z`|Vb?{zXgfyW2SmUTfLQnPgn}@bIwa$>p9?I`@6>oT#}Fbm7~-zrVk~y2>4Yz}L_H zU2%;qSK<8vKD!?Ym0W*jN$&pna{h~_Y{i0ew4TR{h+a{Ve5SO$wP$Lqp@XsZvPw(i zC+ia*eNSZK?2$Gw8?0V=xJ{G2-ahDrk%|nr*!F=EObd&5Twvx_(9Xq$19@j zlv$%>M%t9;^Xvcp`Fvh|`Xcer>`k6u0zJx8B*M8lg5Nu4WC<8(rY`r|d`IlUYQY)D zxeQpRi6_6A$BzpiGFzRu`d)BxpL)Vu-9$=5FhJW|aoMq0nF)!xJ55%3J-l?l z(`my$fqwgcJKU~!a@mDWDQ?f(`}G<}KnnhM= zt*i43Tivft^Mbxxe%g|kuUP4|AWoZ0D*TjXVh1Ptg(X?OzPw9U*NHD+F__~P(!ms^ zWZ1fEmzAfJ^PUffxF0EdhMJsa-TQvu?^RkBswNwI7-r4+I{*Kl=Y_{*%S*Ink_(?q zbpQ8|zkY(#ldUIqvG5(sSUpE5`{Tu>dmpUmTyy15a*)69C7*y0KCPKa5u4;2j2?O|UHfUh%E}vk&-NyI z2tVvvnJKu=DPEIth1gob&a>YH5~6;@`%j(StHb1Zed&s2D)&t%MjR8D479_MCrOcGcG3 zv&s9UthaV}M4+SQ^e-2MZJI6#&Y1F{*eqFjf`A&o#RG=Be;o1xx%P9PIhM^9 zYH3~j-EEq+n?zc!xymB90F;;KHK zkgF{-RtaPFj$CktMs^58P|A{TXTMF9WlGrZ0 zBXU8a(*~V_Z#UEDvvP~=NN3AEq_a1+rabe2(6{|9;_N18W|_XenLa0p zo*cP)_39aMzN#DHJS_8ndc|>nx{=&}k&9Q@Q}5a&_SnN4Z1fh?Hl1Fky}UQHN_6Lj z-kBzWLPkkWk@s9Ojr^15bEtmw3 z?{F@!1M}W17q>)7T5Enzic7g_nAAU4`KFEUx5GEw?3xy|iYxx_f8zV*`2SnW7hKBZ zHU7Wp#JAg51Gv{_$(7Gsu$oPRb!*7)Z*O=1|M%OuxcF3j0dsKC;-dD$?H7Etos62I zGMkoJ%gJn=6r#QANc(}^Ute5g-pBGm&}60l(g!bs)M`_;QYVHo&1l-SAdFF{DQIuX z)jKaMYWdEUS zVcf#8YsK`(-cy5<-YIvtwXx+gIEXJh)|N6$XUgw2JGLxz^kT{2RnPbsof8!jXvB4W zy^k~+%*^$!T`Jss)=b)}L?d+8uP-k*pEbLk;WD#} z;mD1R$vc;w6Ux1=%D=1aVSUcl_pD;&2hX$_6k445cS_0W4rgWMk=z=i;_ZrO53w9o zy4iCnc8!3`!x>@iFO&XhYQ=P8U3He%Wknie@tv@eMK{-(PrUUf`N5UlUvqBvs5+M~%X3bhuNxuO;m zyprcsq>t>3TP<$-stp@YMs;g3s)c?u^buCfyL$C%U~0QEbNknHC63qKKRBXm)6%^- z4^3G#oo82YmY{^+nW^kESG@(zs@DDacwFLS+@zILI7(h#YF*LedE=O`dCm=k8F{A! zKDhVGt<68_y<7)o3&L`Us_)aimys<9viuwIBea~t(+qFE|F#RK!Z{4{&HS4Qlm}s(wptss& zuF~W3^)|J+Ff_B+o1eVT_#eFJOzbUs;x3 zhhgFBq)d-3 zn`kT36dB55qMvqZ&w~G7*Vikq_g>KHuv>C1uf{!|Y=#{4-MSbJDL&v1^_ncIlEg*Oh zbRh=sc|)0U21Wiu8r5$$vR87RVhr{A(cOGIQ}0(s{| zGv0PN2Hy<(|9?JTby>NBVS(%s#-mlnw^FROXr8~>s@;+6z+@!-XmP)tlzE;^XfDq> z>m^69@XgZevA@5*b{E~p$C4G~Zk~}nrBAPQ-L9a60zB+nwy)p)&dN5aas9=c z#%JbAug$)`ZjKqB%?F2*mrs2um3w;O&%fXAmDhCdE=!uZJ*T>D-Cl)D1#jfmem@iM znH1c9=5yrblzVJy_ADvw9(rYy*PC*P?(IA|mHXKep^fMHx#BdWKLsyzT0H&nzhxKi zcy!$M)8@LfamCT*0%_*Ijftxm_FYo2xV+4Fwr%yc-?NHl8(c4$I5*%-$c5t1XU!j9 z|H3U)FopLr*XJ*n{nKMOOlQu^b@W}~6rpx8_7|&m=$9Ucb4#PQ=UKnqGI{RI$ds*@ zy0tRzw{Qxt(3&;NF4xibsA1GuzpRT2&-K*zeLiPh{_f69wKpR23O-YHBrAQxY9IQ~ zGRb^$Vq!s0?KEZ+8KdQM%Wf@fm*aBOpMK$T$`mvGjj%7}oUMyzpz+ z`zW?+2kQ=<4SQ3*z$Y!|b2Oj!hO>urKQGtks#7`j>S}f5q#*m^?UjH>NH z`?q%M7d))zT%bQez14x!W#d}OV;sMqM$OuGzpmQJD70m!vdn=gEBF0;HhXj0*-83t z3vTQbolx$g`Cx^(l*&`=rYLh8_N$$_ zrgY&bPjdMCbJp+gWUXHNK>NwzK<&*7cCjdgEzx-qv_vOnwep5jYgav6sOQ&%!F;xlZl``d;CA;FR zL+4+UgMTj1|L3B=>crvcbtVhKs$>L&7ff8P-!M%j;Idci>@~~IcidTZW6j-j*ER*Q zu{;)3c5~QpT;SRs0f#q@>~aC+h6lAa?Ae?6aNh5@#Yd+uK4rRfet?mmLAZfpWu4a3 z@zG+zgMVV8io&{|1=I&Axsm zAYJ_Q(Kx3~y0T(f5q+jdrZTuTJ?^va(_w5sT6)Fz+|`9sZ~xo(``u&%Ux(WN>H$uk z}F`84F~Yu>(o71cAxdve(z0#uEv9ppP}i zxRQFQY9@16>!jFg#_P}5Je*{4<42s%tQBX|q$2#^Y3|^*U#WK>O2toyeMfa;-hN>} z3r45s*>@-JI&bMR`LL%%-R$eDONeTk`jI`z@?>C+Fl9MD`aVcF+%*8Y+` ze@;Jg(Er07D-!we#C48KoFT5D1FZHxY?D6J9;Pyxd)f8u^?ThsrZE<1TwQItB%m!b zKv0KosfE|_4gZ(h|9zP{J+^JboEqlyg~&>Pe;l@5=WJ$L}=}e`I*%eEq-TMMjgdk|G0MxQRrCTs-q| z)2>~!^fDHR&-QIz7h>@^)JQ|dOi^_=-|7d-ZK~DpcB-f6oh&}7v9lutRE3?ryH;q6 z$!x}JQd>T(y>Gwe#zek?oog0msC27uxvF_Ny%*8GdHzlRL>XKQ`*n@ z%jwFYqfy6FlS}T_B^;M6Kl1j_!+%S-;=ilUQJ-HE#IGZMWRc z+wIw)HiO4SRO;N$=kvNZ$ZS!%^GHE*kM7bb%prld%9v$j)_qvB`P{D0=d3TTFK!l_ z<{8+!VnPLELecLvob=lM&xga- zZ?_1CCS28#i4|zSyW{D!=(vJ|tnc^#|F`%1z0+Ziq8CIaG`1C}IEw)UMj&&2nA*j06JTHH^^!{%=<&G0r``uNTM znrk&RC+oi4+OsmI$SU!-h-Suan`g}@vITNH^e*lDYbNqp`OCrc?oKOX)Pkm+c=mV3 z&(GqC_F}UGCHK~yiB8^Q)455$bJm@k+7sSA{ktdDYR8}ROp6x$l)iU*)7p~Vj~H(X zO^>hpxxW5y_BZD0!>%GfE-&-FU4FlI|DR9ZKeoK;(*OJJsAj?H#l4@z_y0KBYkrTz zu{^WcjbYYeD@ET!QcUX?s^>G+c$I>NQ07;?TKWB6HNUzCN8`#q7Pd=CG9MSV%Xu}m zRx1T%=}kG)sx@gz;V&Oa=0M%KBJVQ0b+=uJxU_O%;MW(6`}h5L)D5~d*m2{!b$t5{ zR3`m?yWRfD1m|zJ^X*kG9;-U!F1>dKYgJZu_Q{_c8XLEWM5S{ad7$7tp}D#Exq)Mq z`sR%@)oya?p8Yb*w))%k`1-T13tfKhsr($FU8AQE^8L-t;s*yB*ErnYSKI0HWVrw&Q)c^kr8i+b}RqG;G#?n*2HqVH$xKecI&yUCbJDZ{^Pj2A#c)ivC z!3(B^Ek|7xlZ?~ORD8V}9$)!XbeoJh)AhN6ALq=qDt#oi@zCLR{^NBVi)25=bjOz6 ze0rGQ{z}!ZNjpJH;`?pCNf;H}`NG(|sAchtjny|FPIEf#%DFjud!7tm+1=7`HX(Vn z?gbYY&Q!2&)cUUceQuRyLg>UO)dM_bAzD+DG;bW3oxiVgW8ziNVWTeWpRF(CSRe9V zc;VpZYu^{y%j|eKA)>M}HrZ0zWH#sR^F}ilB!wN!5!|vRV5?hO%7V(9@6)9Z{R`N( zQ{JfJR)k72%fJ15i~A?;viko&J#Y8s)9;F+!$MpRF6c1P3UghlbHuDv}vcxPaE8A!GHnUtQ-?vBCC#l5Mt>n6=C6>Lh=wn-hMp)Ro)0=j0w%%O) zbMLvz^K~+1jFB>BY!z{l{pX)sp07OjbIy6o=a%VdGdiz{36*$<+iX&s_V4en>-Ic% zqCe#nYdG(;xGT2f{_LW8F@fLL>im|Su3o zN<}Xr4P3-SBLpH9NyG&UcZ|m26>^^Vvna3-vUgu+s)%zErU#7Tv>hVTw z$q;PU{WHN;`q1%4o-gzAHbmHQ?2yf2*gGw1N<GY z3))nnxqQwc)yRgRx+M!MC9Yq+y77Mk`+`lISlDzErPWK;*BIp(gz4ozn52{;aWXu% zG&HktO25q~4*&L`FG8{ISF=P{9_TsuyzXPOoFs4Vgu@elEyt37Y zdCfOG4lI0r&iIB|joXU}SsW*i-o0~>kAMCB&zHrci@TirJ$i1+rI?p&{qCYXbDwaM zoz%%)?~jxy&-kd6YH#1J^=|Q%Kdvui{y(=CQ2H2QHa~gSj+>Kozibfb&xpEv>(xDv zvh_*3Ib?X1-g%pM&ttprf9|u|L!oc&XZkF-#FDnoi&Mv$TU^g&rD%(#PuRvM2?=+X zTezP|pI;kxnsIuR!U@;IfsgMLpTFB(ytQlEgi8`E*DfpfSv-0&**`74^4raH|M_-* zmrV8xnxPZ>UZh`8Go>Kv z(9T&Z_A@l=ve)kUb}O56W6H(q3g-oJ&u{6wE$HC5G5P4HBNIQ?|NkvNw@s+ps!MN@ z`JCXnRugm_{~VK$KCm!m;qkU<7mBVbslH{|^g>(5J6rwx)$sV(4GE2}7Tvqh|E|xw zs-aG;=<@T)YQB#G^xAi&N_}|Lt^aAtUhneeDN98XoF7bCZ7_X`@P|pN-Ubd~{;ST0 z95*_vx_z14<0al+qFUYBPoAEht{=UPr+SLS;*^Aduys397*=%fJ<@pT%yQ?^e2!C^ zt*0+AYcBOz8S*t~&hE6^T#s!zb}SX+$|earI4oiQ{B!BCGF@(W+GlVb)h|!;;h1W-W#Mv;jY|usdjE^DNqgq@ zXyv((MP@VmT%$MJW>;SZp#fD;~Cr7j(z1W)5APlIoIBcxBU$=nd(X&KH`T zxUVL-O5J3LoT1s>wPn*{#~<(O|Nma9U)$Mi5h*c6uX~|^z?{d9Ev{Q$wJvP(mU-pZ zXm+9@;q%}3_45x)re^M!5*BlJE(|D~GkMVw-EPg-;6}(ppLr*bZf4=hU|V6Ak$`EPZXS>3!Olz@Q?;@_7+DB*T9B_NUQoCY_#X18w`v{GQ zDK@K0uC5BLs(0M(se8(4fgVecnLkfpPv9cKd&eK1P!ye{STn6hvv+lqlCR2xIo|`z ze}D7Qil6?V;LX}QFTVc|dG$ex@s6%=!{j`h4wV}dcW8gtjs0WzfAh>ZldoTSWnaE? za=yE(@9kR0X%p7md{e#U&)uWV*1ujXPCq~Itk?0S*Oa9W&pPDgXyc~eJI!h8t@4O% zhb+%u`tV|L|GK!nQjW}RUz?&Lnl|oyv8cPx=99-%DUNRuk9_%-ItJ(*+&@cPZ}E&o z1?fNbKOQvyh~J#X%eqrmcauVRwWBX<%AbI+c~2cnHN_jffAyLrY%6+tN_+jDPuutZ zU910gTG1v0bo8WB9Z~tV`M8+>a^52GO zhDQn%Y>xl?Dm-}Q6tSwP6}MK3?|5R;&u+?Gt?3zG|MzS4yPfR}J|<7iGWnP82)QaT zVN$C>;vtrluLdhZ>wjII4_fVzCo@y0S9t1DrJECk`e)CcJ<)M*ivzQjwe`;XyWj7N z4qY+-q|(%ejWS9MOiNdANd7+&;*vk%P#dpwR8*9}iK}-Rr~kOPckvb8MHf|#B8y(v z^7L&GF`cx^v!eRX$Kw|b98DILY`)ULm?rV(=lS|;x<4+pTenAfh{^m*YUQYBUBv!* z%Y%JAU8O5@PprRrXI3BUPnPEnd^PWmJa2ofc#FTfy>I2m{b$$6?>Y7I_WtV6y`^6* z7a1`sPrM;{TKE5py&~3C&t~X9E!4LaIe%lmkQduQU-P>zr<6`FNwsxKo2E2j(}|+a zxYxJpbR?tHG*(P)I$HJf)6*!a6OU?+U21UXx|zHE?lbPUY+b84Hie}G=}u`_Fm3Y< z#q8K;eHve%&CcKS0Cc&-Lw)f=&e{P#3ZLX^U6p0on39 zg%4ZB?f!nbEW#q?JlSA@!cL2eO?`XwVj_2G9$?@tsQP_v`##g;V?D>^s(Z9PEY{^n z-FQ&c%x#rG1=ekmZz}5kJeJ?SZQG+)Yq#GE3fGvfRN=P1|5US9 z&|v|C1v-gS4bG^t8ZNWq$#4h{7oWaGY%{ms4hH61&R%a9w&&MW{YCzRX#sH#2KZ{MC)M58JiP-J0h9JHEieb+`DsyV*RI-w&DQt?1fRXY|T6_dlop z_x-i)D^FOYl*(BasdQU#EBUiDdUSqx*e*Y>{@+iZgtL{$&iv~YyDIc+rd6p{_xdo; zwVe&=dV!!Jq$q`mLx(1}C)8d%^y_r|zfVVm{pT2`^JVi^-3)epdFoKCPRx!E-}nFj zYybbJf992*3lUxyYBkFr9N!&~^x@NK{rx{4bzhIGUTgPV%D z=an&wS{mnuRXtw7#`7lbn(rTPPhFe2-TM1}ypI2Gb7y7ys8-4F(_&6atclDAk1KaGPrWob z)%fG?`+xn6yH9V}ZaBlX`rGgK`}_SxB}KA?>e!~KzUHt0ahTWqj)NFmM#^F4q(^@~ zwA-hwX)6zLd@=vB%Kd8Wcf^;oUH1$UU#($ZQe-jN)aI0t-#0$fRbpc0Z$Fl< zoHEUzq~H3TMJ?~ODVo6%`#8_L%T=mWv6(IZQ2P4X%jGK!ByL3gnZS1?J22QoP5i=y ztglZPTojhPnPHghXZdtWr?9$Q@fpK6$tRW`J3HHa{f9Sqc5Z&Ljz3hq>F}a0>EEs# z5_q>|=g!JQEu1FFZ*FW{?AEJPB;dli%cSAkhUrn8)6O32m5zM;%k)9;!nm##=2zJA z=I=NfbujPcip70Nrv6KVEb5=nE$7(S_2t9Lvtm+#%x5pGUVZY>%z5SaDqURia&K?T z{UshB;{HcFH*VvTbrY(uMW*}CHtSvXsP|dnqvaOecB!FSIa)uQ{EP!UD-tpo`D?$t zxL9IzaLwAay%)Ou+a&+meCYh{oTpe~J)w5vO<^9k)^8uK6)c*vCjS4g>-T@(`+g#5 zyRldE|$TE|42bvnKjHRy;~ zXqdAfG!?e5Pg=5V<>Km++9LOSE$Js&$se8cUOQFjN%yO9y}Kh(9RJm)C2YcH%{p(V zqI

J>nJCBzSEJTAeXZF5T0nY^NPBbFttv?*q2ai*4VnHckGRp)-+p(o2QO{V7sX zQQ1xPrFnJRO79+@#G>&MWzU* zottxV?InixQS&IUr12f7@TT;yJw3_7nEdoyY+~2I8 zl&bubM|r_}w_wKub)Q~-pH?3+`*q%``&aJCofna6o)vNJZ$YxotPYnMP809(Y&=!_ z)mZ(cP5ry)i(_8ZoxN~c&&GKFD&?=|FPfg;dhKqzFx#K6d!yg{zvOuBZ`4wI0h24{ zM;3p~*){$5t6o*B#S_1LX#0Be%IUSQw+WnoJTd0lSB?H0g`;`46GXB`NBCU?Gr?x$mW7I?4h_GXFvwW_PYa>EV> z|2=^#Hwd@vVp80~vd42mP>WR1RSiX-!mI0_|Gs*a|NU3t$DbA%yqO(&M{QAv_4=L9 zq>d)*-8?txlgvwblka!S=il4^!Orew`{9{M@gZHzrY=uROU`}Ju*?2FE8B|0jOFI? z`?Zgi7kxVMuyxn3knmZb)9(B|J^x&U&cu`7)TE^(9XogYUKYl;NYK3hj!Mg%>djXw zzir$Q6|_jRaQfROCWBr3?5DrK?Eipgt)`&=l}+1Uky-dy7^vb)LcRZtU8Oa7td+a1%IP6w%%FilSk z)8OHn+Vtq_x&_Z4uJYc=dUVEypoKd5`=lilHz_W)nD$Ab*ZijKxzf(Yl%P2)7&(@l z_{6sK(>#r@l?pGmmtTC~B4M0%ru2HOd;Wyesk!&lzwC(Fnst?1e~-fE4GJ$4&)o~_ zUD$YBz^>C@HllgKmzPGHcYM0o%@yH$TlwIroacwO=C6?tyD_QfxLe6@3lM5FV2!~XFVCCHJdLt`(^&MmASDKZuebx zIrP`YT_=v$IrW9)4z~?u3skf=7_}Kn1Y`-iGjl{uJ;ho&u`O?<(`%`zzy2(0n=)-m zvD{n6RSVW#>RKVLqLIT9c`EJy1bLRw$v~cAQ;g!Z2sW z#43%5PfMKm|Hklsk#Q=VAT#S^WksYGzpV+Iz?Gg9qm!!Jd1CIBT=t#+`_A(ntWOO5 zCQtQS4jSo}wJv*+vtjx?n>!lM1LSmUI612pJw4v8DsKK)zSM>5qRW}T5+PbrZ+h6) zFSnM9+?wNh{M)y-S+W;v@^jZ{Pw%{wK-wzB6pX)TEv@p*vat(PuKO|Jbqq(TOPzay)Y9w<+{@O?t<jp2Rz~OtagX zvHSl2`^_z;bKukFl5>ilN1cjZP8L{t%IHC|!S>@tN97Gq?tOde#EGYiCrTKv3z(kK z&-PkrumARS|LR}gzjZC(a-96@HN9m95Bv#OZ=q4$Z8$t=%nTT+eQE#H*Y_O|DUm7B@B z)!cGXfA1e%`+l#u$R*Q@_c~6e{1d6#{O0S`m7z^;rvrrge8R+n{A_d_1oX@wW`<5n zdATVjqp#tQcB-t)g-O2CX0<%vl1cE&UdS6&Fwyy;!xneZhm)nJFWd9a*=hSj--{=g z*nJkBvO9W8tNh6xVHqW!ZCh_}KiQD4DB?BSYC|i#$W`qt_IK1)NXGv-Bo4Y%(bUXL zXvx2kTZ*IIhA^(<*SxrsI39)IP& zrV~9e@4p7erL7a!-LrXJYcuP_w;-isO;6tKDGE8+lX@g;$0V=I`HnBIpDVcg=goti zC1*oj*EBCRouu7tbj@1hifGoPDDL0aCA?Z$o##8{sZO6YmHU-eu9>;$sf8ETdooW< z%~)(9!Z&;Gh4aQsT|QP__iMYfY?_!%(iYD}s&_OG{rUNMe$^|@zl(S5+O@0aznQe8 zz|`pI==zV{@c}A#e0N#P{Hc37;mPFdylpe;|Gti|@^hChy)rX>-pz{_1^qT}60nP| zuYRZce8vC1H1np|BZm&{T_MfS)#=ny644;j!_nl~yLefTWme4Lj$?a%F9=t;JzGQM z&c+?)$0w?Y6&&F!YR$2Gwokq~UZSb+n9Fm^U9)vWRwq~dHawSU|LmS^*~Xi5=W>5> z)S1`o9=vnGYPDZ=;lEe)%I}$;_x{8Ds$%(Pi`EoB|M#fw_^~A)Ya`ArFBZ6X;lhUG z<9#0@*rHfoKA&lvZq#$yI?pm?!wTK#ZErwpW-OEcCf!_Ac6CR^$456erzf}R@UG;N zIyPb9URLk#xAXU(?NI%-E_%CPd`sQ6$n@Bfi>^=F9*Z$bn>?(TXH!}9(%-K&c}LLW z$gh{omKkqezjyE6rAwb~x$LK%#;+?n`_eBXWx4Y=HYP9kpMUQ05>=avMY;txx#h`S zZT~`FWGJ2!U9DMkuzr2`9oCf{lNTL{>O1df#4)QTK~DCHT;%l|LH7-o?OVB0El|Gv z|A*ECb2?mv|4BPleJ?1uVj*3X5z3KWI?v$EWd*78u3R@2CiSw-tg)NwSzc1QE&Woo zb@1Am=lGjKq6NNuP*~)+O(5lk^!3nn%^mMJ>o2}u$9KG9O=r~O`co6UQ#dw1c(eI@ z-P5VzTi#7EG%)7=_xF8${lCxi-LLIZWBmL1yuH7N+v3e0H{=dW7)jLixV31n+wrK8 zUCu*oa@U2XX0hP5kD970@7cRKpK&feE?a)5=(MhneEz!?KMa-`Zdtf(+qb3D+Zo}j5+?8eYV|_< zsh19xA2{K@#Z`f4_w|i;`SqXO%yn60`?IDzf0^;*cc;>J6t2O-)hlnf#+CmNSZD3y$F}^Ru91{^+aLQssZ(bfCbQjs^@%S^ zRynTr>($K5%UYG)zXo08*57j=Wn$1y(fD&chk1D`)Ho)7y%wGCnx<#K$9b$J{g_sy zMygxIOLG~2hGK&6#eGC>(%?DK+e?ER5xyq#nhzI5__*>h4-KHn~HLuJUvbK zGXFe&dAmQCy!HP+Xy*UI(S0+uaox39&+L?ZOBR0a{{DLZcikp;w6{CP3K%T-;T$Fs?zN(tlR*gaivN4D3-RL!6Q^A)!Xel7ZD;1oD>@8*4vqgL}g zZVzmct9*85X3pxRS9WNL&zba9ceBNGw*Mi&?i8OtJJZ-bF*;Y{^Ay7#|K^Y94`vVVRy^FAM4kb z=_j>M6m8snLNN72>FRyIefh3Su3KHSu(x3P6WJHiD~v?ABV%5@etpy0$jrn-a;XO6 z!#Tz0E^=RwnU*aVsPFsuR#vircKd)vsy0|Ky>zI25%|`i?9Gkm^Xuy_FZ1=;HEr6o zmBGus?2m~2&VF(6^W$forw;$&Z_YLj_4AV3*uVIll@~|XhF3d{H~;>;Lp)n_Ta!%m z?pqlqa)s$nee|XmzCU!VdhtacomqW5@;ATwR6qCP!M~-h%IshCMZ3QoQHuRjD9PDX zziwsVrI3IZ&(_F1j9aubqv=4CK73xBHu0m+gXLm>7Juh0WRlwI&TG)4*p_Hn_2ot3<70{j zd{c#L4>RaUEOoI^`o1Oe^1Hp?<9btiBoDiC_p_CKT;z0t;n0M>_c2Ny_FqEYOxcro zxUKwt?R57O#d}{an?0}UmFAR~Pa>%wBp+sYMEzOlp#Sp!kw;GVt;Ehw|MQuxe#*7; zp+C+ieV?n@eKbigCh?8k4W4yVn_und`2Dy0uEC9e_b+R$c^J1xqfmRVx9aZAg%_J| z&q+AxXLjOv@<$nV$E0Gz%X_czC#JUFR^M|aUq^oH)~!8P`|W;Ze13K|dVAj6ckk5h zZp)2Uo4oV?zu&iS-HPa`I{jRFY`&{eZFbo!p5t!c~uP@LB|YC)ehH--SuVb^*HDC zTjy?EJA>WF@X|rOhNrH%46_WAkM)4=F!=lHYmDDKo5=n3R$oGNR5nlB`|sCl8}+j2 z^82;swSo84zj%6l6X&yZd$;?_^UYlgkL_Rd_HmJ*x>d4#r?=CTZ;d|>wrKos+K~13 z{DJ0|_bx`(`|qePeJkZA-F^1<(OY|Y9=z|^zi+xq(!`7-wfWocHwi4>85y;t*WGh5 z|6%@EV++aN1B;v`-hJFZ$3H`*d*iByr?i>_);VeytdN&~^WsGUJHr(x(WrAF`qNX_ zUpM&HS`e4PQuIVs?zQ>3ucvz1bbotnTYICOd1}*=#ENSkY)`{lE}6xY-ApaNSE>Fx z;oI)?d6j9w!IL+&&FDItHSgx8)YBCPhuis|`xN|syZyByuWR)ekzbF_F2539lQRFt z+u)U45AU1ZQ&suie(iF}(%Lsg?hpSumELgsdx7c z`20h*O-~aDbS}e4?Rx4ET*SSsAb{AK@+z-`6>=*q0g0JGeQwCaWo2MsgPFQGRV(Y4W*nm&yP=xo3R4e5IivJmQ#-U{4f#v zuR9{>F3{F_;mG4%yR3LpEJ0Z&yV|Jc$A?+7X6^WIde*Y|nZS~(vKbzXdw-oXSjE_T zQlK|TBTl+4@T)-Wm%yYZ?&C>L{U)6*Nnc)Elo8L)&Q7)b67p$pikMODTW=k)*DqdJ ze7zE^zx&Om5G_~puiw5+5}F}8`{nv1!@~wVhi~X}Xj}0LmF#$uVpIO^&dbZo-{07% zJkv*Lo_n86eZusPeJGLM&_h^Nvl?xsNn6&bJ&0^?s`D{WtAvyh#4Vh6M0&Z)J5 z+Yae-Tox=jxbe7Lba3!wA2s`bKOS2aKdX5xeLrCThUkmw8%|Ezn<&wi_(p!t_b=yb z9z5#S-}mR!Y4hA$E)tJAY$`vcJmO1mdol6vh2V`7e2(}ozs$Aw>({TxL3<{WtdgFe zn=3O#f6oV}y%B5IUF^JRz;oE(8~-BC0yVW4XJ!~aUT*(4v%1>)jpKDva?VAqg|J@(E&bB2=v>8T-IC5w# zxxC!}|IPDtZD$_N_+AdYc>nX16`SQlzFj^t^-qT6 z1;HgH!V1E*_bVRvu3M+q^6+r`-$h6TH227<%+~KA9Aqx05wBJw4smm)G(6p3mnVpJ;bG=cyXf z<#kNARG2ANjaSyCBX(bnC3`AMk$aEC#*~vnW~R5d=U-2M60qv}7nLiupDz1PyTE@_ zqAgK^U2u2o`9{^))mChq9!}DTu8LAVVq#`?ujcbv@%WmL&(6*^RF+!|x>wRtuU$?- zM9a0ce^Mg9m)faIQ*Up}y}hGQS$4XQ+RJOpmMsg>D*gZWJLov5_wU~MsR&hRN>>M^ zygzBl`9Af*OHg20cxd)csqTujy7h98w3}E)Ufw*@>}z`}KXa%?Z_iWBVo*MxTU0meaEw2CX!(sl7 zS2QI#4$OGT;Cp@%|K`2&ji zFPFTv!`G?gT>iT6f}Y^?_LhLw?P(Hii8XE!Z&_d4udc0NYu>fXO6lE}%*$!#=2SlF zRNuB`OTvX~*W7$wzJ04YOQ!nGMvYFj7Z+c+gzLnxhUloQzISP#!(r8n;a+p*5dULCb2{x&xME-j0 zf5bGwRqOn|Hm=~40-?7AyXKyF6VtJkB_Tyg+v|y}by-1-+p_5|Bdw&Q%bX=lOicyn zedV9EbBP73;f3!LIO3xx)=h6qlyLjHcxkku)5nOWB2oQawj2&GLPZp;dLV}efTwA= zCJBW(wSNjw4Y^o-Qs~e*rc$ws;rhSwZ*EF`er|4VZf=Ry&Lgp|dA7|TdaD-go+p;d zURJ)*~Enb^&_>{kwg@S5Saf94DMLA)?FI=m2CW@Xbefc+(mHj67 z&Mnh8e0kozd81-eURtVYb9sN$fi>dVhx(=|^v;1KbCwXXGM2T^yVfspi9e}vQuUOX znVAqP$6?R^$=xST9csJc+LXn0fZ6f~Q>aAt%OqR=OY`Q<3(<0&q2HnygK3i}mYIx3*T+ATCe5tKm;Rxxp(1eHyJq7fdT si0xT*z-bex1Px&wxkj+E9Q@DjBC2?}F>%rf1_lNOPgg&ebxsLQ0OPT&*Z=?k From bf92c6b2e97ea161842e21fd826b68beb6e41e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 25 Oct 2020 12:16:19 +0100 Subject: [PATCH 004/110] Fix grade header item object comparision (#999) --- .../ui/modules/grade/details/GradeDetailsAdapter.kt | 8 -------- .../ui/modules/grade/details/GradeDetailsItem.kt | 5 +++-- .../ui/modules/grade/details/GradeDetailsPresenter.kt | 5 +++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index c129d9485..6f9321692 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -40,10 +40,6 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter -) +) { + var newGrades = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 9f4f8f267..53f5101a1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -213,9 +213,10 @@ class GradeDetailsPresenter @Inject constructor( subject = subject, average = average, pointsSum = points, - newGrades = grades.filter { grade -> !grade.isRead }.size, grades = subItems - ), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems + ).apply { + newGrades = grades.filter { grade -> !grade.isRead }.size + }, ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems }.flatten() } From 23e309d38ea8cd296c86c16ed05d84877ed17605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 26 Oct 2020 11:54:27 +0100 Subject: [PATCH 005/110] New Crowdin updates (#993) --- app/src/main/res/values-cs-rCZ/strings.xml | 9 ++++++++- app/src/main/res/values-de/strings.xml | 9 ++++++++- app/src/main/res/values-pl/strings.xml | 9 +++++++-- app/src/main/res/values-ru/strings.xml | 9 ++++++++- app/src/main/res/values-uk/strings.xml | 9 ++++++++- app/src/main/res/values/strings.xml | 2 +- 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index c55abd967..67babcd53 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -445,12 +445,19 @@ Zkopírováno Vrátit + + Stahování aktualizací začalo… + Aktualizace byla stažena. + Restartovat + Aktualizace selhala! Wulkanowy nemusí fungovat správně. Zvažte aktualizaci Žádné internetové připojení Vypršel časový limit připojení k denik - Přihlášení selhalo. Zkuste to znovu nebo restartujte aplikaci + Přihlášení selhalo. Zkus to znovu Je vyžadována změna hesla Probíhá údržba UONET+ deník. Zkuste to později znovu + Neznámá chyba denika UONET+. Prosím zkuste to znovu později + Neznámá chyba aplikace Došlo k neočekávané chybě Funkce deaktivována vaší školou Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c33c273ab..89af346c3 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -405,12 +405,19 @@ Kopiert lösen + + Download of updates has started… + An update has just been downloaded. + Restart + Update failed! Wulkanowy may not function properly. Consider updating Keine Internetverbindung Das Zeitlimit für die Verbindung zum Klassenbuch ist abgelaufen - Anmeldung fehlgeschlagen. Versuchen Sie es noch einmal oder starten Sie die Anwendung neu. + Login failed. Try again Passwortänderung erforderlich Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal + Unknown UONET + register error. Try again later + Unknown application error Ein unerwarteter Fehler ist aufgetreten Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8dea21234..bf77741cb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -445,14 +445,19 @@ Skopiowano Cofnij + + Rozpoczęto pobieranie aktualizacji… + Aktualizacja została pobrana. + Uruchom ponownie + Aktualizacja nie powiodła się! Wulkanowy może nie działać prawidłowo. Rozważ aktualizację Brak połączenia z internetem Upłynął limit czasu na połączenie z dziennikiem Logowanie nie powiodło się. Spróbuj ponownie Wymagana zmiana hasła Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później - Wystąpił nieznany błąd dziennika UONET+. Spróbuj ponownie później - Wystąpił nieznany błąd aplikacji + Nieznany błąd dziennika UONET+. Spróbuj ponownie później + Nieznany błąd aplikacji Wystąpił nieoczekiwany błąd Funkcja wyłączona przez szkołę Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 28ac69439..7a10553dc 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -445,12 +445,19 @@ Скопировано Отменить + + Download of updates has started… + An update has just been downloaded. + Restart + Update failed! Wulkanowy may not function properly. Consider updating Нет интернет-подключения Слишком долгое ожидание соединения с дневником - Авторизация не удалась. Попробуйте ещё раз или перезапустите дневник + Login failed. Try again Требуется смена пароля Технический перерыв в журнале UONET + продолжается. Попробуйте позже + Unknown UONET + register error. Try again later + Unknown application error Произошла неожиданная ошибка Функция была выключена школой Функция не доступна в этом режиме diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ed10d24d4..4a15db9f5 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -445,12 +445,19 @@ Скопійовано Відмінити + + Download of updates has started… + An update has just been downloaded. + Restart + Update failed! Wulkanowy may not function properly. Consider updating Брак з\'єднання з інтернетом Занадто довге очікування з\'єднання з щоденником - Аутентифікація не вдалася. Спробуйте ще раз або запустіть додаток знову + Login failed. Try again Потрібно змінити пароль Технічна перерва в журналі UONET + продовжується. Спробуйте пізніше + Unknown UONET + register error. Try again later + Unknown application error Відбулася несподівана помилка Функція вимкнена школою Функція не доступна в цьому режимі diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d45ef8dca..8e849573d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -473,7 +473,7 @@ Undo - Start downloading update… + Download of updates has started… An update has just been downloaded. Restart Update failed! Wulkanowy may not function properly. Consider updating From 8036f3d7f75fb9bed85489f95d887609ee63ceda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Thu, 29 Oct 2020 13:58:56 +0100 Subject: [PATCH 006/110] Add HMS flavor (#998) --- .gitignore | 3 + app/build.gradle | 18 ++++++ app/src/debug/agconnect-services.json | 33 +++++++++++ ...eAnalyticsHelper.kt => AnalyticsHelper.kt} | 2 +- .../{CrashlyticsUtils.kt => CrashLogUtils.kt} | 4 +- .../github/wulkanowy/utils/AnalyticsHelper.kt | 38 +++++++++++++ .../github/wulkanowy/utils/CrashLogUtils.kt | 52 ++++++++++++++++++ .../io/github/wulkanowy/utils/UpdateHelper.kt | 17 ++++++ app/src/main/AndroidManifest.xml | 5 ++ .../java/io/github/wulkanowy/WulkanowyApp.kt | 8 +-- .../ui/modules/about/AboutPresenter.kt | 4 +- .../modules/attendance/AttendancePresenter.kt | 4 +- .../summary/AttendanceSummaryPresenter.kt | 4 +- .../ui/modules/exam/ExamPresenter.kt | 4 +- .../ui/modules/grade/GradePresenter.kt | 4 +- .../grade/details/GradeDetailsPresenter.kt | 4 +- .../statistics/GradeStatisticsPresenter.kt | 4 +- .../grade/summary/GradeSummaryPresenter.kt | 4 +- .../ui/modules/homework/HomeworkPresenter.kt | 4 +- .../details/HomeworkDetailsPresenter.kt | 4 +- .../login/advanced/LoginAdvancedPresenter.kt | 4 +- .../modules/login/form/LoginFormPresenter.kt | 4 +- .../login/recover/LoginRecoverPresenter.kt | 4 +- .../LoginStudentSelectPresenter.kt | 4 +- .../login/symbol/LoginSymbolPresenter.kt | 4 +- .../luckynumber/LuckyNumberPresenter.kt | 4 +- .../wulkanowy/ui/modules/main/MainActivity.kt | 4 +- .../ui/modules/main/MainPresenter.kt | 4 +- .../preview/MessagePreviewPresenter.kt | 4 +- .../message/send/SendMessagePresenter.kt | 4 +- .../message/tab/MessageTabPresenter.kt | 4 +- .../mobiledevice/MobileDevicePresenter.kt | 4 +- .../token/MobileDeviceTokenPresenter.kt | 4 +- .../ui/modules/note/NotePresenter.kt | 4 +- .../school/SchoolPresenter.kt | 4 +- .../teacher/TeacherPresenter.kt | 4 +- .../ui/modules/settings/SettingsPresenter.kt | 4 +- .../modules/timetable/TimetablePresenter.kt | 4 +- .../completed/CompletedLessonsPresenter.kt | 4 +- .../TimetableWidgetProvider.kt | 4 +- ...eAnalyticsHelper.kt => AnalyticsHelper.kt} | 7 ++- .../wulkanowy/utils/CrashlyticsUtils.kt | 4 +- app/src/release/agconnect-services.json.gpg | Bin 0 -> 608 bytes .../login/form/LoginFormPresenterTest.kt | 4 +- .../LoginStudentSelectPresenterTest.kt | 4 +- .../ui/modules/main/MainPresenterTest.kt | 4 +- build.gradle | 5 +- 47 files changed, 250 insertions(+), 78 deletions(-) create mode 100644 app/src/debug/agconnect-services.json rename app/src/fdroid/java/io/github/wulkanowy/utils/{FirebaseAnalyticsHelper.kt => AnalyticsHelper.kt} (86%) rename app/src/fdroid/java/io/github/wulkanowy/utils/{CrashlyticsUtils.kt => CrashLogUtils.kt} (71%) create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt create mode 100644 app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt rename app/src/play/java/io/github/wulkanowy/utils/{FirebaseAnalyticsHelper.kt => AnalyticsHelper.kt} (78%) create mode 100644 app/src/release/agconnect-services.json.gpg diff --git a/.gitignore b/.gitignore index d3fb6e4e9..5daeb6b97 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,6 @@ Thumbs.db !/gradle/wrapper/gradle-wrapper.jar .idea/jarRepositories.xml + + +app/src/release/agconnect-services.json diff --git a/app/build.gradle b/app/build.gradle index 363eb74bb..f238eecc7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,12 +69,26 @@ android { flavorDimensions "platform" productFlavors { + hms { + dimension "platform" + minSdkVersion 19 + manifestPlaceholders = [ + install_channel: "AppGallery" + ] + } + play { dimension "platform" + manifestPlaceholders = [ + install_channel: "Google Play" + ] } fdroid { dimension "platform" + manifestPlaceholders = [ + install_channel: "F-Droid" + ] } } @@ -192,6 +206,9 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' + hmsImplementation 'com.huawei.hms:hianalytics:5.0.4.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300' + releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" @@ -210,3 +227,4 @@ dependencies { } apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.huawei.agconnect' diff --git a/app/src/debug/agconnect-services.json b/app/src/debug/agconnect-services.json new file mode 100644 index 000000000..48192df01 --- /dev/null +++ b/app/src/debug/agconnect-services.json @@ -0,0 +1,33 @@ +{ + "agcgw":{ + "backurl":"connect-dre.dbankcloud.cn", + "url":"connect-dre.hispace.hicloud.com" + }, + "client":{ + "cp_id":"890048000024105546", + "product_id":"", + "client_id":"", + "client_secret":"", + "app_id":"101440411", + "package_name":"io.github.wulkanowy.dev", + "api_key":"" + }, + "service":{ + "analytics":{ + "collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "resource_id":"p1", + "channel_id":"" + }, + "search":{ + "url":"https://search-dre.cloud.huawei.com" + }, + "cloudstorage":{ + "storage_url":"https://ops-dre.agcstorage.link" + }, + "ml":{ + "mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn" + } + }, + "region":"DE", + "configuration_version":"1.0" +} diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt similarity index 86% rename from app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt rename to app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index f23645bc3..0cd9a52e4 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -6,7 +6,7 @@ import javax.inject.Singleton @Singleton @Suppress("UNUSED_PARAMETER") -class FirebaseAnalyticsHelper @Inject constructor() { +class AnalyticsHelper @Inject constructor() { fun logEvent(name: String, vararg params: Pair) { // do nothing diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashLogUtils.kt similarity index 71% rename from app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt rename to app/src/fdroid/java/io/github/wulkanowy/utils/CrashLogUtils.kt index d03a319a2..5d58270d4 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -8,6 +8,6 @@ open class TimberTreeNoOp : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {} } -class CrashlyticsTree : TimberTreeNoOp() +class CrashLogTree : TimberTreeNoOp() -class CrashlyticsExceptionTree : TimberTreeNoOp() +class CrashLogExceptionTree : TimberTreeNoOp() diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt new file mode 100644 index 000000000..b3cecf243 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.utils + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import com.huawei.hms.analytics.HiAnalytics +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AnalyticsHelper @Inject constructor( + @ApplicationContext private val context: Context +) { + + private val analytics by lazy { HiAnalytics.getInstance(context) } + + fun logEvent(name: String, vararg params: Pair) { + Bundle().apply { + params.forEach { + if (it.second == null) return@forEach + when (it.second) { + is String, is String? -> putString(it.first, it.second as String) + is Int, is Int? -> putInt(it.first, it.second as Int) + is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean) + } + } + analytics.onEvent(name, this) + } + } + + fun setCurrentScreen(activity: Activity, name: String?) { + analytics.onEvent("screen_view", Bundle().apply { + putString("screen_name", name) + putString("screen_class", activity::class.simpleName) + }) + } +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt new file mode 100644 index 000000000..7f4bedae4 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -0,0 +1,52 @@ +package io.github.wulkanowy.utils + +import android.util.Log +import com.huawei.agconnect.crash.AGConnectCrash +import fr.bipi.tressence.base.FormatterPriorityTree +import fr.bipi.tressence.common.StackTraceRecorder +import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException +import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException + +class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { + + private val connectCrash by lazy { AGConnectCrash.getInstance() } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (skipLog(priority, tag, message, t)) return + + connectCrash.log(format(priority, tag, message)) + } +} + +class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { + + private val connectCrash by lazy { AGConnectCrash.getInstance() } + + override fun skipLog(priority: Int, tag: String?, message: String, t: Throwable?): Boolean { + return when (t) { + is FeatureDisabledException, + is FeatureNotAvailableException, + is UnknownHostException, + is SocketTimeoutException, + is InterruptedIOException -> true + else -> super.skipLog(priority, tag, message, t) + } + } + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (skipLog(priority, tag, message, t)) return + + connectCrash.setCustomKey("priority", priority) + connectCrash.setCustomKey("tag", tag.orEmpty()) + connectCrash.setCustomKey("message", message) + connectCrash.log(priority, t?.stackTraceToString()) + if (t != null) { + connectCrash.log(priority, t.stackTraceToString()) + } else { + connectCrash.log(priority, StackTraceRecorder(format(priority, tag, message)).stackTraceToString()) + } + } +} diff --git a/app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt new file mode 100644 index 000000000..3abab9629 --- /dev/null +++ b/app/src/hms/java/io/github/wulkanowy/utils/UpdateHelper.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.utils + +import android.app.Activity +import android.view.View +import javax.inject.Inject + +@Suppress("UNUSED_PARAMETER") +class UpdateHelper @Inject constructor() { + + lateinit var messageContainer: View + + fun checkAndInstallUpdates(activity: Activity) {} + + fun onActivityResult(requestCode: Int, resultCode: Int) {} + + fun onResume(activity: Activity) {} +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ec2f7816..a8d2b49e3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -110,6 +110,11 @@ android:resource="@xml/provider_paths" /> + + + (errorHandler, studentRepository) { override fun onAttachView(view: AboutView) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 158f08ba3..19f8523d4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -9,7 +9,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResourceIn @@ -34,7 +34,7 @@ class AttendancePresenter @Inject constructor( private val attendanceRepository: AttendanceRepository, private val semesterRepository: SemesterRepository, private val prefRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = now().previousOrSameSchoolDay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index e5dce9ac8..4ce49d960 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.subject.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach @@ -22,7 +22,7 @@ class AttendanceSummaryPresenter @Inject constructor( private val attendanceSummaryRepository: AttendanceSummaryRepository, private val subjectRepository: SubjectRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var subjects = emptyList() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index f63316a8d..406d011c0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday @@ -30,7 +30,7 @@ class ExamPresenter @Inject constructor( studentRepository: StudentRepository, private val examRepository: ExamRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = now().nextOrSameSchoolDay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index d4c9f210a..e91c94eb2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCurrentOrLast import kotlinx.coroutines.delay @@ -18,7 +18,7 @@ class GradePresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { var selectedIndex = 0 diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 53f5101a1..a34dd7acb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -13,7 +13,7 @@ import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResourceIn @@ -29,7 +29,7 @@ class GradeDetailsPresenter @Inject constructor( private val semesterRepository: SemesterRepository, private val preferencesRepository: PreferencesRepository, private val averageProvider: GradeAverageProvider, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var newGradesAmount: Int = 0 diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 73cee9e96..2f46d21db 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -9,7 +9,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.subject.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach @@ -23,7 +23,7 @@ class GradeStatisticsPresenter @Inject constructor( private val subjectRepository: SubjectRepository, private val semesterRepository: SemesterRepository, private val preferencesRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var subjects = emptyList() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index caa3cb84d..7ce98d105 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach @@ -18,7 +18,7 @@ class GradeSummaryPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val averageProvider: GradeAverageProvider, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index 8af0d83f6..f264d58e0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday @@ -29,7 +29,7 @@ class HomeworkPresenter @Inject constructor( studentRepository: StudentRepository, private val homeworkRepository: HomeworkRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index e485dd748..1d4dac2cb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -17,7 +17,7 @@ class HomeworkDetailsPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val homeworkRepository: HomeworkRepository, - private val analytics: FirebaseAnalyticsHelper, + private val analytics: AnalyticsHelper, private val preferencesRepository: PreferencesRepository ) : BasePresenter(errorHandler, studentRepository) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt index dfe82ad8b..9dfc3011b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank @@ -17,7 +17,7 @@ import javax.inject.Inject class LoginAdvancedPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(loginErrorHandler, studentRepository) { override fun onAttachView(view: LoginAdvancedView) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index b921140c7..3e77cd775 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -4,7 +4,7 @@ import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank @@ -15,7 +15,7 @@ import javax.inject.Inject class LoginFormPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index e58377058..319541b8e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -4,7 +4,7 @@ import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.recover.RecoverRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank @@ -15,7 +15,7 @@ import javax.inject.Inject class LoginRecoverPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: RecoverErrorHandler, - private val analytics: FirebaseAnalyticsHelper, + private val analytics: AnalyticsHelper, private val recoverRepository: RecoverRepository ) : BasePresenter(loginErrorHandler, studentRepository) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index 1384f25a0..dc8241992 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank import kotlinx.coroutines.flow.onEach @@ -17,7 +17,7 @@ import javax.inject.Inject class LoginStudentSelectPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt index a2cda4fc1..9f19539a0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt @@ -4,7 +4,7 @@ import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.ifNullOrBlank @@ -16,7 +16,7 @@ import javax.inject.Inject class LoginSymbolPresenter @Inject constructor( studentRepository: StudentRepository, private val loginErrorHandler: LoginErrorHandler, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(loginErrorHandler, studentRepository) { private var lastError: Throwable? = null diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index 90aea441e..cfd793f20 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -5,7 +5,7 @@ import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach @@ -16,7 +16,7 @@ class LuckyNumberPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val luckyNumberRepository: LuckyNumberRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index abc002de2..245f3b16c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -39,7 +39,7 @@ import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.UpdateHelper import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getThemeAttrColor @@ -55,7 +55,7 @@ class MainActivity : BaseActivity(), MainVie override lateinit var presenter: MainPresenter @Inject - lateinit var analytics: FirebaseAnalyticsHelper + lateinit var analytics: AnalyticsHelper @Inject lateinit var updateHelper: UpdateHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 8d5c9d67f..7ea8197e7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import timber.log.Timber import javax.inject.Inject @@ -17,7 +17,7 @@ class MainPresenter @Inject constructor( studentRepository: StudentRepository, private val prefRepository: PreferencesRepository, private val syncManager: SyncManager, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { fun onAttachView(view: MainView, initMenu: MainView.Section?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 7a633a2df..039c7f140 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -11,7 +11,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResourceIn @@ -24,7 +24,7 @@ class MessagePreviewPresenter @Inject constructor( errorHandler: ErrorHandler, studentRepository: StudentRepository, private val messageRepository: MessageRepository, - private val analytics: FirebaseAnalyticsHelper, + private val analytics: AnalyticsHelper, private var appInfo: AppInfo ) : BasePresenter(errorHandler, studentRepository) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index f877c8096..e06c49635 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -11,7 +11,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.toFormattedString @@ -27,7 +27,7 @@ class SendMessagePresenter @Inject constructor( private val reportingUnitRepository: ReportingUnitRepository, private val recipientRepository: RecipientRepository, private val preferencesRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { fun onAttachView(view: SendMessageView, message: Message?, reply: Boolean?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 79062dcfc..2143a4882 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -8,7 +8,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.toFormattedString @@ -32,7 +32,7 @@ class MessageTabPresenter @Inject constructor( studentRepository: StudentRepository, private val messageRepository: MessageRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { lateinit var folder: MessageFolder diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index 6314c2c31..456609e43 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResourceIn @@ -21,7 +21,7 @@ class MobileDevicePresenter @Inject constructor( studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val mobileDeviceRepository: MobileDeviceRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt index f6d0de992..8270693d9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach @@ -18,7 +18,7 @@ class MobileDeviceTokenPresenter @Inject constructor( studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val mobileDeviceRepository: MobileDeviceRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { override fun onAttachView(view: MobileDeviceTokenVIew) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index db3b495a8..2e8bec5d1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResourceIn @@ -21,7 +21,7 @@ class NotePresenter @Inject constructor( studentRepository: StudentRepository, private val noteRepository: NoteRepository, private val semesterRepository: SemesterRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index 4cda5d33c..554d422df 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach @@ -18,7 +18,7 @@ class SchoolPresenter @Inject constructor( studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val schoolRepository: SchoolRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var address: String? = null diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index ff02116b9..0c58317b1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.teacher.TeacherRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import kotlinx.coroutines.flow.onEach @@ -18,7 +18,7 @@ class TeacherPresenter @Inject constructor( studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, private val teacherRepository: TeacherRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private lateinit var lastError: Throwable diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index 3d1e063e2..e640dd60d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -9,7 +9,7 @@ import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AppInfo -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.isHolidays import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach @@ -22,7 +22,7 @@ class SettingsPresenter @Inject constructor( studentRepository: StudentRepository, private val preferencesRepository: PreferencesRepository, private val timetableNotificationHelper: TimetableNotificationSchedulerHelper, - private val analytics: FirebaseAnalyticsHelper, + private val analytics: AnalyticsHelper, private val syncManager: SyncManager, private val chuckerCollector: ChuckerCollector, private val appInfo: AppInfo diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 9aebfd04e..0e913acf6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -9,7 +9,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday @@ -34,7 +34,7 @@ class TimetablePresenter @Inject constructor( private val timetableRepository: TimetableRepository, private val semesterRepository: SemesterRepository, private val prefRepository: PreferencesRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(errorHandler, studentRepository) { private var baseDate: LocalDate = now().nextOrSameSchoolDay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index f1c2cedc6..e5551ed5f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -7,7 +7,7 @@ import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRe import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResourceIn import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday @@ -30,7 +30,7 @@ class CompletedLessonsPresenter @Inject constructor( private val completedLessonsErrorHandler: CompletedLessonsErrorHandler, private val semesterRepository: SemesterRepository, private val completedLessonsRepository: CompletedLessonsRepository, - private val analytics: FirebaseAnalyticsHelper + private val analytics: AnalyticsHelper ) : BasePresenter(completedLessonsErrorHandler, studentRepository) { private var baseDate: LocalDate = now().nextOrSameSchoolDay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 60ad9d5cc..18e0ea562 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -24,7 +24,7 @@ import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.utils.FirebaseAnalyticsHelper +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.previousSchoolDay @@ -49,7 +49,7 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { lateinit var sharedPref: SharedPrefProvider @Inject - lateinit var analytics: FirebaseAnalyticsHelper + lateinit var analytics: AnalyticsHelper companion object { diff --git a/app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt similarity index 78% rename from app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt rename to app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index f7e4a0fc0..8897f180d 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/FirebaseAnalyticsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -9,7 +9,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class FirebaseAnalyticsHelper @Inject constructor( +class AnalyticsHelper @Inject constructor( @ApplicationContext private val context: Context ) { @@ -30,6 +30,9 @@ class FirebaseAnalyticsHelper @Inject constructor( } fun setCurrentScreen(activity: Activity, name: String?) { - analytics.setCurrentScreen(activity, name, null) + analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, Bundle().apply { + putString(FirebaseAnalytics.Param.SCREEN_NAME, name) + putString(FirebaseAnalytics.Param.SCREEN_CLASS, activity::class.simpleName) + }) } } diff --git a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt index d7850b072..b26f4f6ba 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/CrashlyticsUtils.kt @@ -10,7 +10,7 @@ import java.io.InterruptedIOException import java.net.SocketTimeoutException import java.net.UnknownHostException -class CrashlyticsTree : FormatterPriorityTree(Log.VERBOSE) { +class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { private val crashlytics by lazy { FirebaseCrashlytics.getInstance() } @@ -21,7 +21,7 @@ class CrashlyticsTree : FormatterPriorityTree(Log.VERBOSE) { } } -class CrashlyticsExceptionTree : FormatterPriorityTree(Log.ERROR) { +class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { private val crashlytics by lazy { FirebaseCrashlytics.getInstance() } diff --git a/app/src/release/agconnect-services.json.gpg b/app/src/release/agconnect-services.json.gpg new file mode 100644 index 0000000000000000000000000000000000000000..484d8bc72d7a15b689a3fb76cddd91f1992cebf4 GIT binary patch literal 608 zcmeC-WnpJ#x{|fQYq?^{?hltWG3LCrj>*&9acxT8 zenrbN!D{v|T*0w*XS%L^Sb9*_Tk4?jeV1R2JD1dM64L&9A>>KjrzZ`;kK~(Y{Jt+; z)bDJ0?*Ej1c}}9y4D0U}&d9vu6Z%xe-qF&wPk++Yy*F;Zs*2Q7TXvaKwqe?>E3b?G zdK9-XSUz(@jfchUcT{rL0@)a|GUkmJOb{FkC$JQ z<9qd5f0pN$s^e^HO*OcBV|XsGG3abCtw>m8GjY=T)lWH+f*=1sbi*lPLHV{M=biou zOO4-7_h*9V_;&zm=sJ!mvAJ zv*7Qg%cn7C#@vW}SJ9r;-}HOaW}{FS^<({6qQ&2CW+=M^_A?7Hd^j4{7;^OKjISKR7LmZLtn6cQ|L6U~ur$r?+|kZ4&s-FIg{O8v5|;#tTV|E&Ch8nEZvEA51G> zF!yZDzuEVEENhqdm7inF+p=lJozs`nSoU)+3l5pHXw5z|rgldYkJ)}+E2c5G2Bdgw zz0~8>ILTT}KlYGWN?rQT$!WL$)~*a`F5Ta>kSltz^?%p-Nm54^bL|gZ(O44p Date: Fri, 30 Oct 2020 00:54:35 +0100 Subject: [PATCH 007/110] New Crowdin updates (#1002) --- app/src/main/res/values-pl/strings.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index bf77741cb..99fd05dbd 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -22,12 +22,12 @@ Semestr %1$d, %2$d/%3$d Zaloguj się za pomocą konta ucznia lub rodzica - Podaj symbol + Podaj symbol ze strony dziennika Nazwa użytkownika Email Login, PESEL lub e-mail Hasło - Dziennik UONET+ + Odmiana dziennika UONET+ Mobilne API Scraper Hybrydowe @@ -37,17 +37,17 @@ Symbol Zaloguj To hasło jest za krótkie - Dane logowania są niepoprawne. Upewnij się, że został wybrany odpowiedni dziennik UONET+ w polu poniżej + Dane logowania są niepoprawne. Upewnij się, że została wybrana odpowiednia odmiana dziennika UONET+ w polu poniżej Nieprawidłowy PIN Nieprawidłowy token Token stracił ważność Niepoprawny adres email - Niepoprawny login + Użyj przydzielonego loginu zamiast emaila Niepoprawny symbol - Nie znaleziono ucznia. Sprawdź symbol + Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+ To pole jest wymagane Wybrany uczeń jest już zalogowany - Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUpewnij się, że w polu Dziennik UONET+ na poprzednim ekranie został ustawiony odpowiedni dziennik + Symbol znajdziesz na stronie dziennika w Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nUpewnij się, że w polu Dziennik UONET+ na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika. Wulkanowy na chwilę obecną nie wykrywa uczniów przedszkolnych Wybierz uczniów do zalogowania w aplikacji Inne opcje W tym trybie nie działa szczęśliwy numerek, uczeń na tle klasy, podsumowanie frekwencji, usprawiedliwianie nieobecności, lekcje zrealizowane, informacje o szkole i podgląd listy zarejestrowanych urządzeń @@ -59,7 +59,7 @@ Discord Wyślij email Opisz problem: - Upewnij się, że został wybrany odpowiedni dziennik UONET+! + Upewnij się, że została wybrana odpowiednia odmiana dziennika UONET+! Nie pamiętam hasła Przywróć swoje konto Przywróć @@ -452,12 +452,12 @@ Aktualizacja nie powiodła się! Wulkanowy może nie działać prawidłowo. Rozważ aktualizację Brak połączenia z internetem - Upłynął limit czasu na połączenie z dziennikiem - Logowanie nie powiodło się. Spróbuj ponownie - Wymagana zmiana hasła + Nie udało się połączyć z dziennikiem. Serwery mogą być przeciążone. Spróbuj ponownie później + Ładowanie danych nie powiodło się. Spróbuj ponownie później + Wymagana zmiana hasła do dziennika Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później Nieznany błąd dziennika UONET+. Spróbuj ponownie później - Nieznany błąd aplikacji + Nieznany błąd aplikacji. Spróbuj ponownie później Wystąpił nieoczekiwany błąd Funkcja wyłączona przez szkołę Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API From 24d0c5057b6c26f0a957ad9d0a9d564eb35436b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 30 Oct 2020 01:49:30 +0100 Subject: [PATCH 008/110] Version 0.22.1 --- .travis.yml | 2 +- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 9 ++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index e383a74b5..58fece973 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.22.0 + - 0.22.1 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index f238eecc7..ec5088143 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 30 - versionCode 73 - versionName "0.22.0" + versionCode 74 + versionName "0.22.1" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -126,7 +126,7 @@ play { serviceAccountCredentials = file('key.p12') defaultToAppBundles = false track = 'alpha' - updatePriority = 0 + updatePriority = 1 } ext { @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.22.0" + implementation "io.github.wulkanowy:sdk:0.22.1" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index e89c473fa..f9f0d9bbe 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,7 +1,6 @@ -Wersja 0.22 -- naprawiliśmy krytyczny błąd ze stabilnością przy ładowaniu danych -- naprawiliśmy skalowanie tekstu przy bardzo dużych jego rozmiarach -- naprawiliśmy wyświetlanie powiadomień o odwołanych lekcjach (nie powinny się już pokazywać) -- dodaliśmy informowanie o dostępności aktualizacji w aplikacji +Wersja 0.22.1 +- naprawiliśmy problem z wyświetlaniem pozycji na liście ocen +- zmieniliśmy komunikaty o błędach, które powinny być teraz czytelniejsze dla większej liczby użytkowników +- zwiększyliśmy maksymalny czas, przez który aplikacja będzie próbowała łączyć się z dziennikiem do 1 minuty Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From c634c64e7050f9f751c1e4819e088b96c2552fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 30 Oct 2020 12:24:00 +0100 Subject: [PATCH 009/110] Update hianalytics to 5.0.4.301 (#1003) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index ec5088143..17733d44d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,7 +206,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' - hmsImplementation 'com.huawei.hms:hianalytics:5.0.4.300' + hmsImplementation 'com.huawei.hms:hianalytics:5.0.4.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From c6f4c868b2405b80805e7859717584d5d8829420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 30 Oct 2020 12:31:41 +0100 Subject: [PATCH 010/110] Version 0.22.2 --- .travis.yml | 3 ++- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58fece973..26abd2025 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.22.1 + - 0.22.2 android: licenses: @@ -58,6 +58,7 @@ script: - | if [ $TRAVIS_TAG ]; then gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg; + gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; ./gradlew publishPlayRelease -PenableFirebase --stacktrace; diff --git a/app/build.gradle b/app/build.gradle index 17733d44d..541aff36c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 30 - versionCode 74 - versionName "0.22.1" + versionCode 75 + versionName "0.22.2" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.22.1" + implementation "io.github.wulkanowy:sdk:0.22.2" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index f9f0d9bbe..23fd77bc3 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,4 +1,4 @@ -Wersja 0.22.1 +Wersja 0.22.2 - naprawiliśmy problem z wyświetlaniem pozycji na liście ocen - zmieniliśmy komunikaty o błędach, które powinny być teraz czytelniejsze dla większej liczby użytkowników - zwiększyliśmy maksymalny czas, przez który aplikacja będzie próbowała łączyć się z dziennikiem do 1 minuty From 883024018201db26a5cba85b678c15511035f271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 1 Nov 2020 16:53:31 +0100 Subject: [PATCH 011/110] Add conferences (#1004) --- .../28.json | 1842 +++++++++++++++++ .../github/wulkanowy/data/RepositoryModule.kt | 4 + .../github/wulkanowy/data/db/AppDatabase.kt | 11 +- .../wulkanowy/data/db/dao/ConferenceDao.kt | 15 + .../wulkanowy/data/db/entities/Conference.kt | 35 + .../data/db/migrations/Migration28.kt | 23 + .../conference/ConferenceLocal.kt | 25 + .../conference/ConferenceRemote.kt | 31 + .../conference/ConferenceRepository.kt | 25 + .../modules/conference/ConferenceAdapter.kt | 36 + .../modules/conference/ConferenceFragment.kt | 102 + .../modules/conference/ConferencePresenter.kt | 96 + .../ui/modules/conference/ConferenceView.kt | 29 + .../wulkanowy/ui/modules/more/MoreFragment.kt | 8 + .../ui/modules/more/MorePresenter.kt | 2 + .../wulkanowy/ui/modules/more/MoreView.kt | 4 + .../main/res/drawable/ic_more_conferences.xml | 9 + .../main/res/layout/fragment_conference.xml | 104 + app/src/main/res/layout/item_conference.xml | 87 + app/src/main/res/values/strings.xml | 5 + 20 files changed, 2491 insertions(+), 2 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt create mode 100644 app/src/main/res/drawable/ic_more_conferences.xml create mode 100644 app/src/main/res/layout/fragment_conference.xml create mode 100644 app/src/main/res/layout/item_conference.xml diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json new file mode 100644 index 000000000..c7c4c0331 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/28.json @@ -0,0 +1,1842 @@ +{ + "formatVersion": 1, + "database": { + "version": 28, + "identityHash": "3a449a55ea73fbfbb7973f1f3f834e10", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `grade` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `is_semester` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "grade", + "columnName": "grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semester", + "columnName": "is_semester", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3a449a55ea73fbfbb7973f1f3f834e10')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 6486cab97..50642ddd5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -154,4 +154,8 @@ internal class RepositoryModule { @Singleton @Provides fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao + + @Singleton + @Provides + fun provideConferenceDao(database: AppDatabase) = database.conferenceDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index ebd5119fa..93f225427 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -10,6 +10,7 @@ import androidx.room.migration.Migration import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao @@ -32,6 +33,7 @@ import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradePointsStatistics @@ -70,6 +72,7 @@ import io.github.wulkanowy.data.db.migrations.Migration24 import io.github.wulkanowy.data.db.migrations.Migration25 import io.github.wulkanowy.data.db.migrations.Migration26 import io.github.wulkanowy.data.db.migrations.Migration27 +import io.github.wulkanowy.data.db.migrations.Migration28 import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration5 @@ -103,7 +106,8 @@ import javax.inject.Singleton Recipient::class, MobileDevice::class, Teacher::class, - School::class + School::class, + Conference::class, ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -112,7 +116,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 27 + const val VERSION_SCHEMA = 28 fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array { return arrayOf( @@ -142,6 +146,7 @@ abstract class AppDatabase : RoomDatabase() { Migration25(), Migration26(), Migration27(), + Migration28(), ) } @@ -198,4 +203,6 @@ abstract class AppDatabase : RoomDatabase() { abstract val teacherDao: TeacherDao abstract val schoolDao: SchoolDao + + abstract val conferenceDao: ConferenceDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt new file mode 100644 index 000000000..4ed9aecf5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ConferenceDao.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.Conference +import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton + +@Dao +@Singleton +interface ConferenceDao : BaseDao { + + @Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId") + fun loadAll(diaryId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt new file mode 100644 index 000000000..8ddcbbb0b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Conference.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.LocalDateTime + +@Entity(tableName = "Conferences") +data class Conference( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "diary_id") + val diaryId: Int, + + val title: String, + + val subject: String, + + val agenda: String, + + @ColumnInfo(name = "present_on_conference") + val presentOnConference: String, + + @ColumnInfo(name = "conference_id") + val conferenceId: Int, + + val date: LocalDateTime +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt new file mode 100644 index 000000000..51e7628b5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration28.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration28 : Migration(27, 28) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL(""" + CREATE TABLE IF NOT EXISTS Conferences ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + diary_id INTEGER NOT NULL, + title TEXT NOT NULL, + subject TEXT NOT NULL, + agenda TEXT NOT NULL, + present_on_conference TEXT NOT NULL, + conference_id INTEGER NOT NULL, + date INTEGER NOT NULL + ) + """) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt new file mode 100644 index 000000000..9f56641f7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.data.repositories.conference + +import io.github.wulkanowy.data.db.dao.ConferenceDao +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ConferenceLocal @Inject constructor(private val conferenceDb: ConferenceDao) { + + fun getConferences(student: Student, semester: Semester): Flow> { + return conferenceDb.loadAll(semester.diaryId, student.studentId) + } + + suspend fun saveConferences(items: List) { + conferenceDb.insertAll(items) + } + + suspend fun deleteConferences(items: List) { + conferenceDb.deleteAll(items) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt new file mode 100644 index 000000000..50e869a99 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.data.repositories.conference + +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ConferenceRemote @Inject constructor(private val sdk: Sdk) { + + suspend fun getConferences(student: Student, semester: Semester): List { + return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getConferences() + .map { + it.agenda + Conference( + studentId = student.studentId, + diaryId = semester.diaryId, + agenda = it.agenda, + conferenceId = it.id, + date = it.date, + presentOnConference = it.presentOnConference, + subject = it.subject, + title = it.title + ) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt new file mode 100644 index 000000000..187ecf58f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.data.repositories.conference + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ConferenceRepository @Inject constructor( + private val local: ConferenceLocal, + private val remote: ConferenceRemote +) { + + fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getConferences(student, semester) }, + fetch = { remote.getConferences(student, semester) }, + saveFetchResult = { old, new -> + local.deleteConferences(old uniqueSubtract new) + local.saveConferences(new uniqueSubtract old) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt new file mode 100644 index 000000000..c87286149 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceAdapter.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.ui.modules.conference + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.databinding.ItemConferenceBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class ConferenceAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemConferenceBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + with(holder.binding) { + conferenceItemDate.text = item.date.toFormattedString("dd.MM.yyyy HH:mm") + conferenceItemName.text = item.presentOnConference + conferenceItemTitle.text = item.title + conferenceItemSubject.text = item.subject + conferenceItemContent.text = item.agenda + conferenceItemContent.visibility = if (item.agenda.isBlank()) View.GONE else View.VISIBLE + } + } + + class ItemViewHolder(val binding: ItemConferenceBinding) : RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt new file mode 100644 index 000000000..74d938975 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceFragment.kt @@ -0,0 +1,102 @@ +package io.github.wulkanowy.ui.modules.conference + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.databinding.FragmentConferenceBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import javax.inject.Inject + +@AndroidEntryPoint +class ConferenceFragment : BaseFragment(R.layout.fragment_conference), + ConferenceView, MainView.TitledView { + + @Inject + lateinit var presenter: ConferencePresenter + + @Inject + lateinit var conferencesAdapter: ConferenceAdapter + + companion object { + fun newInstance() = ConferenceFragment() + } + + override val isViewEmpty: Boolean + get() = conferencesAdapter.items.isEmpty() + + override val titleStringId: Int + get() = R.string.conferences_title + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentConferenceBinding.bind(view) + messageContainer = binding.conferenceRecycler + presenter.onAttachView(this) + } + + override fun initView() { + with(binding.conferenceRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = conferencesAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + conferenceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + conferenceErrorRetry.setOnClickListener { presenter.onRetry() } + conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() } + } + } + + override fun updateData(data: List) { + with(conferencesAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(conferencesAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun hideRefresh() { + binding.conferenceSwipe.isRefreshing = false + } + + override fun showProgress(show: Boolean) { + binding.conferenceProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showEmpty(show: Boolean) { + binding.conferenceEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showErrorView(show: Boolean) { + binding.conferenceError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.conferenceErrorMessage.text = message + } + + override fun enableSwipe(enable: Boolean) { + binding.conferenceSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt new file mode 100644 index 000000000..71b412da3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt @@ -0,0 +1,96 @@ +package io.github.wulkanowy.ui.modules.conference + +import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.repositories.conference.ConferenceRepository +import io.github.wulkanowy.data.repositories.semester.SemesterRepository +import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject + +class ConferencePresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val conferenceRepository: ConferenceRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private lateinit var lastError: Throwable + + override fun onAttachView(view: ConferenceView) { + super.onAttachView(view) + view.initView() + Timber.i("Conferences view was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData() + } + + fun onSwipeRefresh() { + loadData(true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(true) + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + private fun loadData(forceRefresh: Boolean = false) { + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + conferenceRepository.getConferences(student, semester, forceRefresh) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading conference data started") + Status.SUCCESS -> { + Timber.i("Loading conference result: Success") + view?.run { + updateData(it.data!!.sortedByDescending { conference -> conference.date }) + showContent(it.data.isNotEmpty()) + showEmpty(it.data.isEmpty()) + showErrorView(false) + } + analytics.logEvent( + "load_data", + "type" to "conferences", + "items" to it.data!!.size + ) + } + Status.ERROR -> { + Timber.i("Loading conference result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt new file mode 100644 index 000000000..37845a6ff --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceView.kt @@ -0,0 +1,29 @@ +package io.github.wulkanowy.ui.modules.conference + +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.ui.base.BaseView + +interface ConferenceView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun hideRefresh() + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showContent(show: Boolean) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index 1bdcc26fa..bf8918fce 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.databinding.FragmentMoreBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.about.AboutFragment +import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment import io.github.wulkanowy.ui.modules.main.MainActivity @@ -53,6 +54,9 @@ class MoreFragment : BaseFragment(R.layout.fragment_more), override val mobileDevicesRes: Pair? get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) } + override val conferencesRes: Pair? + get() = context?.run { getString(R.string.conferences_title) to getCompatDrawable(R.drawable.ic_more_conferences) } + override val schoolAndTeachersRes: Pair? get() = context?.run { getString(R.string.schoolandteachers_title) to getCompatDrawable((R.drawable.ic_more_schoolandteachers)) } @@ -108,6 +112,10 @@ class MoreFragment : BaseFragment(R.layout.fragment_more), (activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance()) } + override fun openConferencesView() { + (activity as? MainActivity)?.pushView(ConferenceFragment.newInstance()) + } + override fun openSchoolAndTeachersView() { (activity as? MainActivity)?.pushView(SchoolAndTeachersFragment.newInstance()) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index 6d80f4181..d083c9819 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -27,6 +27,7 @@ class MorePresenter @Inject constructor( noteRes?.first -> openNoteView() luckyNumberRes?.first -> openLuckyNumberView() mobileDevicesRes?.first -> openMobileDevicesView() + conferencesRes?.first -> openConferencesView() schoolAndTeachersRes?.first -> openSchoolAndTeachersView() settingsRes?.first -> openSettingsView() aboutRes?.first -> openAboutView() @@ -48,6 +49,7 @@ class MorePresenter @Inject constructor( noteRes, luckyNumberRes, mobileDevicesRes, + conferencesRes, schoolAndTeachersRes, settingsRes, aboutRes diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt index 922afdfd5..bb1faeda4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreView.kt @@ -15,6 +15,8 @@ interface MoreView : BaseView { val mobileDevicesRes: Pair? + val conferencesRes: Pair? + val schoolAndTeachersRes: Pair? val settingsRes: Pair? @@ -41,5 +43,7 @@ interface MoreView : BaseView { fun openMobileDevicesView() + fun openConferencesView() + fun openSchoolAndTeachersView() } diff --git a/app/src/main/res/drawable/ic_more_conferences.xml b/app/src/main/res/drawable/ic_more_conferences.xml new file mode 100644 index 000000000..87be8e055 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_conferences.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_conference.xml b/app/src/main/res/layout/fragment_conference.xml new file mode 100644 index 000000000..d7cac8786 --- /dev/null +++ b/app/src/main/res/layout/fragment_conference.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_conference.xml b/app/src/main/res/layout/item_conference.xml new file mode 100644 index 000000000..4c881f344 --- /dev/null +++ b/app/src/main/res/layout/item_conference.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8e849573d..cea6a83d3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -325,6 +325,11 @@ No subject + + Conferences + No info about conferences + + Add account Logout From 83d1d860a61a3efbf256e9f7e7224bcc07479463 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 15:55:36 +0000 Subject: [PATCH 012/110] Bump coil from 1.0.0-rc3 to 1.0.0 (#1006) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 541aff36c..dd71b0db7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,7 +194,7 @@ dependencies { implementation "fr.bipi.treessence:treessence:0.3.2" implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'com.wdullaer:materialdatetimepicker:4.2.3' - implementation "io.coil-kt:coil:1.0.0-rc3" + implementation "io.coil-kt:coil:1.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.3.1' From 6af8263952b580c28af72134f4b2ca409e362a1b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 15:55:51 +0000 Subject: [PATCH 013/110] Bump firebase-inappmessaging-ktx from 19.1.1 to 19.1.2 (#1010) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index dd71b0db7..c2a4ebcf4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -200,7 +200,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics:17.6.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1' - playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1" + playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.2" playImplementation 'com.google.firebase:firebase-messaging:20.3.0' playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2' playImplementation 'com.google.android.play:core-ktx:1.8.1' From 7c9e85793b19a4e1d003445226dd27aba295eeca Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 16:15:17 +0000 Subject: [PATCH 014/110] Bump firebase-inappmessaging-display-ktx from 19.1.1 to 19.1.2 (#1007) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c2a4ebcf4..5e351315f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -199,7 +199,7 @@ dependencies { implementation 'me.xdrop:fuzzywuzzy:1.3.1' playImplementation 'com.google.firebase:firebase-analytics:17.6.0' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1' + playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.2' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.2" playImplementation 'com.google.firebase:firebase-messaging:20.3.0' playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2' From 9ba999feb05e30cd7568c7564ec728c95a965401 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 16:28:32 +0000 Subject: [PATCH 015/110] Bump about_libraries from 8.4.2 to 8.4.3 (#1011) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index daec68063..051fce64f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.4.10' - about_libraries = '8.4.2' + about_libraries = '8.4.3' hilt_version = "2.29.1-alpha" } repositories { From b3109aed0ba98fc24b75b17cdd13a94f426de960 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 16:40:39 +0000 Subject: [PATCH 016/110] Bump firebase-messaging from 20.3.0 to 21.0.0 (#1008) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5e351315f..c1248afdf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -201,7 +201,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-analytics:17.6.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.2' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.2" - playImplementation 'com.google.firebase:firebase-messaging:20.3.0' + playImplementation 'com.google.firebase:firebase-messaging:21.0.0' playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2' playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' From 20644a7a677063210cf32f1150ccf0d307998faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 1 Nov 2020 19:05:05 +0100 Subject: [PATCH 017/110] Update english strings (#1014) --- .../main/res/values/preferences_values.xml | 2 +- app/src/main/res/values/strings.xml | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values/preferences_values.xml b/app/src/main/res/values/preferences_values.xml index d994213c8..5f2aec4ce 100644 --- a/app/src/main/res/values/preferences_values.xml +++ b/app/src/main/res/values/preferences_values.xml @@ -78,7 +78,7 @@ - Alphabetic + Alphabetically By date diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cea6a83d3..cc3dbe3c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,12 +26,12 @@ Sign in with the student or parent account - Enter the symbol + Enter the symbol from the register page Username Email Login, PESEL or e-mail Password - UONET+ Register + UONET+ register variant Mobile API Scraper Hybrid @@ -41,17 +41,17 @@ Symbol Sign in Password too short - Login details are incorrect. Make sure the correct UONET+ register is selected + Login details are incorrect. Make sure the correct UONET+ register variation is selected in the field below Invalid PIN Invalid token Token expired Invalid email - Invalid login + Use the assigned login instead of email Invalid symbol - Student not found. Check the symbol + Student not found. Validate the symbol and the chosen variation of the UONET+ register This field is required Selected student is already logged in - The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen. Wulkanowy does not detect pre-school students at the moment Select students to log in to the application Other options In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices @@ -65,7 +65,7 @@ Describe details of problem: Zgłoszenie: Problemy z logowaniem Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nOpis problemu: - Make sure the correct UONET+ register is selected! + Make sure you select the correct UONET+ register variation! I forgot my password Recover your account Recover @@ -486,12 +486,12 @@ No internet connection - Connection to the register timed out - Login failed. Try again - Password change required + Connection to register failed. Servers can be overloaded. Please try again later + Loading data failed. Please try again later + Register password change required Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later - Unknown application error + Unknown application error. Please try again later An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API From 57e760844f3aba5a6528e6ea0e4ded7ba8ba5163 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 18:15:52 +0000 Subject: [PATCH 018/110] Bump kotlinx-coroutines-test from 1.3.9 to 1.4.0 (#1012) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c1248afdf..c4bb798ce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,7 +216,7 @@ dependencies { testImplementation "junit:junit:4.13.1" testImplementation "io.mockk:mockk:$mockk" - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.0' androidTestImplementation "androidx.test:core:1.3.0" androidTestImplementation "androidx.test:runner:1.3.0" From 9eb091fbf4cdb0b1d00445ffa0c8914d1d1ae2ee Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 18:16:21 +0000 Subject: [PATCH 019/110] Bump kotlinx-coroutines-android from 1.3.9 to 1.4.0 (#1005) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c4bb798ce..d17275512 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -147,7 +147,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0' implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.activity:activity-ktx:1.1.0" From fe191bb0df19c1fe2f28b64efcf8bbf68cedc7e8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2020 18:36:55 +0000 Subject: [PATCH 020/110] Bump firebase-analytics from 17.6.0 to 18.0.0 (#1009) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d17275512..ddb63d47d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,7 +198,7 @@ dependencies { implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.3.1' - playImplementation 'com.google.firebase:firebase-analytics:17.6.0' + playImplementation 'com.google.firebase:firebase-analytics:18.0.0' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.2' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.2" playImplementation 'com.google.firebase:firebase-messaging:21.0.0' From ada5854d10bac7d6e9490e149182b31197d04c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 2 Nov 2020 17:54:02 +0100 Subject: [PATCH 021/110] New Crowdin updates (#1013) --- app/src/main/res/values-cs-rCZ/strings.xml | 25 ++-- .../main/res/values-de/preferences_values.xml | 4 +- app/src/main/res/values-de/strings.xml | 69 ++++----- app/src/main/res/values-pl/strings.xml | 9 +- app/src/main/res/values-ru/strings.xml | 35 +++-- .../main/res/values-uk/preferences_values.xml | 8 +- app/src/main/res/values-uk/strings.xml | 137 +++++++++--------- 7 files changed, 151 insertions(+), 136 deletions(-) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 67babcd53..dd474aec0 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -22,12 +22,12 @@ Semestr %1$d, %2$d/%3$d Přihlaste se pomocí studentského nebo nadřazeného účtu - Zadejte symbol + Zadejte symbol ze stránky deníku Uživatelské jméno Email Přihlášení, číslo PESEL nebo e-mail Heslo - Deník UONET+ + Variace deníku UONET+ Mobile API Scraper Hybridní @@ -37,17 +37,17 @@ Symbol Přihlásit Toto heslo je příliš krátké - Přihlašovací údaje jsou nesprávné. Zkontrolujte, zda je v poli níže vybrán správný deník UONET+ + Přihlašovací údaje jsou nesprávné. Ujistěte se, že je v poli níže vybrána správná variace deníku UONET+ Neplatný PIN Neplatný token Platnost tokenu vypršela Nesprávná e-mailová adresa - Neplatné přihlášení + Místo e-mailu použijte přiřazené přihlašovací údaje Neplatný symbol - Student nebyl nalezen. Zkontrolujte symbol + Student nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Toto pole je povinné Vybraný student je již přihlášen - Symbol najdete na stránce deníku v Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Symbol najdete na stránce deníku v  Uczeń →  Dostęp Mobilny →  Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+. Wulkanowy v tuto chvíli nezjistí předškolní studenty Vyberte studenty k přihlášení do aplikace Jiné možnosti V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, absolvované lekce, informace o škole a prohlížení seznamu registrovaných zařízení @@ -59,7 +59,7 @@ Discord Poslat e-mail Popište podrobnosti problému: - Ujistěte se, že je vybrán správný UONET+ deník! + Ujistěte se, že jste vybrali správnou variantu deníku UONET+! Zapomněl jsem své heslo Obnovte svůj účet Obnovit @@ -326,6 +326,9 @@ Učitelé Žádné informace o učitelích Žádný předmět + + Setkání + Žádné informace o setkání Přidat účet Odhlásit se @@ -452,12 +455,12 @@ Aktualizace selhala! Wulkanowy nemusí fungovat správně. Zvažte aktualizaci Žádné internetové připojení - Vypršel časový limit připojení k denik - Přihlášení selhalo. Zkus to znovu - Je vyžadována změna hesla + Nelze se připojit ke deníku. Servery mohou být přetíženy. Prosím zkuste to znovu později + Načítání dat se nezdařilo. Prosím zkuste to znovu později + Je vyžadována změna hesla pro deník Probíhá údržba UONET+ deník. Zkuste to později znovu Neznámá chyba denika UONET+. Prosím zkuste to znovu později - Neznámá chyba aplikace + Neznámá chyba aplikace. Prosím zkuste to znovu později Došlo k neočekávané chybě Funkce deaktivována vaší školou Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API diff --git a/app/src/main/res/values-de/preferences_values.xml b/app/src/main/res/values-de/preferences_values.xml index 2ee70f622..7226ff2df 100644 --- a/app/src/main/res/values-de/preferences_values.xml +++ b/app/src/main/res/values-de/preferences_values.xml @@ -31,8 +31,8 @@ 0,75 - Alphabetic - By date + Alphabetisch + Nach Datum Dzienniczek+ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 89af346c3..d885261cd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -22,12 +22,12 @@ Semester %1$d, %2$d/%3$d Melden Sie sich mit dem Studenten- oder Elternkonto an - Geben Sie das Symbol + Geben Sie das Symbol von der Registerseite ein Benutzername Email Anmeldung, PESEL oder e-mail Passwort - UONET+ Klassenbuch + UONET+ Registervariante Mobile API Scraper Hybride @@ -37,17 +37,17 @@ Symbol Anmelden Passwort ist zu kurz - Anmeldedaten sind falsch. Stellen Sie sicher, dass das richtige UONET+-Register ausgewählt ist + Anmeldedaten sind falsch. Stellen Sie sicher, dass die richtige UONET+ Registervariation im unteren Feld ausgewählt ist Ungültige PIN Ungültige token Token ist nicht mehr gültig Ungültige email - Ungültige login + Den zugewiesenen Login anstelle von email verwenden Ungültige symbol - Student nicht gefunden. Überprüfen Sie das Symbol + Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers Dieses Datenfeld ist erforderlich Ausgewählter Student ist bereits angemeldet. - Das Symbol finden Sie auf der Registerseite unter Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Das Symbol kann auf der Registerseite in Uczeń→ Dostęp Mobilny → Zarejestruj urządzenie mobilnegefunden werden.\n\nStellen Sie sicher, dass Sie die entsprechende Registervariante im Feld UONET+ Registervariante auf dem vorherigen Bildschirm festgelegt haben. Wulkanowy erkennt zur Zeit keine Vorschulstudenten Wählen Sie die Studenten aus, die sich bei der Anwendung anmelden sollen. Andere Optionen In diesem Modus funktioniert eine Glücknummer, eine Klassenstatistik, eine Zusammenfassung der Anwesenheit, eine Entschuldigung für die Abwesenheit, abgeschlossene Lektionen, Schulinformationen und eine Vorschau der Liste der registrierten Geräte nicht @@ -59,7 +59,7 @@ Discord email senden Beschreiben Sie die Details des Problems: - Stellen Sie sicher, dass das richtige UONET + Klassenbuch ausgewählt ist! + Stellen Sie sicher, dass Sie die richtige UONET+ Registervariation wählen! Ich habe mein Passwort vergessen. Ihr Konto wiederherstellen Wiederherstellen @@ -154,8 +154,8 @@ Entschuldigte Verspätung Unentschuldigte Verspätung Anwesend - Deleted - Unknown + Entfernt + Unbekannt Lektion Nummer Keine Einträgen @@ -226,29 +226,29 @@ - %d praise - %d praises + %d Lob + %d Lob - New praise - New praises + Neues Lob + Neues Lob - You received %1$d praise - You received %1$d praises + Du hast %1$d Lob erhalten + Du hast %1$d Lob erhalten - %d neutral note - %d neutral notes + %d neutrale Notiz + %d neutrale Notizen - New neutral note - New neutral notes + Neue neutrale Notiz + Neue neutrale Notizen - You received %1$d neutral note - You received %1$d neutral notes + Du hast %1$d neutrale Notiz erhalten + Du hast %1$d neutrale Notizen erhalten Keine Informationen über Hausaufgaben @@ -286,6 +286,9 @@ Lehrerinnen und Lehrer Keine Informationen über Lehrer Kein Thema + + Sitzungen + Keine Informationen über Sitzungen Konto hinzufügen Abmelden @@ -355,12 +358,12 @@ Thema der Anwendung Noten erweitern Aktuelle Lektion im Stundenplan markieren - Show groups next to subjects in timetable + Zeige Gruppen neben Themen im Zeitplan Liste der Diagramme in Klassenbewertungen anzeigen Unterricht der ganzen Klasse anzeigen - Show subjects without grades in Grades + Zeigen Sie Themen ohne Noten in Noten Farbschema der Noten - Subjects sorting in \"Grades\" + Themen sortieren in \"Noten\" App Sprache Benachrichtigungen Benachrichtigungen anzeigen @@ -406,18 +409,18 @@ Kopiert lösen - Download of updates has started… - An update has just been downloaded. - Restart - Update failed! Wulkanowy may not function properly. Consider updating + Download der Updates wurde gestartet… + Ein Update wurde gerade heruntergeladen. + Neustart + Update fehlgeschlagen! Wulkanowy funktioniert möglicherweise nicht richtig. Überlegen Sie die Aktualisierung Keine Internetverbindung - Das Zeitlimit für die Verbindung zum Klassenbuch ist abgelaufen - Login failed. Try again - Passwortänderung erforderlich + Registrierungsverbindung fehlgeschlagen. Server können überlastet sein. Bitte versuchen Sie es später noch einmal + Das Laden der Daten ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal + Passwortänderung für Registrierung erforderlich Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal - Unknown UONET + register error. Try again later - Unknown application error + Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut + Unbekannter Anwendungsfehler. Bitte versuchen Sie es später noch einmal Ein unerwarteter Fehler ist aufgetreten Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 99fd05dbd..67f8bd1be 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -168,7 +168,7 @@ Spóźnienie usprawiedliwione Spóźnienie nieusprawiedliwione Obecność - Usunięty + Usunięto Nieznany Numer lekcji Brak wpisów @@ -326,6 +326,9 @@ Nauczyciele Brak informacji o nauczycielach Brak przedmiotu + + Zebrania + Brak informacji o zebraniach Dodaj konto Wyloguj @@ -398,9 +401,9 @@ Pokazuj grupę obok przedmiotu na planie Pokazuj listę wykresów w ocenach klasy Pokazuj lekcje całej klasy - Pokazuj przedmioty bez ocen w Oceny + Pokazuj przedmioty bez ocen w Ocenach Schemat kolorów ocen - Sortowanie przedmiotów w \"Oceny\" + Sortowanie przedmiotów w Ocenach Język aplikacji Powiadomienia Pokazuj powiadomienia diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7a10553dc..5e260d999 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -22,12 +22,12 @@ %1$d семестр, %2$d/%3$d Авторизируйтесь при помощи аккаунта ученика или родителя - Впишите \"symbol\" + Введите символ со страницы регистрации Имя пользователя Электронная почта Логин, PESEL или электронная почта Пароль - Дневник UONET+ + Разновидностью бревна UONET+ Mobile API Scraper Hybrid @@ -37,17 +37,17 @@ Symbol Войти Слишком короткий пароль - Указаны неверные данные. Убедитесь, что вы выбрали нужный дневник + Данные для входа неверны. Убедитесь, что в поле ниже выбран правильный вариант регистра UONET+ Неправильный PIN Неправильный token Токен просрочен Неверный адрес электронной почты - Неправильный логин + Используйте назначенный логин вместо электронной почты Неправильный symbol - Не удалось найти ученика. Проверьте \"symbol\" + Студент не найден. Подтвердите символ и выбранный вариант регистра UONET+ Обязательное поле Данный ученик уже авторизован - Вы можете найти \"symbol\" на странице VULCAN по пути Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Этот символ можно найти на странице регистрации в  Uczeń →  Dostęp Mobilny →  Zarejestruj urządzenie mobilne.\n\nУбедитесь, что вы установили соответствующий вариант регистра в поле Разновидностью бревна UONET+ на предыдущем экране. Wulkanowy на данный момент не обнаруживает дошкольников Выберите учеников для авторизации в приложении Другие варианты В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств @@ -59,7 +59,7 @@ Discord Отправить письмо Опишите детали проблемы: - Убедитесь, что выбран нужный дневник! + Убедитесь, что вы выбрали правильный вариант регистра UONET+! Забыли пароль? Восстановите свой аккаунт Восстановить @@ -326,6 +326,9 @@ Учителя Нет информации о учителях Нет предмета + + Встречи + Нет информации о встречах Добавить аккаунт Выйти @@ -446,18 +449,18 @@ Скопировано Отменить - Download of updates has started… - An update has just been downloaded. - Restart - Update failed! Wulkanowy may not function properly. Consider updating + Загрузка обновлений началась… + Только что было скачано обновление. + Перезапустить + Не удалось обновить! Wulkanowy может работать некорректно. Рассмотрите возможность обновления Нет интернет-подключения - Слишком долгое ожидание соединения с дневником - Login failed. Try again - Требуется смена пароля + Не удалось подключиться к регистрации. Серверы могут быть перегружены. Пожалуйста, повторите попытку позже + Не удалось загрузить данные. Пожалуйста, повторите попытку позже + Необходимо изменить пароль реестра Технический перерыв в журнале UONET + продолжается. Попробуйте позже - Unknown UONET + register error. Try again later - Unknown application error + Неизвестная ошибка UONET + регистр. Попробуйте позже + Неизвестная ошибка приложения. Пожалуйста, повторите попытку позже Произошла неожиданная ошибка Функция была выключена школой Функция не доступна в этом режиме diff --git a/app/src/main/res/values-uk/preferences_values.xml b/app/src/main/res/values-uk/preferences_values.xml index 0ff8bfd0c..02b6ccf85 100644 --- a/app/src/main/res/values-uk/preferences_values.xml +++ b/app/src/main/res/values-uk/preferences_values.xml @@ -31,8 +31,8 @@ 0,75 - Alphabetic - By date + За алфавітом + За датою Dzienniczek+ @@ -41,8 +41,8 @@ Середня оцінка з 2 семестру - Average of grades from both semesters - Average of grades from the whole year + Середнє оцінювання за обидва семестри + Середнє оцінювання за весь рік Не показувати diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 4a15db9f5..48da2784f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -22,12 +22,12 @@ %1$d семестр, %2$d/%3$d Авторизуйтеся за допомогою аккаунта учня або батька - Впишіть \"symbol\" + Введіть символ зі сторінки реєстру Ім\'я користувача Електронна пошта Логін, PESEL або електронна пошта Пароль - Щоденник + UONET + варіант реєстрації Мobile API Scraper Hybrid @@ -37,17 +37,17 @@ Symbol Увійти Занадто короткий пароль - Вказані невірні дані. Впевніться, що ви увібрали потрібний щоденник + Дані для входу неправильні. Переконайтеся, що у полі нижче вказано правильний варіант реєстрації UONET+ Неправильний PIN Неправильний token Минув термін дії токену Недійсна адреса електронної пошти - Неправильний логін + Використовуйте призначений логін замість електронної пошти Неправильний symbol - Не вдалося знайти учня. Будь ласка, перевірте \"symbol\" + Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+ Обов\'язкове поле Даного учня вже авторизовано - Ви можете знайти \"symbol\" на сторінцi VULCAN стежкою Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne + Символ можна знайти на сторінці реєстру в   Uczeń →   Dostęp Mobilny →   Zarejestruj urządzenie mobilne .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів Виберіть учнів для авторизації в додатку Інші варіанти У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності і уроків, інформація про школу і список зареєстрованних пристроїв @@ -59,7 +59,7 @@ Discord Відправити лист Опишіть деталі помилки: - Переконайтесь, що обрано потрібний щоденник! + Переконайтеся, що ви вибрали правильний варіант реєстрації UONET+! Забули пароль? Відновіть свій аккаунт Відновити @@ -108,16 +108,16 @@ Нові оцінки - New predicted grade - New predicted grades - New predicted grades - New predicted grades + Нова прогнозована оцінка + Нові прогнозовані оцінки + Нові прогнозовані оцінки + Нові прогнозовані оцінки - New final grade - New final grades - New final grades - New final grades + Нова підсумкова оцінка + Нові підсумкові оцінки + Нові підсумкові оцінки + Нові підсумкові оцінки Ви отримали %1$d оцінку @@ -126,16 +126,16 @@ Ви отримали %1$d оцінок - You received %1$d predicted grade - You received %1$d predicted grades - You received %1$d predicted grades - You received %1$d predicted grades + Ви отримали %1$d нову прогнозовану оцінку + Ви отримали %1$d нові прогнозовані оцінки + Ви отримали %1$d нових прогнозованих оцінок + Ви отримали %1$d нових прогнозованих оцінок - You received %1$d final grade - You received %1$d final grades - You received %1$d final grades - You received %1$d final grades + Ви отримали %1$d нову підсумкову оцінку + Ви отримали %1$d нові підсумкові оцінки + Ви отримали %1$d нових підсумкових оцінок + Ви отримали %1$d нових підсумкових оцінок Урок @@ -168,8 +168,8 @@ Спізнення з поважних причин Спізнення з не поважних причин Присутність - Deleted - Unknown + Вилучено + Невідомо Номер уроку Брак записів @@ -206,8 +206,8 @@ Перемістити у кошик Видалити назавжди Повідомлення було успішно видалено - Share - Print + Поділіться + Друк Тема Зміст Повідомлення було успішно відправлено @@ -254,41 +254,41 @@ - %d praise - %d praises - %d praises - %d praises + %d похвалили + %d похвали + %d похвал + %d похвал - New praise - New praises - New praises - New praises + Нова похвала + Нові похвали + Нові похвали + Нові похвали - You received %1$d praise - You received %1$d praises - You received %1$d praises - You received %1$d praises + Ви отримали %1$d нову похвалу + Ви отримали %1$d нові похвали + Ви отримали %1$d нових похвал + Ви отримали %1$d нових похвал - %d neutral note - %d neutral notes - %d neutral notes - %d neutral notes + %d нейтральна нота + %d нейтральні ноти + %d нейтральних нот + %d нейтральних нот - New neutral note - New neutral notes - New neutral notes - New neutral notes + Нова нейтральна нота + Нова нейтральна нота + Нова нейтральна нота + Нові нейтральні ноти - You received %1$d neutral note - You received %1$d neutral notes - You received %1$d neutral notes - You received %1$d neutral notes + Ви отримали %1$d нову нейтральну ноту + Ви отримали %1$d нові нейтральні ноти + Ви отримали %1$d нових нейтральних нот + Ви отримали %1$d нових нейтральних нот Брак домашніх завдань @@ -326,15 +326,18 @@ Вчителі Брак інформації про вчителів Брак предмету + + Зустрічі + Немає інформації про зустрічі Додати аккаунт Вийти Ви впевнені, що хочете вийти з цього аккаунту? Вийти з аккаунту учня - Student account - Parent account - Mobile API mode - Hybrid mode + Студентський рахунок + Головний рахунок + Режим мобільного API + Гібридний режим Версія додатка Розробники @@ -395,12 +398,12 @@ Тема додатку Більше оцінок Позначити поточний урок у розкладі - Show groups next to subjects in timetable + Покажіть групи поруч із предметами в розкладі Показувати діаграми в оцінках класу Показати уроки всього класу - Show subjects without grades in Grades + Показуйте предмети без оцінок у оцінках Схема кольорів оцінок - Subjects sorting in \"Grades\" + Сортування предметів за \"Оцінками\" Мова додатку Повідомлення Показувати повідомлення @@ -446,18 +449,18 @@ Скопійовано Відмінити - Download of updates has started… - An update has just been downloaded. - Restart - Update failed! Wulkanowy may not function properly. Consider updating + Завантаження оновлень розпочато… + Щойно завантажено оновлення. + Перезапустити + Помилка оновлення! Wulkanowy може не працювати належним чином. Подумайте про оновлення Брак з\'єднання з інтернетом - Занадто довге очікування з\'єднання з щоденником - Login failed. Try again - Потрібно змінити пароль + Помилка підключення до реєстрації. Сервери можуть бути перевантажені. Будь-ласка спробуйте пізніше + Помилка завантаження даних. Будь-ласка спробуйте пізніше + Потрібна реєстрація зміни пароля Технічна перерва в журналі UONET + продовжується. Спробуйте пізніше - Unknown UONET + register error. Try again later - Unknown application error + Невідома помилка реєстру UONET+. Спробуйте ще раз пізніше + Невідома помилка програми. Будь-ласка спробуйте пізніше Відбулася несподівана помилка Функція вимкнена школою Функція не доступна в цьому режимі From a1ebf6c6ad806960504265f9e7095a3ec0c73a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 11 Nov 2020 16:03:52 +0100 Subject: [PATCH 022/110] Add average in class grades statistics (#1017) --- app/build.gradle | 2 +- .../29.json | 1898 +++++++++++++++++ .../GradeStatisticsLocalTest.kt | 30 +- .../github/wulkanowy/utils/CrashLogUtils.kt | 2 +- .../github/wulkanowy/data/RepositoryModule.kt | 8 +- .../github/wulkanowy/data/db/AppDatabase.kt | 19 +- .../data/db/dao/GradePartialStatisticsDao.kt | 13 + .../data/db/dao/GradeSemesterStatisticsDao.kt | 13 + .../data/db/dao/GradeStatisticsDao.kt | 18 - .../db/entities/GradePartialStatistics.kt | 33 + ...atistics.kt => GradeSemesterStatistics.kt} | 15 +- .../data/db/migrations/Migration29.kt | 33 + .../data/pojos/GradeStatisticsItem.kt | 9 +- .../gradestatistics/GradeStatisticsLocal.kt | 52 +- .../gradestatistics/GradeStatisticsRemote.kt | 49 +- .../GradeStatisticsRepository.kt | 116 +- .../sync/works/GradeStatisticsWork.kt | 4 +- .../statistics/GradeStatisticsAdapter.kt | 58 +- .../statistics/GradeStatisticsPresenter.kt | 15 +- .../GradeStatisticsRemoteTest.kt | 28 +- 20 files changed, 2270 insertions(+), 145 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt rename app/src/main/java/io/github/wulkanowy/data/db/entities/{GradeStatistics.kt => GradeSemesterStatistics.kt} (61%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt diff --git a/app/build.gradle b/app/build.gradle index ddb63d47d..0afaab011 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.22.2" + implementation "io.github.wulkanowy:sdk:bcf6b53" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json new file mode 100644 index 000000000..3e863c578 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/29.json @@ -0,0 +1,1898 @@ +{ + "formatVersion": 1, + "database": { + "version": 29, + "identityHash": "30e4647c7dd84a6ac9b2f9f3ef7d3264", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '30e4647c7dd84a6ac9b2f9f3ef7d3264')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt index 197d2d0ec..db0834325 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt @@ -4,8 +4,8 @@ import androidx.room.Room import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.wulkanowy.data.db.AppDatabase +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.Semester import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -27,7 +27,7 @@ class GradeStatisticsLocalTest { fun createDb() { testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) .build() - gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics) + gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradePartialStatisticsDao, testDb.gradePointsStatisticsDao, testDb.gradeSemesterStatisticsDao) } @After @@ -41,9 +41,9 @@ class GradeStatisticsLocalTest { getGradeStatistics("Matematyka", 2, 1), getGradeStatistics("Fizyka", 1, 2) ) - runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) } + runBlocking { gradeStatisticsLocal.saveGradePartialStatistics(list) } - val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() } + val stats = runBlocking { gradeStatisticsLocal.getGradePartialStatistics(getSemester()).first() } assertEquals(1, stats.size) assertEquals(stats[0].subject, "Matematyka") } @@ -55,12 +55,10 @@ class GradeStatisticsLocalTest { getGradeStatistics("Chemia", 2, 1), getGradeStatistics("Fizyka", 1, 2) ) - runBlocking { gradeStatisticsLocal.saveGradesStatistics(list) } + runBlocking { gradeStatisticsLocal.saveGradePartialStatistics(list) } - val stats = runBlocking { gradeStatisticsLocal.getGradesStatistics(getSemester(), false).first() } + val stats = runBlocking { gradeStatisticsLocal.getGradePartialStatistics(getSemester()).first() } assertEquals(2, stats.size) -// assertEquals(3, stats.size) -// assertEquals(stats[0].subject, "Wszystkie") // now in main repo assertEquals(stats[0].subject, "Matematyka") assertEquals(stats[1].subject, "Chemia") } @@ -72,9 +70,9 @@ class GradeStatisticsLocalTest { getGradePointsStatistics("Chemia", 2, 1), getGradePointsStatistics("Fizyka", 1, 2) ) - runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(list) } + runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(list) } - val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } + val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() } with(stats[0]) { assertEquals(subject, "Matematyka") assertEquals(others, 5.0) @@ -84,17 +82,17 @@ class GradeStatisticsLocalTest { @Test fun saveAndRead_subjectEmpty() { - runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) } + runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(listOf()) } - val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } + val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() } assertEquals(emptyList(), stats) } @Test fun saveAndRead_allEmpty() { - runBlocking { gradeStatisticsLocal.saveGradesPointsStatistics(listOf()) } + runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(listOf()) } - val stats = runBlocking { gradeStatisticsLocal.getGradesPointsStatistics(getSemester()).first() } + val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() } assertEquals(emptyList(), stats) } @@ -102,8 +100,8 @@ class GradeStatisticsLocalTest { return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1) } - private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics { - return GradeStatistics(studentId, semesterId, subject, 5, 5, false) + private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradePartialStatistics { + return GradePartialStatistics(studentId, semesterId, subject, "", "", listOf(5), listOf(5)) } private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics { diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt index 7f4bedae4..8adf2b29c 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -42,7 +42,7 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { connectCrash.setCustomKey("priority", priority) connectCrash.setCustomKey("tag", tag.orEmpty()) connectCrash.setCustomKey("message", message) - connectCrash.log(priority, t?.stackTraceToString()) + if (t != null) { connectCrash.log(priority, t.stackTraceToString()) } else { diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 50642ddd5..5efe77739 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -85,11 +85,15 @@ internal class RepositoryModule { @Singleton @Provides - fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics + fun provideGradePartialStatisticsDao(database: AppDatabase) = database.gradePartialStatisticsDao @Singleton @Provides - fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics + fun provideGradeSemesterStatisticsDao(database: AppDatabase) = database.gradeSemesterStatisticsDao + + @Singleton + @Provides + fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatisticsDao @Singleton @Provides diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 93f225427..57160a2bf 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -13,8 +13,9 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.LuckyNumberDao @@ -36,8 +37,9 @@ import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.LuckyNumber @@ -73,6 +75,7 @@ import io.github.wulkanowy.data.db.migrations.Migration25 import io.github.wulkanowy.data.db.migrations.Migration26 import io.github.wulkanowy.data.db.migrations.Migration27 import io.github.wulkanowy.data.db.migrations.Migration28 +import io.github.wulkanowy.data.db.migrations.Migration29 import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration5 @@ -93,8 +96,9 @@ import javax.inject.Singleton AttendanceSummary::class, Grade::class, GradeSummary::class, - GradeStatistics::class, + GradePartialStatistics::class, GradePointsStatistics::class, + GradeSemesterStatistics::class, Message::class, MessageAttachment::class, Note::class, @@ -116,7 +120,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 28 + const val VERSION_SCHEMA = 29 fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array { return arrayOf( @@ -147,6 +151,7 @@ abstract class AppDatabase : RoomDatabase() { Migration26(), Migration27(), Migration28(), + Migration29() ) } @@ -176,9 +181,11 @@ abstract class AppDatabase : RoomDatabase() { abstract val gradeSummaryDao: GradeSummaryDao - abstract val gradeStatistics: GradeStatisticsDao + abstract val gradePartialStatisticsDao: GradePartialStatisticsDao - abstract val gradePointsStatistics: GradePointsStatisticsDao + abstract val gradePointsStatisticsDao: GradePointsStatisticsDao + + abstract val gradeSemesterStatisticsDao: GradeSemesterStatisticsDao abstract val messagesDao: MessagesDao diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt new file mode 100644 index 000000000..bce6ce572 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradePartialStatisticsDao.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradePartialStatistics +import kotlinx.coroutines.flow.Flow + +@Dao +interface GradePartialStatisticsDao : BaseDao { + + @Query("SELECT * FROM GradePartialStatistics WHERE student_id = :studentId AND semester_id = :semesterId") + fun loadAll(semesterId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt new file mode 100644 index 000000000..09ae81714 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSemesterStatisticsDao.kt @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import kotlinx.coroutines.flow.Flow + +@Dao +interface GradeSemesterStatisticsDao : BaseDao { + + @Query("SELECT * FROM GradeSemesterStatistics WHERE student_id = :studentId AND semester_id = :semesterId") + fun loadAll(semesterId: Int, studentId: Int): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt deleted file mode 100644 index b462ad5db..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeStatisticsDao.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.wulkanowy.data.db.dao - -import androidx.room.Dao -import androidx.room.Query -import io.github.wulkanowy.data.db.entities.GradeStatistics -import kotlinx.coroutines.flow.Flow -import javax.inject.Singleton - -@Singleton -@Dao -interface GradeStatisticsDao : BaseDao { - - @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester") - fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Flow> - - @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND is_semester = :isSemester") - fun loadAll(semesterId: Int, studentId: Int, isSemester: Boolean): Flow> -} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt new file mode 100644 index 000000000..db164afde --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradePartialStatistics.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "GradePartialStatistics") +data class GradePartialStatistics( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "semester_id") + val semesterId: Int, + + val subject: String, + + @ColumnInfo(name = "class_average") + val classAverage: String, + + @ColumnInfo(name = "student_average") + val studentAverage: String, + + @ColumnInfo(name = "class_amounts") + val classAmounts: List, + + @ColumnInfo(name = "student_amounts") + val studentAmounts: List + +) { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeStatistics.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt similarity index 61% rename from app/src/main/java/io/github/wulkanowy/data/db/entities/GradeStatistics.kt rename to app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt index 8ad8b8b8d..e747271ce 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeStatistics.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSemesterStatistics.kt @@ -4,8 +4,8 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -@Entity(tableName = "GradesStatistics") -data class GradeStatistics( +@Entity(tableName = "GradeSemesterStatistics") +data class GradeSemesterStatistics( @ColumnInfo(name = "student_id") val studentId: Int, @@ -15,13 +15,14 @@ data class GradeStatistics( val subject: String, - val grade: Int, + val amounts: List, - val amount: Int, - - @ColumnInfo(name = "is_semester") - val semester: Boolean + @ColumnInfo(name = "student_grade") + val studentGrade: Int ) { @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @Transient + var average: String = "" } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt new file mode 100644 index 000000000..327552d75 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration29.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration29 : Migration(28, 29) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("DROP TABLE IF EXISTS GradesStatistics") + database.execSQL(""" + CREATE TABLE IF NOT EXISTS GradeSemesterStatistics ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + semester_id INTEGER NOT NULL, + subject TEXT NOT NULL, + amounts TEXT NOT NULL, + student_grade INTEGER NOT NULL + ) + """) + database.execSQL(""" + CREATE TABLE IF NOT EXISTS GradePartialStatistics ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + semester_id INTEGER NOT NULL, + subject TEXT NOT NULL, + class_average TEXT NOT NULL, + student_average TEXT NOT NULL, + class_amounts TEXT NOT NULL, + student_amounts TEXT NOT NULL + ) + """) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt index 34b62abdb..88257470d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/GradeStatisticsItem.kt @@ -1,14 +1,19 @@ package io.github.wulkanowy.data.pojos +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.ui.modules.grade.statistics.ViewType data class GradeStatisticsItem( val type: ViewType, - val partial: List, + val average: String, + + val partial: GradePartialStatistics?, + + val semester: GradeSemesterStatistics?, val points: GradePointsStatistics? ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt index e0e2cd4db..3e3c819f7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt @@ -1,9 +1,11 @@ package io.github.wulkanowy.data.repositories.gradestatistics +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.Semester import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -11,31 +13,47 @@ import javax.inject.Singleton @Singleton class GradeStatisticsLocal @Inject constructor( - private val gradeStatisticsDb: GradeStatisticsDao, - private val gradePointsStatisticsDb: GradePointsStatisticsDao + private val gradePartialStatisticsDb: GradePartialStatisticsDao, + private val gradePointsStatisticsDb: GradePointsStatisticsDao, + private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao ) { - fun getGradesStatistics(semester: Semester, isSemester: Boolean): Flow> { - return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester) + // partial + fun getGradePartialStatistics(semester: Semester): Flow> { + return gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) } - fun getGradesPointsStatistics(semester: Semester): Flow> { + suspend fun saveGradePartialStatistics(items: List) { + gradePartialStatisticsDb.insertAll(items) + } + + suspend fun deleteGradePartialStatistics(items: List) { + gradePartialStatisticsDb.deleteAll(items) + } + + // points + fun getGradePointsStatistics(semester: Semester): Flow> { return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) } - suspend fun saveGradesStatistics(gradesStatistics: List) { - gradeStatisticsDb.insertAll(gradesStatistics) - } - - suspend fun saveGradesPointsStatistics(gradePointsStatistics: List) { + suspend fun saveGradePointsStatistics(gradePointsStatistics: List) { gradePointsStatisticsDb.insertAll(gradePointsStatistics) } - suspend fun deleteGradesStatistics(gradesStatistics: List) { - gradeStatisticsDb.deleteAll(gradesStatistics) - } - - suspend fun deleteGradesPointsStatistics(gradesPointsStatistics: List) { + suspend fun deleteGradePointsStatistics(gradesPointsStatistics: List) { gradePointsStatisticsDb.deleteAll(gradesPointsStatistics) } + + // semester + fun getGradeSemesterStatistics(semester: Semester): Flow> { + return gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) + } + + suspend fun saveGradeSemesterStatistics(items: List) { + gradeSemesterStatisticsDb.insertAll(items) + } + + suspend fun deleteGradeSemesterStatistics(items: List) { + gradeSemesterStatisticsDb.deleteAll(items) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt index 1ff8132fc..144df8a06 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt @@ -1,7 +1,8 @@ package io.github.wulkanowy.data.repositories.gradestatistics +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.sdk.Sdk @@ -12,20 +13,38 @@ import javax.inject.Singleton @Singleton class GradeStatisticsRemote @Inject constructor(private val sdk: Sdk) { - suspend fun getGradeStatistics(student: Student, semester: Semester, isSemester: Boolean): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).let { - if (isSemester) it.getGradesAnnualStatistics(semester.semesterId) - else it.getGradesPartialStatistics(semester.semesterId) - }.map { - GradeStatistics( - semesterId = semester.semesterId, - studentId = semester.studentId, - subject = it.subject, - grade = it.gradeValue, - amount = it.amount, - semester = isSemester - ) - } + suspend fun getGradePartialStatistics(student: Student, semester: Semester): List { + return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getGradesPartialStatistics(semester.semesterId) + .map { + GradePartialStatistics( + semesterId = semester.semesterId, + studentId = student.studentId, + subject = it.subject, + classAverage = it.classAverage, + studentAverage = it.studentAverage, + classAmounts = it.classItems + .sortedBy { item -> item.grade } + .map { item -> item.amount }, + studentAmounts = it.studentItems.map { item -> item.amount } + ) + } + } + + suspend fun getGradeSemesterStatistics(student: Student, semester: Semester): List { + return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getGradesSemesterStatistics(semester.semesterId) + .map { + GradeSemesterStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + amounts = it.items + .sortedBy { item -> item.grade } + .map { item -> item.amount }, + studentGrade = it.items.singleOrNull { item -> item.isStudentHere }?.grade ?: 0 + ) + } } suspend fun getGradePointsStatistics(student: Student, semester: Semester): List { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt index 52ca705f7..ca0f6ffb9 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt @@ -1,13 +1,15 @@ package io.github.wulkanowy.data.repositories.gradestatistics +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.ui.modules.grade.statistics.ViewType import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract +import java.util.Locale import javax.inject.Inject import javax.inject.Singleton @@ -17,55 +19,125 @@ class GradeStatisticsRepository @Inject constructor( private val remote: GradeStatisticsRemote ) { - fun getGradesStatistics(student: Student, semester: Semester, subjectName: String, isSemester: Boolean, forceRefresh: Boolean) = networkBoundResource( + fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getGradesStatistics(semester, isSemester) }, - fetch = { remote.getGradeStatistics(student, semester, isSemester) }, + query = { local.getGradePartialStatistics(semester) }, + fetch = { remote.getGradePartialStatistics(student, semester) }, saveFetchResult = { old, new -> - local.deleteGradesStatistics(old uniqueSubtract new) - local.saveGradesStatistics(new uniqueSubtract old) + local.deleteGradePartialStatistics(old uniqueSubtract new) + local.saveGradePartialStatistics(new uniqueSubtract old) }, mapResult = { items -> when (subjectName) { - "Wszystkie" -> items.groupBy { it.grade }.map { - GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, - it.value.fold(0) { acc, e -> acc + e.amount }, false) - } + items + "Wszystkie" -> { + val numerator = items.map { + it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0 + }.filterNot { it == .0 } + (items.reversed() + GradePartialStatistics( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = subjectName, + classAverage = if (numerator.isEmpty()) "" else numerator.average().let { + "%.2f".format(Locale.FRANCE, it) + }, + studentAverage = "", + classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), + studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() + )).reversed() + } else -> items.filter { it.subject == subjectName } - }.mapToStatisticItems() + }.mapPartialToStatisticItems() + } + ) + + fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { local.getGradeSemesterStatistics(semester) }, + fetch = { remote.getGradeSemesterStatistics(student, semester) }, + saveFetchResult = { old, new -> + local.deleteGradeSemesterStatistics(old uniqueSubtract new) + local.saveGradeSemesterStatistics(new uniqueSubtract old) + }, + mapResult = { items -> + val itemsWithAverage = items.map { item -> + item.copy().apply { + val denominator = item.amounts.sum() + average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount -> + (gradeValue + 1) * amount + }.sum().toDouble() / denominator).let { + "%.2f".format(Locale.FRANCE, it) + } + } + } + when (subjectName) { + "Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = subjectName, + amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), + studentGrade = 0 + ).apply { + average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let { + "%.2f".format(Locale.FRANCE, it) + } + }).reversed() + else -> itemsWithAverage.filter { it.subject == subjectName } + }.mapSemesterToStatisticItems() } ) fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getGradesPointsStatistics(semester) }, + query = { local.getGradePointsStatistics(semester) }, fetch = { remote.getGradePointsStatistics(student, semester) }, saveFetchResult = { old, new -> - local.deleteGradesPointsStatistics(old uniqueSubtract new) - local.saveGradesPointsStatistics(new uniqueSubtract old) + local.deleteGradePointsStatistics(old uniqueSubtract new) + local.saveGradePointsStatistics(new uniqueSubtract old) }, mapResult = { items -> when (subjectName) { "Wszystkie" -> items else -> items.filter { it.subject == subjectName } - }.mapToStatisticsItem() + }.mapPointsToStatisticsItems() } ) - private fun List.mapToStatisticItems() = groupBy { it.subject }.map { + private fun List>.sumGradeAmounts(): List { + val result = mutableListOf(0, 0, 0, 0, 0, 0) + forEach { + it.forEachIndexed { grade, amount -> + result[grade] += amount + } + } + return result + } + + private fun List.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map { GradeStatisticsItem( type = ViewType.PARTIAL, - partial = it.value - .sortedByDescending { item -> item.grade } - .filter { item -> item.amount != 0 }, - points = null + average = it.classAverage, + partial = it, + points = null, + semester = null ) } - private fun List.mapToStatisticsItem() = map { + private fun List.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map { + GradeStatisticsItem( + type = ViewType.SEMESTER, + partial = null, + points = null, + average = "", + semester = it + ) + } + + private fun List.mapPointsToStatisticsItems() = map { GradeStatisticsItem( type = ViewType.POINTS, - partial = emptyList(), + partial = null, + semester = null, + average = "", points = it ) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 3d61a423b..461836b8d 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -12,8 +12,8 @@ class GradeStatisticsWork @Inject constructor( override suspend fun doWork(student: Student, semester: Semester) { with(gradeStatisticsRepository) { - getGradesStatistics(student, semester, "Wszystkie", isSemester = true, forceRefresh = true).waitForResult() - getGradesStatistics(student, semester, "Wszystkie", isSemester = false, forceRefresh = true).waitForResult() + getGradesPartialStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() + getGradesSemesterStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() getGradesPointsStatistics(student, semester, "Wszystkie", forceRefresh = true).waitForResult() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt index dbb60910a..cbcb444a1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsAdapter.kt @@ -17,8 +17,9 @@ import com.github.mikephil.charting.data.PieDataSet import com.github.mikephil.charting.data.PieEntry import com.github.mikephil.charting.formatter.ValueFormatter import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding @@ -68,22 +69,32 @@ class GradeStatisticsAdapter @Inject constructor() : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { - ViewType.PARTIAL.id, ViewType.SEMESTER.id -> PieViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)) - ViewType.POINTS.id -> BarViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false)) + ViewType.PARTIAL.id -> PartialViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)) + ViewType.SEMESTER.id -> SemesterViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false)) + ViewType.POINTS.id -> PointsViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false)) else -> throw IllegalStateException() } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { - is PieViewHolder -> bindPieChart(holder, items[position].partial) - is BarViewHolder -> bindBarChart(holder, items[position].points!!) + is PartialViewHolder -> bindPartialChart(holder, items[position].partial!!) + is SemesterViewHolder -> bindSemesterChart(holder, items[position].semester!!) + is PointsViewHolder -> bindBarChart(holder, items[position].points!!) } } - private fun bindPieChart(holder: PieViewHolder, partials: List) { - with(holder.binding.gradeStatisticsPieTitle) { - text = partials.firstOrNull()?.subject + private fun bindPartialChart(holder: PartialViewHolder, partials: GradePartialStatistics) { + bindPieChart(holder.binding, partials.subject, partials.classAverage, partials.classAmounts) + } + + private fun bindSemesterChart(holder: SemesterViewHolder, semester: GradeSemesterStatistics) { + bindPieChart(holder.binding, semester.subject, semester.average, semester.amounts) + } + + private fun bindPieChart(binding: ItemGradeStatisticsPieBinding, subject: String, average: String, amounts: List) { + with(binding.gradeStatisticsPieTitle) { + text = subject visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE } @@ -92,22 +103,23 @@ class GradeStatisticsAdapter @Inject constructor() : else -> materialGradeColors } - val dataset = PieDataSet(partials.map { - PieEntry(it.amount.toFloat(), it.grade.toString()) - }, "Legenda") + val dataset = PieDataSet(amounts.mapIndexed { grade, amount -> + PieEntry(amount.toFloat(), (grade + 1).toString()) + }.reversed().filterNot { it.value == 0f }, "Legenda") with(dataset) { valueTextSize = 12f sliceSpace = 1f valueTextColor = Color.WHITE - setColors(partials.map { - gradeColors.single { color -> color.first == it.grade }.second - }.toIntArray(), holder.binding.root.context) + val grades = amounts.mapIndexed { grade, amount -> (grade + 1) to amount }.filterNot { it.second == 0 } + setColors(grades.reversed().map { (grade, _) -> + gradeColors.single { color -> color.first == grade }.second + }.toIntArray(), binding.root.context) } - with(holder.binding.gradeStatisticsPie) { + with(binding.gradeStatisticsPie) { setTouchEnabled(false) - if (partials.size == 1) animateXY(1000, 1000) + if (amounts.size == 1) animateXY(1000, 1000) data = PieData(dataset).apply { setValueFormatter(object : ValueFormatter() { override fun getPieLabel(value: Float, pieEntry: PieEntry): String { @@ -128,8 +140,9 @@ class GradeStatisticsAdapter @Inject constructor() : minAngleForSlices = 25f description.isEnabled = false - centerText = partials.fold(0) { acc, it -> acc + it.amount } - .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } + centerText = amounts.fold(0) { acc, it -> acc + it } + .let { resources.getQuantityString(R.plurals.grade_number_item, it, it) } + + ("\n\nŚrednia: $average").takeIf { average.isNotBlank() }.orEmpty() setHoleColor(context.getThemeAttrColor(android.R.attr.windowBackground)) setCenterTextColor(context.getThemeAttrColor(android.R.attr.textColorPrimary)) @@ -137,7 +150,7 @@ class GradeStatisticsAdapter @Inject constructor() : } } - private fun bindBarChart(holder: BarViewHolder, points: GradePointsStatistics) { + private fun bindBarChart(holder: PointsViewHolder, points: GradePointsStatistics) { with(holder.binding.gradeStatisticsBarTitle) { text = points.subject visibility = if (items.size == 1) GONE else VISIBLE @@ -200,9 +213,12 @@ class GradeStatisticsAdapter @Inject constructor() : } } - private class PieViewHolder(val binding: ItemGradeStatisticsPieBinding) : + private class PartialViewHolder(val binding: ItemGradeStatisticsPieBinding) : RecyclerView.ViewHolder(binding.root) - private class BarViewHolder(val binding: ItemGradeStatisticsBarBinding) : + private class SemesterViewHolder(val binding: ItemGradeStatisticsPieBinding) : + RecyclerView.ViewHolder(binding.root) + + private class PointsViewHolder(val binding: ItemGradeStatisticsBarBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index 2f46d21db..aca3ec965 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -153,8 +153,8 @@ class GradeStatisticsPresenter @Inject constructor( with(gradeStatisticsRepository) { when (type) { - ViewType.SEMESTER -> getGradesStatistics(student, semester, currentSubjectName, true, forceRefresh) - ViewType.PARTIAL -> getGradesStatistics(student, semester, currentSubjectName, false, forceRefresh) + ViewType.PARTIAL -> getGradesPartialStatistics(student, semester, currentSubjectName, forceRefresh) + ViewType.SEMESTER -> getGradesSemesterStatistics(student, semester, currentSubjectName, forceRefresh) ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh) } } @@ -164,8 +164,15 @@ class GradeStatisticsPresenter @Inject constructor( Status.SUCCESS -> { Timber.i("Loading grade stats result: Success") view?.run { - showEmpty(it.data!!.isEmpty() || it.data.first().partial.isEmpty()) - showContent(it.data.isNotEmpty() && it.data.first().partial.isNotEmpty()) + val isNoContent = it.data!!.isEmpty() || when (type) { + ViewType.SEMESTER -> it.data.firstOrNull()?.semester?.amounts.orEmpty().sum() == 0 + ViewType.PARTIAL -> it.data.firstOrNull()?.partial?.classAmounts.orEmpty().sum() == 0 + ViewType.POINTS -> it.data.firstOrNull()?.points?.let { points -> + points.student == .0 && points.others == .0 + } ?: false + } + showEmpty(isNoContent) + showContent(!isNoContent) showErrorView(false) updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt index cd2a3070b..6d3d63a7a 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt @@ -4,7 +4,8 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.GradePointsStatistics -import io.github.wulkanowy.sdk.pojo.GradeStatistics +import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem +import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject import io.github.wulkanowy.utils.init import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -35,8 +36,8 @@ class GradeStatisticsRemoteTest { @Test fun getGradeStatisticsTest() { coEvery { mockSdk.getGradesPartialStatistics(1) } returns listOf( - getGradeStatistics("Fizyka"), - getGradeStatistics("Matematyka") + getGradeStatisticsPartialSubject("Fizyka"), + getGradeStatisticsPartialSubject("Matematyka") ) every { semesterMock.studentId } returns 1 @@ -45,7 +46,7 @@ class GradeStatisticsRemoteTest { every { semesterMock.semesterId } returns 1 every { mockSdk.switchDiary(any(), any()) } returns mockSdk - val stats = runBlocking { GradeStatisticsRemote(mockSdk).getGradeStatistics(student, semesterMock, false) } + val stats = runBlocking { GradeStatisticsRemote(mockSdk).getGradePartialStatistics(student, semesterMock) } assertEquals(2, stats.size) } @@ -66,19 +67,24 @@ class GradeStatisticsRemoteTest { assertEquals(2, stats.size) } - private fun getGradeStatistics(subjectName: String): GradeStatistics { - return GradeStatistics( + private fun getGradeStatisticsPartialSubject(subjectName: String): GradeStatisticsSubject { + return GradeStatisticsSubject( subject = subjectName, - gradeValue = 5, - amount = 10, - grade = "", - semesterId = 1 + studentAverage = "", + classAverage = "", + classItems = listOf( + GradeStatisticsItem( + subject = subjectName, + grade = 0, + amount = 0 + ) + ), + studentItems = listOf() ) } private fun getGradePointsStatistics(subjectName: String): GradePointsStatistics { return GradePointsStatistics( - semesterId = 1, subject = subjectName, student = 0.80, others = 0.40 From 8a00ae95b899a41344fa9b3b932bce7dbf9e4b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Jelnicki?= Date: Fri, 13 Nov 2020 23:59:45 +0100 Subject: [PATCH 023/110] Update contributor's username (#1020) --- app/src/main/assets/contributors.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index 40aff4c48..54b47350b 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -33,6 +33,6 @@ }, { "displayName": "Mateusz Idziejczak", - "githubUsername": "PanTajemnic" + "githubUsername": "Luncenok" } ] From c7fdcc2bbdbf4f533688b56702cd313b18745778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 17 Nov 2020 16:58:24 +0100 Subject: [PATCH 024/110] Add facebook link to about (#1021) --- .../github/wulkanowy/ui/modules/about/AboutFragment.kt | 9 +++++++++ .../wulkanowy/ui/modules/about/AboutPresenter.kt | 8 +++++++- .../io/github/wulkanowy/ui/modules/about/AboutView.kt | 4 ++++ app/src/main/res/drawable/ic_about_facebook.xml | 10 ++++++++++ app/src/main/res/values/strings.xml | 2 ++ build.gradle | 2 +- 6 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_about_facebook.xml diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt index 5ec1a66e3..0730033f1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutFragment.kt @@ -58,6 +58,11 @@ class AboutFragment : BaseFragment(R.layout.fragment_about Triple(getString(R.string.about_discord), getString(R.string.about_discord_summary), getCompatDrawable(R.drawable.ic_about_discord)) } + override val facebookRes: Triple? + get() = context?.run { + Triple(getString(R.string.about_facebook), getString(R.string.about_facebook_summary), getCompatDrawable(R.drawable.ic_about_facebook)) + } + override val homepageRes: Triple? get() = context?.run { Triple(getString(R.string.about_homepage), getString(R.string.about_homepage_summary), getCompatDrawable(R.drawable.ic_about_homepage)) @@ -113,6 +118,10 @@ class AboutFragment : BaseFragment(R.layout.fragment_about context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) } + override fun openFacebookPage() { + context?.openInternetBrowser("https://www.facebook.com/wulkanowy", ::showMessage) + } + override fun openHomepage() { context?.openInternetBrowser("https://wulkanowy.github.io/", ::showMessage) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt index cd08b67eb..76ba82129 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt @@ -3,8 +3,8 @@ package io.github.wulkanowy.ui.modules.about import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import timber.log.Timber import javax.inject.Inject @@ -46,6 +46,11 @@ class AboutPresenter @Inject constructor( openDiscordInvite() analytics.logEvent("about_open", "name" to "discord") } + facebookRes?.first -> { + Timber.i("Opening facebook") + openFacebookPage() + analytics.logEvent("about_open", "name" to "facebook") + } homepageRes?.first -> { Timber.i("Opening homepage") openHomepage() @@ -78,6 +83,7 @@ class AboutPresenter @Inject constructor( feedbackRes, faqRes, discordRes, + facebookRes, homepageRes, licensesRes, privacyRes diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt index 4c4b002f9..54882b302 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutView.kt @@ -15,6 +15,8 @@ interface AboutView : BaseView { val discordRes: Triple? + val facebookRes: Triple? + val homepageRes: Triple? val licensesRes: Triple? @@ -31,6 +33,8 @@ interface AboutView : BaseView { fun openDiscordInvite() + fun openFacebookPage() + fun openEmailClient() fun openFaqPage() diff --git a/app/src/main/res/drawable/ic_about_facebook.xml b/app/src/main/res/drawable/ic_about_facebook.xml new file mode 100644 index 000000000..a1b7b46eb --- /dev/null +++ b/app/src/main/res/drawable/ic_about_facebook.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc3dbe3c5..7c77b735c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -351,6 +351,8 @@ Read Frequently Asked Questions Discord server Join the Wulkanowy community + Facebook fanpage + Like our facebook fanpage Privacy policy Rules for collecting personal data Homepage diff --git a/build.gradle b/build.gradle index 051fce64f..c6a6da0b4 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.1.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.4' classpath 'com.huawei.agconnect:agcp:1.4.1.300' From 5e9853b04335b7fd98f781b8054fbd89c62ab911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 21 Nov 2020 15:50:18 +0100 Subject: [PATCH 025/110] New Crowdin updates (#1022) --- app/src/main/res/values-cs-rCZ/strings.xml | 2 ++ app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values-pl/strings.xml | 2 ++ app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values-uk/strings.xml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index dd474aec0..44a03e3d7 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -348,6 +348,8 @@ Přečtěte si často kladené otázky Server Discord Připojte se ke komunitě Wulkanowy + Facebooková fanpage + Stejně jako naše facebooková fanpage Zásady ochrany osobních údajů Pravidla pro shromažďování osobních údajů Domovská stránka diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d885261cd..a16b18e56 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -308,6 +308,8 @@ Lesen Sie Häufig gestellte Fragen Discord server Treten Sie der Wulkanowy-Gemeinschaft bei + Facebook fanpage + Gefällt unsere Facebook-Fanpage Datenschutzerklärung Regeln für die Sammlung persönlicher Daten Startseite diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 67f8bd1be..dc877eafa 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -348,6 +348,8 @@ Zobacz Najczęściej Zadawane Pytania Serwer Discord Dołącz do społeczności Wulkanowego + Fanpage na Facebooku + Polub nasz fanpage na Facebooku Polityka prywatności Zasady zbierania danych osobowych Strona domowa diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5e260d999..e2620b603 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -348,6 +348,8 @@ Часто задаваемые вопросы Сервер Discord Присоединиться к сообществу приложения + Facebook фан-страница + Поставьте лайк на нашей странице в Facebook Политика приватности Правила хранения личных данных Домашняя страница diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 48da2784f..cc26b24fb 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -348,6 +348,8 @@ Запитання, які часто задають Сервер Discord Приєднатися до спільноти додатка + Фен-сторінка Facebook + Вподобати нашу фансторінку у Facebook Політика конфіденційності Правила зберігання особистих даних Домашня сторінка From c675dc8b848991836b4e72c2b24c05426f200c21 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:27:47 +0000 Subject: [PATCH 026/110] Bump kotlin_version from 1.4.10 to 1.4.20 (#1026) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c6a6da0b4..92f4e448c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.4.10' + kotlin_version = '1.4.20' about_libraries = '8.4.3' hilt_version = "2.29.1-alpha" } From c42333cd35854639dd77a6ef90434744071058e4 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:28:10 +0000 Subject: [PATCH 027/110] Bump firebase-crashlytics-gradle from 2.3.0 to 2.4.1 (#1029) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 92f4e448c..cbc061934 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.4' classpath 'com.huawei.agconnect:agcp:1.4.1.300' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' classpath "com.github.triplet.gradle:play-publisher:2.8.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0" From 3c0dda9a82c13af9f94db34766fe0212b3e725cb Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:29:49 +0000 Subject: [PATCH 028/110] Bump hianalytics from 5.0.4.301 to 5.0.5.301 (#1023) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0afaab011..6944276a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,7 +206,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' - hmsImplementation 'com.huawei.hms:hianalytics:5.0.4.301' + hmsImplementation 'com.huawei.hms:hianalytics:5.0.5.301' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 13906a7d62c9071e67cd7a65379adde1cae0637c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:47:26 +0000 Subject: [PATCH 029/110] Bump about_libraries from 8.4.3 to 8.6.2 (#1025) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cbc061934..d0479a632 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.4.20' - about_libraries = '8.4.3' + about_libraries = '8.6.2' hilt_version = "2.29.1-alpha" } repositories { From 0fc828f006bbd2a51554cab88b39d834bf215e1c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:48:25 +0000 Subject: [PATCH 030/110] Bump agcp from 1.4.1.300 to 1.4.2.301 (#1024) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d0479a632..6aa3d6f2f 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.android.tools.build:gradle:4.1.1' classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.4' - classpath 'com.huawei.agconnect:agcp:1.4.1.300' + classpath 'com.huawei.agconnect:agcp:1.4.2.301' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' classpath "com.github.triplet.gradle:play-publisher:2.8.0" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" From 1428887204f286ccb99fda0a6e5fadad636bad67 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:57:37 +0000 Subject: [PATCH 031/110] Bump agconnect-crash from 1.4.1.300 to 1.4.2.301 (#1030) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6944276a4..2d35af507 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -207,7 +207,7 @@ dependencies { playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' hmsImplementation 'com.huawei.hms:hianalytics:5.0.5.301' - hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300' + hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.2.301' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 7ed478749644e9514a6623e5a752aa4912c416ed Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 14:19:21 +0000 Subject: [PATCH 032/110] Bump chucker from 3.3.0 to 3.4.0 (#1027) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2d35af507..5957bd6a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -132,7 +132,7 @@ play { ext { work_manager = "2.4.0" room = "2.2.5" - chucker = "3.3.0" + chucker = "3.4.0" mockk = "1.10.2" moshi = "1.11.0" } From f263b5534a5c57644738c4cae7bd9578e08bd3aa Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 14:08:26 +0000 Subject: [PATCH 033/110] Bump mockk from 1.10.2 to 1.10.3-jdk8 (#1034) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5957bd6a8..a8ee923fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,7 +133,7 @@ ext { work_manager = "2.4.0" room = "2.2.5" chucker = "3.4.0" - mockk = "1.10.2" + mockk = "1.10.3-jdk8" moshi = "1.11.0" } From 41dbd2d25f0a32ac4d435d7e7e38deece2806d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 6 Dec 2020 15:14:58 +0100 Subject: [PATCH 034/110] New Crowdin updates (#1033) --- app/src/main/res/values-cs-rCZ/strings.xml | 100 ++++++++++----------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 44a03e3d7..3769b8511 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -1,10 +1,10 @@ - Přihlásit se + Přihlášení Wulkanowy Známky - Prezence + Docházka Zkoušky Plán lekce Nastavení @@ -16,12 +16,12 @@ Zprávy Nová zpráva Poznámky a úspěchy - Domácí práce + Domácí úkoly Vyberte účet Semestr %1$d, %2$d/%3$d - Přihlaste se pomocí studentského nebo nadřazeného účtu + Přihlaste se pomocí studentského nebo rodičovského účtu Zadejte symbol ze stránky deníku Uživatelské jméno Email @@ -41,14 +41,14 @@ Neplatný PIN Neplatný token Platnost tokenu vypršela - Nesprávná e-mailová adresa + Neplatný e-mail Místo e-mailu použijte přiřazené přihlašovací údaje Neplatný symbol Student nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Toto pole je povinné Vybraný student je již přihlášen Symbol najdete na stránce deníku v  Uczeń →  Dostęp Mobilny →  Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+. Wulkanowy v tuto chvíli nezjistí předškolní studenty - Vyberte studenty k přihlášení do aplikace + Vyberte studenty, kteří se mají do aplikace přihlásit Jiné možnosti V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, absolvované lekce, informace o škole a prohlížení seznamu registrovaných zařízení Tento režim zobrazuje stejná data, která se zobrazují na webových stránkách deníka @@ -65,7 +65,7 @@ Obnovit Student je již přihlášen - Správce účtu + Manažer účtů Přihlásit se Platnost relace vypršela Vaše relace vypršela, přihlaste se prosím znovu @@ -79,9 +79,9 @@ Komentář Žádné nové známky Počet nových známek: %1$d - Průměrný: %1$.2f + Průměr: %1$.2f Body: %s - Žádný průměr + Bez průměru Předpovězeno: %1$s Konečná: %1$s Celkem bodů @@ -126,10 +126,10 @@ Máte %1$d nové známky - Máte %1$d novou konečnou známku - Máte %1$d nové konečné známky - Máte %1$d nové konečné známky - Máte %1$d nové konečné známky + Máte %1$d novou předpokládanou známku + Máte %1$d nové předpokládané známky + Máte %1$d nové předpokládané známky + Máte %1$d nové předpokládané známky Máte %1$d novou konečnou známku @@ -143,7 +143,7 @@ Skupina Hodiny Změny - Dnes žádné lekce + Žádné lekce tento den %s min %s sek dosud %1$s @@ -155,32 +155,32 @@ Dokončené lekce Zobrazit dokončené lekce - Žádné informace o absolvovaných lekcích + Žádné informace o dokončených lekcích Téma - Absence + Nepřítomnost Zdroje Souhrn docházky Nepřítomen ze školních důvodů - Omluvená absence + Omluvená nepřítomnost Neomluvená absence Osvobození - Oprávněné zpoždění - Neomluvená zpoždění + Omluvené zpoždění + Neomluvené zpoždění Přítomnost Smazáno Neznámý Číslo lekce Žádné položky - %1$d absence - %1$d absence - %1$d absence - %1$d absence + %1$d nepřítomnost + %1$d nepřítomnosti + %1$d nepřítomnosti + %1$d nepřítomnosti - Důvod absence (volitelný) + Důvod nepřítomnosti (volitelný) Poslat - Absence úspěšně omluvena! + Nepřítomnost úspěšně omluvena! Musíte vybrat alespoň jednu nepřítomnost! Ospravedlnit @@ -198,7 +198,7 @@ Žádné zprávy Při stahování obsahu zprávy došlo k chybě Od: - Do: + Komu: Datum: %s Odpověď Poslat dále @@ -226,10 +226,10 @@ Nové zprávy - Obdrželi jste %1$d zprávu - Obdrželi jste %1$d zpráv - Obdrželi jste %1$d zpráv - Obdrželi jste %1$d zpráv + Máte %1$d novou zprávu + Máte %1$d nové zprávy + Máte %1$d nové zprávy + Máte %1$d nové zprávy Žádné informace o poznámkách @@ -286,12 +286,12 @@ Máte %1$d novou neutrální pozornost - Máte %1$d nové neutrální komentáře - Máte %1$d nové neutrální komentáře - Máte %1$d nové neutrální komentáře + Máte %1$d nové neutrální pozornosti + Máte %1$d nové neutrální pozornosti + Máte %1$d nové neutrální pozornosti - Žádný domácí úkol + Žádné informace o domácím úkolu Označit jako hotové Neudělané Přílohy @@ -315,7 +315,7 @@ Škola Žádné informace o škole - Školní jméno + Název školy Adresa školy Telefon Jméno ředitele @@ -336,14 +336,14 @@ Odhlášení studentů Studentský účet Rodičovský účet - Režimu Mobíle API + Režim Mobilního API Hybridní režim Verze aplikace Tvůrci Seznam vývojářů Wulkanowy Nahlásit chybu - Pošlete hlášení o chybě e-mailem + Odeslat zprávu o chybě e-mailem FAQ Přečtěte si často kladené otázky Server Discord @@ -365,8 +365,8 @@ Sdílejte protokoly Obnovit - Kontrola aktualizací - Před nahlášením chyby nejprve zkontrolujte, zda je k dispozici aktualizace s opravou chyby + Zkontrolovat aktualizace + Před hlášením chyby zkontrolujte, zda je k dispozici aktualizace s opravou chyb Obsah Zkuste to znovu @@ -383,21 +383,21 @@ Předmět Předchozí Další - Vyhledávání - Vyhledávání… + Hledat + Hledat… Žádné lekce - Vyberte téma + Vybrat motiv Světlý Tmavý - Téma systému + Motiv systému Vzhled Výchozí zobrazení Výpočet koncoročního průměru Vynutit průměrný výpočet podle aplikace Zobrazit přítomnost v účasti - Téma aplikace + Motiv aplikace Rozbalit známky Označte aktuální lekci v plánu lekce Ukázat skupiny vedle předmětů v plánu lekce @@ -412,22 +412,22 @@ Ukázat nadcházející oznámení o lekci Opravte problémy se synchronizací a upozorněním Ve vašem zařízení mohou nastat problémy se synchronizací dat a oznámenímii.\n\nChcete-li je opravit, přidejte Wulkanowy do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu. - Jdi do nastavení + Přejít do nastavení Ukázat oznámení o ladění Synchronizace Automatická aktualizace Pozastaveno na dovolené Interval aktualizací Pouze Wi-Fi - Nyní synchronizovat + Synchronizovat nyní Synchronizováno! - Synchronizace se nezdařila + Synchronizace selhala Probíhá synchronizace Synchronizace Ruční synchronizace neobnoví zobrazení aplikace. \nChcete-li zobrazit synchronizovaná data, restartujte aplikaci po synchronizaci. - Jiný + Jiné Hodnota plusu Hodnota mínusu Odpovědět s historií zpráv @@ -437,9 +437,9 @@ Šťastné číslo Nové zprávy Nové poznámky - Oznámení push + Push oznámení Nadcházející lekce - Ladění + Debug Černý Červený From 40ec5bbe867846d611e495301ac832ec0e03be21 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 14:29:32 +0000 Subject: [PATCH 035/110] Bump kotlinx-coroutines-android from 1.4.0 to 1.4.2-native-mt (#1032) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a8ee923fe..02c121f95 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -147,7 +147,7 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.activity:activity-ktx:1.1.0" From bf342ed28947e5b5702b515eba9adaa089e52dbd Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 14:32:08 +0000 Subject: [PATCH 036/110] Bump kotlinx-coroutines-test from 1.4.0 to 1.4.2-native-mt (#1031) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 02c121f95..22d24083b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,7 +216,7 @@ dependencies { testImplementation "junit:junit:4.13.1" testImplementation "io.mockk:mockk:$mockk" - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.0' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2' androidTestImplementation "androidx.test:core:1.3.0" androidTestImplementation "androidx.test:runner:1.3.0" From ce802cc737088ffd3013854f18a387e3940f7aac Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 14:43:48 +0000 Subject: [PATCH 037/110] Bump about_libraries from 8.6.2 to 8.6.3 (#1037) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6aa3d6f2f..84d1d2367 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { kotlin_version = '1.4.20' - about_libraries = '8.6.2' + about_libraries = '8.6.3' hilt_version = "2.29.1-alpha" } repositories { From e637896ad3c971ea552619bafd3b5a6829d15786 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 14:50:57 +0000 Subject: [PATCH 038/110] Bump firebase-crashlytics from 17.2.2 to 17.3.0 (#1038) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 22d24083b..3e4bbfe09 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,7 +202,7 @@ dependencies { playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.2' playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.2" playImplementation 'com.google.firebase:firebase-messaging:21.0.0' - playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2' + playImplementation 'com.google.firebase:firebase-crashlytics:17.3.0' playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' From d115372c3bc9c197aeec882139426302686249a1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 14:57:50 +0000 Subject: [PATCH 039/110] Bump coil from 1.0.0 to 1.1.0 (#1040) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3e4bbfe09..cd7a1bcba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,7 +194,7 @@ dependencies { implementation "fr.bipi.treessence:treessence:0.3.2" implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation 'com.wdullaer:materialdatetimepicker:4.2.3' - implementation "io.coil-kt:coil:1.0.0" + implementation "io.coil-kt:coil:1.1.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.3.1' From 33d540e1c9b6af3953cbba00af95c3d9fcba61e6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 15:04:43 +0000 Subject: [PATCH 040/110] Bump desugar_jdk_libs from 1.0.10 to 1.1.1 (#1039) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index cd7a1bcba..c85af3c70 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -144,7 +144,7 @@ configurations.all { dependencies { implementation "io.github.wulkanowy:sdk:bcf6b53" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' From 05a597313b4898c8953a5f2ab9e9caea2b95a439 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Dec 2020 17:54:09 +0000 Subject: [PATCH 041/110] Bump hilt_version from 2.29.1-alpha to 2.30.1-alpha (#1035) --- .../main/java/io/github/wulkanowy/data/RepositoryModule.kt | 4 ++-- app/src/main/java/io/github/wulkanowy/di/AppModule.kt | 4 ++-- .../main/java/io/github/wulkanowy/services/ServicesModule.kt | 4 ++-- build.gradle | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 5efe77739..e8a3fa48e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -11,8 +11,8 @@ import com.chuckerteam.chucker.api.RetentionManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ApplicationComponent import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository @@ -21,7 +21,7 @@ import timber.log.Timber import javax.inject.Singleton @Module -@InstallIn(ApplicationComponent::class) +@InstallIn(SingletonComponent::class) internal class RepositoryModule { @Singleton diff --git a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt index 9133c34ed..4efb4d84a 100644 --- a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt @@ -6,13 +6,13 @@ import com.yariksoffice.lingver.Lingver import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ApplicationComponent import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent import io.github.wulkanowy.utils.DispatchersProvider import javax.inject.Singleton @Module -@InstallIn(ApplicationComponent::class) +@InstallIn(SingletonComponent::class) internal class AppModule { @Singleton diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt index ac5a84e8f..891f07da0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt +++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt @@ -9,8 +9,8 @@ import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ApplicationComponent import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.services.sync.channels.DebugChannel @@ -38,7 +38,7 @@ import javax.inject.Singleton @Suppress("unused") @Module -@InstallIn(ApplicationComponent::class) +@InstallIn(SingletonComponent::class) abstract class ServicesModule { companion object { diff --git a/build.gradle b/build.gradle index 84d1d2367..a68efccde 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { kotlin_version = '1.4.20' about_libraries = '8.6.3' - hilt_version = "2.29.1-alpha" + hilt_version = "2.30.1-alpha" } repositories { mavenCentral() From 6ca5e11371e65b27d16effd6582cce4c1aa4d64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 6 Dec 2020 19:31:35 +0100 Subject: [PATCH 042/110] Fix HMS analytics and crashlytics (#1042) --- .../io/github/wulkanowy/utils/AnalyticsHelper.kt | 4 ++++ .../io/github/wulkanowy/utils/AnalyticsHelper.kt | 9 +++++---- .../io/github/wulkanowy/utils/CrashLogUtils.kt | 4 ++-- .../wulkanowy/ui/base/BaseDialogFragment.kt | 15 +++++++++++++++ .../wulkanowy/ui/modules/main/MainActivity.kt | 14 ++++++++------ .../wulkanowy/ui/modules/main/MainPresenter.kt | 3 +-- .../github/wulkanowy/ui/modules/main/MainView.kt | 2 -- .../io/github/wulkanowy/utils/AnalyticsHelper.kt | 4 ++++ 8 files changed, 39 insertions(+), 16 deletions(-) diff --git a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index 0cd9a52e4..3bf7e1693 100644 --- a/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/fdroid/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -15,4 +15,8 @@ class AnalyticsHelper @Inject constructor() { fun setCurrentScreen(activity: Activity, name: String?) { // do nothing } + + fun popCurrentScreen(name: String?) { + // do nothing + } } diff --git a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index b3cecf243..5d33825f1 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -30,9 +30,10 @@ class AnalyticsHelper @Inject constructor( } fun setCurrentScreen(activity: Activity, name: String?) { - analytics.onEvent("screen_view", Bundle().apply { - putString("screen_name", name) - putString("screen_class", activity::class.simpleName) - }) + analytics.pageStart(name, activity::class.simpleName) + } + + fun popCurrentScreen(name: String?) { + analytics.pageEnd(name) } } diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt index 8adf2b29c..60588f63c 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -44,9 +44,9 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { connectCrash.setCustomKey("message", message) if (t != null) { - connectCrash.log(priority, t.stackTraceToString()) + connectCrash.recordException(t) } else { - connectCrash.log(priority, StackTraceRecorder(format(priority, tag, message)).stackTraceToString()) + connectCrash.recordException(StackTraceRecorder(format(priority, tag, message))) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 18cd58b41..1c31976ee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -3,10 +3,15 @@ package io.github.wulkanowy.ui.base import android.widget.Toast import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.lifecycleAwareVariable +import javax.inject.Inject abstract class BaseDialogFragment : DialogFragment(), BaseView { + @Inject + lateinit var analyticsHelper: AnalyticsHelper + protected var binding: VB by lifecycleAwareVariable() override fun showError(text: String, error: Throwable) { @@ -28,4 +33,14 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView override fun showErrorDetailsDialog(error: Throwable) { ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + + override fun onResume() { + super.onResume() + analyticsHelper.setCurrentScreen(requireActivity(), this::class.simpleName) + } + + override fun onPause() { + super.onPause() + analyticsHelper.popCurrentScreen(this::class.simpleName) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 245f3b16c..25b41aab5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -38,8 +38,8 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment -import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.UpdateHelper import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.getThemeAttrColor @@ -182,7 +182,10 @@ class MainActivity : BaseActivity(), MainVie } with(navController) { - setOnViewChangeListener(presenter::onViewChange) + setOnViewChangeListener { section, name -> + analytics.setCurrentScreen(this@MainActivity, name) + presenter.onViewChange(section) + } fragmentHideStrategy = HIDE rootFragments = listOf( GradeFragment.newInstance(), @@ -194,10 +197,6 @@ class MainActivity : BaseActivity(), MainVie } } - override fun setCurrentScreen(name: String?) { - analytics.setCurrentScreen(this, name) - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { return if (item.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected() else false @@ -208,6 +207,7 @@ class MainActivity : BaseActivity(), MainVie } override fun switchMenuView(position: Int) { + analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.switchTab(position) } @@ -245,10 +245,12 @@ class MainActivity : BaseActivity(), MainVie } fun pushView(fragment: Fragment) { + analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.pushFragment(fragment) } override fun popView(depth: Int) { + analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) navController.safelyPopFragments(depth) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 7ea8197e7..cc525c29c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -35,9 +35,8 @@ class MainPresenter @Inject constructor( analytics.logEvent("app_open", "destination" to initMenu?.name) } - fun onViewChange(section: MainView.Section?, name: String?) { + fun onViewChange(section: MainView.Section?) { view?.apply { - setCurrentScreen(name) showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL) currentViewTitle?.let { setViewTitle(it) } currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 7e5831471..97b556e3e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -24,8 +24,6 @@ interface MainView : BaseView { fun showAccountPicker() - fun setCurrentScreen(name: String?) - fun showActionBarElevation(show: Boolean) fun notifyMenuViewReselected() diff --git a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt index 8897f180d..ba29e1cb6 100644 --- a/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt +++ b/app/src/play/java/io/github/wulkanowy/utils/AnalyticsHelper.kt @@ -35,4 +35,8 @@ class AnalyticsHelper @Inject constructor( putString(FirebaseAnalytics.Param.SCREEN_CLASS, activity::class.simpleName) }) } + + @Suppress("UNUSED_PARAMETER") + fun popCurrentScreen(name: String?) { + } } From 67cef0f6d9062129bfd3410f051c29c21a846001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 6 Dec 2020 21:23:02 +0100 Subject: [PATCH 043/110] Add register variant auto-matching based on email (#1041) --- app/build.gradle | 2 +- .../repositories/timetable/TimetableRemote.kt | 2 +- .../modules/login/form/LoginFormFragment.kt | 8 ++++++ .../modules/login/form/LoginFormPresenter.kt | 17 +++++++++--- .../ui/modules/login/form/LoginFormView.kt | 4 +++ app/src/main/res/values/api_hosts.xml | 26 +++++++++---------- .../timetable/TimetableRemoteTest.kt | 4 +-- 7 files changed, 43 insertions(+), 20 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c85af3c70..8dde9e7cd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:bcf6b53" + implementation "io.github.wulkanowy:sdk:50c049ef" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt index eef8729e8..9fb80e77d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt @@ -14,7 +14,7 @@ class TimetableRemote @Inject constructor(private val sdk: Sdk) { suspend fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List { return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getTimetable(startDate, endDate) + .getTimetable(startDate, endDate).first .map { Timetable( studentId = semester.studentId, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt index 41345686a..c6d0209da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt @@ -89,6 +89,8 @@ class LoginFormFragment : BaseFragment(R.layout.fragme } } + override fun getHostsValues(): List = hostValues.toList() + override fun setCredentials(username: String, pass: String) { with(binding) { loginFormUsername.setText(username) @@ -96,6 +98,12 @@ class LoginFormFragment : BaseFragment(R.layout.fragme } } + override fun setHost(host: String) { + binding.loginFormHost.setText( + hostKeys.getOrNull(hostValues.indexOf(host)).orEmpty() + ) + } + override fun setUsernameLabel(label: String) { binding.loginFormUsernameLayout.hint = label } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index 3e77cd775..35c79bb5e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.login.form +import androidx.core.net.toUri import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter @@ -56,7 +57,7 @@ class LoginFormPresenter @Inject constructor( fun updateUsernameLabel() { view?.run { - setUsernameLabel(if ("standard" in formHostValue) emailLabel else nicknameLabel) + setUsernameLabel(if ("login" in formHostValue) nicknameLabel else emailLabel) } } @@ -66,6 +67,16 @@ class LoginFormPresenter @Inject constructor( fun onUsernameTextChanged() { view?.clearUsernameError() + + val username = view?.formUsernameValue.orEmpty().trim() + if ("@" in username && "@vulcan" !in username) { + val hosts = view?.getHostsValues().orEmpty().map { it.toUri().host to it }.toMap() + val usernameHost = username.substringAfter("@") + + hosts[usernameHost]?.let { + view?.setHost(it) + } + } } fun onSignInClick() { @@ -135,12 +146,12 @@ class LoginFormPresenter @Inject constructor( view?.setErrorUsernameRequired() isCorrect = false } else { - if ("@" in login && "standard" !in host) { + if ("@" in login && "login" in host) { view?.setErrorLoginRequired() isCorrect = false } - if ("@" !in login && "standard" in host) { + if ("@" !in login && "email" in host) { view?.setErrorEmailRequired() isCorrect = false } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt index 77e665389..31f8a6217 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormView.kt @@ -19,8 +19,12 @@ interface LoginFormView : BaseView { val emailLabel: String + fun getHostsValues(): List + fun setCredentials(username: String, pass: String) + fun setHost(host: String) + fun setUsernameLabel(label: String) fun setErrorUsernameRequired() diff --git a/app/src/main/res/values/api_hosts.xml b/app/src/main/res/values/api_hosts.xml index 29434602e..0a43612d6 100644 --- a/app/src/main/res/values/api_hosts.xml +++ b/app/src/main/res/values/api_hosts.xml @@ -22,25 +22,25 @@ Fakelog - https://vulcan.net.pl/?standard + https://vulcan.net.pl/?email https://eszkola.opolskie.pl https://edu.gdansk.pl https://edu.lublin.eu https://umt.tarnow.pl https://resman.pl https://eduportal.koszalin.pl - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - https://vulcan.net.pl/ - http://fakelog.tk/?standard + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + https://vulcan.net.pl/?login + http://fakelog.cf/?email Default diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt index c948ba31b..a333e0d7a 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt @@ -39,10 +39,10 @@ class TimetableRemoteTest { of(2018, 9, 10), of(2018, 9, 15) ) - } returns listOf( + } returns (listOf( getTimetable(of(2018, 9, 10)), getTimetable(of(2018, 9, 17)) - ) + ) to emptyList()) every { semesterMock.studentId } returns 1 every { semesterMock.diaryId } returns 1 From 73a92497ede9b1c32a7fd059655539b7e6cf1167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 6 Dec 2020 22:12:18 +0100 Subject: [PATCH 044/110] Version 0.23.0 --- .travis.yml | 2 +- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 26abd2025..1fb3df0c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.22.2 + - 0.23.0 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index 8dde9e7cd..e5cfdcb2c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 30 - versionCode 75 - versionName "0.22.2" + versionCode 76 + versionName "0.23.0" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -126,7 +126,7 @@ play { serviceAccountCredentials = file('key.p12') defaultToAppBundles = false track = 'alpha' - updatePriority = 1 + updatePriority = 3 } ext { @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:50c049ef" + implementation "io.github.wulkanowy:sdk:0.23.0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 23fd77bc3..32bce4a96 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,6 +1,7 @@ -Wersja 0.22.2 -- naprawiliśmy problem z wyświetlaniem pozycji na liście ocen -- zmieniliśmy komunikaty o błędach, które powinny być teraz czytelniejsze dla większej liczby użytkowników -- zwiększyliśmy maksymalny czas, przez który aplikacja będzie próbowała łączyć się z dziennikiem do 1 minuty +Wersja 0.23.0 +- naprawiliśmy logowanie do dziennika Gdańskiej i Koszalińskiej Platformy Edukacyjnej +- przy logowaniu odpowiednia odmiana dziennika jest ustawiana automatycznie na podstawie wpisanego adresu email +- dodaliśmy wyświetlanie średniej w ocenach klasy (jeśli włączone przez szkołę) +- dodaliśmy wyświetlanie zebrań Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 93bc4e92a9570d3aa55edaeadbdc3ad14f45d243 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 9 Dec 2020 15:38:06 +0000 Subject: [PATCH 045/110] Bump kotlin_version from 1.4.20 to 1.4.21 (#1047) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a68efccde..6e30851d7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.4.20' + kotlin_version = '1.4.21' about_libraries = '8.6.3' hilt_version = "2.30.1-alpha" } From 1680ad233e1d6ddbb308a8b559ab2cbb3fa2e054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 10 Dec 2020 21:12:14 +0100 Subject: [PATCH 046/110] New translations strings.xml (Czech) (#1049) --- app/src/main/res/values-cs-rCZ/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 3769b8511..6ddc561d6 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -98,8 +98,8 @@ %d známka %d známky - %d známky - %d známky + %d známek + %d známek Nová známka @@ -291,7 +291,7 @@ Máte %1$d nové neutrální pozornosti - Žádné informace o domácím úkolu + Žádné informace o domácích úkolech Označit jako hotové Neudělané Přílohy From 3eba89aeb97ac5a02332745a02e309604193e85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Thu, 10 Dec 2020 21:12:59 +0100 Subject: [PATCH 047/110] Disable recordException in HMS (#1048) --- .../hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt index 60588f63c..b5fb6ad71 100644 --- a/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt +++ b/app/src/hms/java/io/github/wulkanowy/utils/CrashLogUtils.kt @@ -3,7 +3,6 @@ package io.github.wulkanowy.utils import android.util.Log import com.huawei.agconnect.crash.AGConnectCrash import fr.bipi.tressence.base.FormatterPriorityTree -import fr.bipi.tressence.common.StackTraceRecorder import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import java.io.InterruptedIOException @@ -39,7 +38,9 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { if (skipLog(priority, tag, message, t)) return - connectCrash.setCustomKey("priority", priority) + // Disabled due to a bug in the Huawei library + + /*connectCrash.setCustomKey("priority", priority) connectCrash.setCustomKey("tag", tag.orEmpty()) connectCrash.setCustomKey("message", message) @@ -47,6 +48,6 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { connectCrash.recordException(t) } else { connectCrash.recordException(StackTraceRecorder(format(priority, tag, message))) - } + }*/ } } From 315e9b05954414f1b2e2384e270b0873e460f87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 16 Dec 2020 20:48:01 +0100 Subject: [PATCH 048/110] Fix username label on registers other than default (#1050) --- app/build.gradle | 2 +- .../wulkanowy/ui/modules/login/form/LoginFormPresenter.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e5cfdcb2c..be1e5c7ae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.23.0" + implementation "io.github.wulkanowy:sdk:41b164fe" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index 35c79bb5e..e165d264f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -57,7 +57,7 @@ class LoginFormPresenter @Inject constructor( fun updateUsernameLabel() { view?.run { - setUsernameLabel(if ("login" in formHostValue) nicknameLabel else emailLabel) + setUsernameLabel(if ("email" !in formHostValue) nicknameLabel else emailLabel) } } From 88c5c7d9dcd3e689c900e310eb9b3df7c0ea1a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Wed, 16 Dec 2020 21:19:47 +0100 Subject: [PATCH 049/110] Version 0.23.1 --- .travis.yml | 2 +- app/build.gradle | 6 +++--- app/src/main/play/release-notes/pl-PL/default.txt | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1fb3df0c3..238fefdfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: branches: only: - develop - - 0.23.0 + - 0.23.1 android: licenses: diff --git a/app/build.gradle b/app/build.gradle index be1e5c7ae..89b5346ae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 30 - versionCode 76 - versionName "0.23.0" + versionCode 77 + versionName "0.23.1" multiDexEnabled true resValue "string", "app_name", "Wulkanowy" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:41b164fe" + implementation "io.github.wulkanowy:sdk:0.23.1" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 32bce4a96..908a97d53 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,7 +1,5 @@ -Wersja 0.23.0 -- naprawiliśmy logowanie do dziennika Gdańskiej i Koszalińskiej Platformy Edukacyjnej -- przy logowaniu odpowiednia odmiana dziennika jest ustawiana automatycznie na podstawie wpisanego adresu email -- dodaliśmy wyświetlanie średniej w ocenach klasy (jeśli włączone przez szkołę) -- dodaliśmy wyświetlanie zebrań +Wersja 0.23.1 +- naprawiliśmy opis pola na email/login przy wyborze niestandardowych dzienników +- naprawiliśmy obsługę dziennika lubelskiego i koszalińskiego na starszych urządzeniach (z Androidem 5 i starszymi) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases From 295fd0fd902de2a9adc505833889ec95df6b25cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sat, 26 Dec 2020 22:40:21 +0100 Subject: [PATCH 050/110] Add language event (#1052) * Add log event to language * Add log event to app --- .../java/io/github/wulkanowy/WulkanowyApp.kt | 19 +++++++++++++++++++ .../ui/modules/settings/SettingsFragment.kt | 5 ----- .../ui/modules/settings/SettingsPresenter.kt | 7 +++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 57d147252..3661ac0a2 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -11,8 +11,10 @@ import androidx.work.Configuration import com.yariksoffice.lingver.Lingver import dagger.hilt.android.HiltAndroidApp import fr.bipi.tressence.file.FileLoggerTree +import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.utils.ActivityLifecycleLogger +import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.CrashLogExceptionTree import io.github.wulkanowy.utils.CrashLogTree @@ -32,6 +34,12 @@ class WulkanowyApp : Application(), Configuration.Provider { @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var preferencesRepository: PreferencesRepository + + @Inject + lateinit var analyticsHelper: AnalyticsHelper + override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) MultiDex.install(this) @@ -43,6 +51,7 @@ class WulkanowyApp : Application(), Configuration.Provider { themeManager.applyDefaultTheme() initLogging() + logCurrentLanguage() } private fun initLogging() { @@ -62,6 +71,16 @@ class WulkanowyApp : Application(), Configuration.Provider { registerActivityLifecycleCallbacks(ActivityLifecycleLogger()) } + private fun logCurrentLanguage() { + val newLang = if (preferencesRepository.appLanguage == "system") { + appInfo.systemLanguage + } else { + preferencesRepository.appLanguage + } + + analyticsHelper.logEvent("language", "startup" to newLang) + } + override fun getWorkManagerConfiguration() = Configuration.Builder() .setWorkerFactory(workerFactory) .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 6f4a695d6..bda07e347 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.settings -import android.content.Context import android.content.SharedPreferences import android.os.Bundle import androidx.appcompat.app.AlertDialog @@ -42,10 +41,6 @@ class SettingsFragment : PreferenceFragmentCompat(), override val syncFailedString get() = getString(R.string.pref_services_message_sync_failed) - override fun onAttach(context: Context) { - super.onAttach(context) - } - override fun initView() { findPreference(getString(R.string.pref_key_services_force_sync))?.run { onPreferenceClickListener = Preference.OnPreferenceClickListener { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index e640dd60d..758bedf0b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -8,8 +8,8 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.isHolidays import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.onEach @@ -46,7 +46,10 @@ class SettingsPresenter @Inject constructor( appThemeKey -> view?.recreateView() isUpcomingLessonsNotificationsEnableKey -> if (!isUpcomingLessonsNotificationsEnable) timetableNotificationHelper.cancelNotification() appLanguageKey -> view?.run { - updateLanguage(if (appLanguage == "system") appInfo.systemLanguage else appLanguage) + val newLang = if (appLanguage == "system") appInfo.systemLanguage else appLanguage + analytics.logEvent("language", "setting_changed" to newLang) + + updateLanguage(newLang) recreateView() } } From 976320868880775a19d11efbab87d3d925554bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 27 Dec 2020 14:06:07 +0100 Subject: [PATCH 051/110] Add additional lessons to timetable (#1019) --- app/build.gradle | 2 +- .../30.json | 1954 +++++++++++++++++ .../timetable/TimetableLocalTest.kt | 2 +- .../timetable/TimetableRepositoryTest.kt | 14 +- .../github/wulkanowy/data/RepositoryModule.kt | 4 + .../github/wulkanowy/data/db/AppDatabase.kt | 11 +- .../data/db/dao/TimetableAdditionalDao.kt | 16 + .../data/db/entities/TimetableAdditional.kt | 30 + .../data/db/migrations/Migration30.kt | 21 + .../repositories/grade/GradeRepository.kt | 12 +- .../repositories/timetable/TimetableLocal.kt | 19 +- .../repositories/timetable/TimetableRemote.kt | 60 +- .../timetable/TimetableRepository.kt | 56 +- .../ui/modules/timetable/TimetableFragment.kt | 12 +- .../modules/timetable/TimetablePresenter.kt | 13 +- .../ui/modules/timetable/TimetableView.kt | 2 + .../additional/AdditionalLessonsAdapter.kt | 35 + .../additional/AdditionalLessonsFragment.kt | 144 ++ .../additional/AdditionalLessonsPresenter.kt | 166 ++ .../additional/AdditionalLessonsView.kt | 38 + .../timetablewidget/TimetableWidgetFactory.kt | 2 +- .../ic_menu_timetable_lessons_additional.xml | 9 + .../layout/fragment_timetable_additional.xml | 163 ++ .../res/layout/item_timetable_additional.xml | 41 + .../main/res/menu/action_menu_timetable.xml | 7 + app/src/main/res/values/strings.xml | 6 + .../timetable/TimetableRemoteTest.kt | 2 +- 27 files changed, 2777 insertions(+), 64 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt create mode 100644 app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml create mode 100644 app/src/main/res/layout/fragment_timetable_additional.xml create mode 100644 app/src/main/res/layout/item_timetable_additional.xml diff --git a/app/build.gradle b/app/build.gradle index 89b5346ae..e77d03c97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:0.23.1" + implementation "io.github.wulkanowy:sdk:6edc8531" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json new file mode 100644 index 000000000..309f4ac09 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json @@ -0,0 +1,1954 @@ +{ + "formatVersion": 1, + "database": { + "version": 30, + "identityHash": "891980e378373d0a17bd341f9b07cc74", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '891980e378373d0a17bd341f9b07cc74')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt index e793212e6..f4257f257 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt @@ -26,7 +26,7 @@ class TimetableLocalTest { fun createDb() { testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) .build() - timetableDb = TimetableLocal(testDb.timetableDao) + timetableDb = TimetableLocal(testDb.timetableDao, testDb.timetableAdditionalDao) } @After diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt index 1bd3c4679..9f1e29720 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt @@ -46,7 +46,7 @@ class TimetableRepositoryTest { fun initApi() { MockKAnnotations.init(this) testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() - timetableLocal = TimetableLocal(testDb.timetableDao) + timetableLocal = TimetableLocal(testDb.timetableDao, testDb.timetableAdditionalDao) } @After @@ -65,12 +65,12 @@ class TimetableRepositoryTest { )) } - coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf( + coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns (listOf( createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"), createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "", "Religia"), createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "", "W-F"), createTimetableLocal(of(2019, 3, 5, 10, 30), 4, "", "W-F") - ) + ) to emptyList()) val lessons = runBlocking { TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable( @@ -79,7 +79,7 @@ class TimetableRepositoryTest { start = LocalDate.of(2019, 3, 5), end = LocalDate.of(2019, 3, 5), forceRefresh = true - ).filter { it.status == Status.SUCCESS }.first().data.orEmpty() + ).filter { it.status == Status.SUCCESS }.first().data?.first.orEmpty() } assertEquals(4, lessons.size) @@ -108,7 +108,7 @@ class TimetableRepositoryTest { ) runBlocking { timetableLocal.saveTimetable(list) } - coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf( + coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns (listOf( createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true), createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false), @@ -123,7 +123,7 @@ class TimetableRepositoryTest { createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true), createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false), createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) - ) + ) to emptyList()) val lessons = runBlocking { TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable( @@ -132,7 +132,7 @@ class TimetableRepositoryTest { start = LocalDate.of(2019, 12, 23), end = LocalDate.of(2019, 12, 25), forceRefresh = true - ).filter { it.status == Status.SUCCESS }.first().data.orEmpty() + ).filter { it.status == Status.SUCCESS }.first().data?.first.orEmpty() } assertEquals(12, lessons.size) diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index e8a3fa48e..b6dfb534b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -162,4 +162,8 @@ internal class RepositoryModule { @Singleton @Provides fun provideConferenceDao(database: AppDatabase) = database.conferenceDao + + @Singleton + @Provides + fun provideTimetableAdditionalDao(database: AppDatabase) = database.timetableAdditionalDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 57160a2bf..6b2301fc7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -30,6 +30,7 @@ import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.dao.TeacherDao +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.AttendanceSummary @@ -55,6 +56,7 @@ import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.migrations.Migration10 import io.github.wulkanowy.data.db.migrations.Migration11 import io.github.wulkanowy.data.db.migrations.Migration12 @@ -77,6 +79,7 @@ import io.github.wulkanowy.data.db.migrations.Migration27 import io.github.wulkanowy.data.db.migrations.Migration28 import io.github.wulkanowy.data.db.migrations.Migration29 import io.github.wulkanowy.data.db.migrations.Migration3 +import io.github.wulkanowy.data.db.migrations.Migration30 import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration6 @@ -112,6 +115,7 @@ import javax.inject.Singleton Teacher::class, School::class, Conference::class, + TimetableAdditional::class, ], version = AppDatabase.VERSION_SCHEMA, exportSchema = true @@ -120,7 +124,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 29 + const val VERSION_SCHEMA = 30 fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array { return arrayOf( @@ -151,7 +155,8 @@ abstract class AppDatabase : RoomDatabase() { Migration26(), Migration27(), Migration28(), - Migration29() + Migration29(), + Migration30(), ) } @@ -212,4 +217,6 @@ abstract class AppDatabase : RoomDatabase() { abstract val schoolDao: SchoolDao abstract val conferenceDao: ConferenceDao + + abstract val timetableAdditionalDao: TimetableAdditionalDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt new file mode 100644 index 000000000..335e003e1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableAdditionalDao.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate +import javax.inject.Singleton + +@Dao +@Singleton +interface TimetableAdditionalDao : BaseDao { + + @Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt new file mode 100644 index 000000000..c1f1365f9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/TimetableAdditional.kt @@ -0,0 +1,30 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.LocalDate +import java.time.LocalDateTime + +@Entity(tableName = "TimetableAdditional") +data class TimetableAdditional( + + @ColumnInfo(name = "student_id") + val studentId: Int, + + @ColumnInfo(name = "diary_id") + val diaryId: Int, + + val start: LocalDateTime, + + val end: LocalDateTime, + + val date: LocalDate, + + val subject: String, +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt new file mode 100644 index 000000000..b33914fec --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration30.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration30 : Migration(29, 30) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL(""" + CREATE TABLE TimetableAdditional ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + student_id INTEGER NOT NULL, + diary_id INTEGER NOT NULL, + start INTEGER NOT NULL, + `end` INTEGER NOT NULL, + date INTEGER NOT NULL, + subject TEXT NOT NULL + ) + """) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt index 8cbbfdfb0..8c9fe3089 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt @@ -21,11 +21,15 @@ class GradeRepository @Inject constructor( fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh }, - query = { local.getGradesDetails(semester).combine(local.getGradesSummary(semester)) { details, summaries -> details to summaries } }, + query = { + local.getGradesDetails(semester).combine(local.getGradesSummary(semester)) { details, summaries -> + details to summaries + } + }, fetch = { remote.getGrades(student, semester) }, - saveFetchResult = { old, new -> - refreshGradeDetails(student, old.first, new.first, notify) - refreshGradeSummaries(old.second, new.second, notify) + saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) -> + refreshGradeDetails(student, oldDetails, newDetails, notify) + refreshGradeSummaries(oldSummary, newSummary, notify) } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt index df4bfb20a..6e211d063 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt @@ -1,25 +1,42 @@ package io.github.wulkanowy.data.repositories.timetable +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional import kotlinx.coroutines.flow.Flow import java.time.LocalDate import javax.inject.Inject import javax.inject.Singleton @Singleton -class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) { +class TimetableLocal @Inject constructor( + private val timetableDb: TimetableDao, + private val timetableAdditionalDb: TimetableAdditionalDao +) { suspend fun saveTimetable(timetables: List) { timetableDb.insertAll(timetables) } + suspend fun saveTimetableAdditional(additional: List) { + timetableAdditionalDb.insertAll(additional) + } + suspend fun deleteTimetable(timetables: List) { timetableDb.deleteAll(timetables) } + suspend fun deleteTimetableAdditional(additional: List) { + timetableAdditionalDb.deleteAll(additional) + } + fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) } + + fun getTimetableAdditional(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { + return timetableAdditionalDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt index 9fb80e77d..cfc700aed 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.timetable import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.init import java.time.LocalDate @@ -12,29 +13,40 @@ import javax.inject.Singleton @Singleton class TimetableRemote @Inject constructor(private val sdk: Sdk) { - suspend fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getTimetable(startDate, endDate).first - .map { - Timetable( - studentId = semester.studentId, - diaryId = semester.diaryId, - number = it.number, - start = it.start, - end = it.end, - date = it.date, - subject = it.subject, - subjectOld = it.subjectOld, - group = it.group, - room = it.room, - roomOld = it.roomOld, - teacher = it.teacher, - teacherOld = it.teacherOld, - info = it.info, - isStudentPlan = it.studentPlan, - changes = it.changes, - canceled = it.canceled - ) - } + suspend fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Pair, List> { + val (normal, additional) = sdk.init(student) + .switchDiary(semester.diaryId, semester.schoolYear) + .getTimetable(startDate, endDate) + + return normal.map { + Timetable( + studentId = semester.studentId, + diaryId = semester.diaryId, + number = it.number, + start = it.start, + end = it.end, + date = it.date, + subject = it.subject, + subjectOld = it.subjectOld, + group = it.group, + room = it.room, + roomOld = it.roomOld, + teacher = it.teacher, + teacherOld = it.teacherOld, + info = it.info, + isStudentPlan = it.studentPlan, + changes = it.changes, + canceled = it.canceled + ) + } to additional.map { + TimetableAdditional( + studentId = semester.studentId, + diaryId = semester.diaryId, + subject = it.subject, + date = it.date, + start = it.start, + end = it.end + ) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt index ee2734aaf..6e98ed7ce 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt @@ -2,11 +2,14 @@ package io.github.wulkanowy.data.repositories.timetable import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import java.time.LocalDate import javax.inject.Inject @@ -19,23 +22,44 @@ class TimetableRepository @Inject constructor( private val schedulerHelper: TimetableNotificationSchedulerHelper ) { - fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getTimetable(semester, start.monday, end.sunday).map { schedulerHelper.scheduleNotifications(it, student); it } }, - fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) }, - saveFetchResult = { old, new -> - local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) - local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> - item.also { new -> - old.singleOrNull { new.start == it.start }?.let { old -> - return@map new.copy( - room = if (new.room.isEmpty()) old.room else new.room, - teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher - ) - } + fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource( + shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh }, + query = { + local.getTimetable(semester, start.monday, end.sunday) + .map { schedulerHelper.scheduleNotifications(it, student); it } + .combine(local.getTimetableAdditional(semester, start.monday, end.sunday)) { timetable, additional -> + timetable to additional } - }) }, - filterResult = { it.filter { item -> item.date in start..end } } + fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) }, + saveFetchResult = { (oldTimetable, oldAdditional), (newTimetable, newAdditional) -> + refreshTimetable(student, oldTimetable, newTimetable) + refreshAdditional(oldAdditional, newAdditional) + + }, + filterResult = { (timetable, additional) -> + timetable.filter { item -> + item.date in start..end + } to additional.filter { item -> item.date in start..end } + } ) + + private suspend fun refreshTimetable(student: Student, old: List, new: List) { + local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) + local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> + item.also { new -> + old.singleOrNull { new.start == it.start }?.let { old -> + return@map new.copy( + room = if (new.room.isEmpty()) old.room else new.room, + teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher + ) + } + } + }) + } + + private suspend fun refreshAdditional(old: List, new: List) { + local.deleteTimetableAdditional(old.uniqueSubtract(new)) + local.saveTimetableAdditional(new.uniqueSubtract(old)) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index 4aa375c21..a2141d865 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -16,6 +16,7 @@ import io.github.wulkanowy.databinding.FragmentTimetableBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.timetable.additional.AdditionalLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.ui.widgets.DividerItemDecoration import io.github.wulkanowy.utils.SchooldaysRangeLimiter @@ -84,8 +85,11 @@ class TimetableFragment : BaseFragment(R.layout.fragme } override fun onOptionsItemSelected(item: MenuItem): Boolean { - return if (item.itemId == R.id.timetableMenuCompletedLessons) presenter.onCompletedLessonsSwitchSelected() - else false + return when (item.itemId) { + R.id.timetableMenuAdditionalLessons -> presenter.onAdditionalLessonsSwitchSelected() + R.id.timetableMenuCompletedLessons -> presenter.onCompletedLessonsSwitchSelected() + else -> false + } } override fun updateData(data: List, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean) { @@ -176,6 +180,10 @@ class TimetableFragment : BaseFragment(R.layout.fragme } } + override fun openAdditionalLessonsView() { + (activity as? MainActivity)?.pushView(AdditionalLessonsFragment.newInstance()) + } + override fun openCompletedLessonsView() { (activity as? MainActivity)?.pushView(CompletedLessonsFragment.newInstance()) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 0e913acf6..c1361fd40 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -109,6 +109,11 @@ class TimetablePresenter @Inject constructor( view?.showTimetableDialog(lesson) } + fun onAdditionalLessonsSwitchSelected(): Boolean { + view?.openAdditionalLessonsView() + return true + } + fun onCompletedLessonsSwitchSelected(): Boolean { view?.openCompletedLessonsView() return true @@ -144,18 +149,18 @@ class TimetablePresenter @Inject constructor( showWholeClassPlanType = prefRepository.showWholeClassPlan, showGroupsInPlanType = prefRepository.showGroupsInPlan, showTimetableTimers = prefRepository.showTimetableTimers, - data = it.data!! + data = it.data!!.first .filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true } .sortedWith(compareBy({ item -> item.number }, { item -> !item.isStudentPlan })) ) - showEmpty(it.data.isEmpty()) + showEmpty(it.data.first.isEmpty()) showErrorView(false) - showContent(it.data.isNotEmpty()) + showContent(it.data.first.isNotEmpty()) } analytics.logEvent( "load_data", "type" to "timetable", - "items" to it.data!!.size + "items" to it.data!!.first.size ) } Status.ERROR -> { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt index 244120176..30135453a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableView.kt @@ -44,5 +44,7 @@ interface TimetableView : BaseView { fun popView() + fun openAdditionalLessonsView() + fun openCompletedLessonsView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt new file mode 100644 index 000000000..fdc8b8874 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsAdapter.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.databinding.ItemTimetableAdditionalBinding +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class AdditionalLessonsAdapter @Inject constructor() : + RecyclerView.Adapter() { + + var items = emptyList() + + override fun getItemCount() = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + ItemTimetableAdditionalBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + val item = items[position] + + with(holder.binding) { + additionalLessonItemTime.text = "${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}" + additionalLessonItemSubject.text = item.subject + } + } + + class ItemViewHolder(val binding: ItemTimetableAdditionalBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt new file mode 100644 index 000000000..17a1cabeb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -0,0 +1,144 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.wdullaer.materialdatetimepicker.date.DatePickerDialog +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.databinding.FragmentTimetableAdditionalBinding +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.widgets.DividerItemDecoration +import io.github.wulkanowy.utils.SchooldaysRangeLimiter +import io.github.wulkanowy.utils.dpToPx +import java.time.LocalDate +import javax.inject.Inject + +@AndroidEntryPoint +class AdditionalLessonsFragment : + BaseFragment(R.layout.fragment_timetable_additional), + AdditionalLessonsView, MainView.TitledView { + + @Inject + lateinit var presenter: AdditionalLessonsPresenter + + @Inject + lateinit var additionalLessonsAdapter: AdditionalLessonsAdapter + + companion object { + private const val SAVED_DATE_KEY = "CURRENT_DATE" + + fun newInstance() = AdditionalLessonsFragment() + } + + override val titleStringId get() = R.string.additional_lessons_title + + override val isViewEmpty get() = additionalLessonsAdapter.items.isEmpty() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTimetableAdditionalBinding.bind(view) + messageContainer = binding.additionalLessonsRecycler + presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) + } + + override fun initView() { + with(binding.additionalLessonsRecycler) { + layoutManager = LinearLayoutManager(context) + adapter = additionalLessonsAdapter + addItemDecoration(DividerItemDecoration(context)) + } + + with(binding) { + additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh) + additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() } + + additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() } + additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() } + additionalLessonsNextButton.setOnClickListener { presenter.onNextDay() } + + additionalLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f)) + } + } + + override fun updateData(data: List) { + with(additionalLessonsAdapter) { + items = data + notifyDataSetChanged() + } + } + + override fun clearData() { + with(additionalLessonsAdapter) { + items = emptyList() + notifyDataSetChanged() + } + } + + override fun updateNavigationDay(date: String) { + binding.additionalLessonsNavDate.text = date + } + + override fun hideRefresh() { + binding.additionalLessonsSwipe.isRefreshing = false + } + + override fun showEmpty(show: Boolean) { + binding.additionalLessonsEmpty.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showErrorView(show: Boolean) { + binding.additionalLessonsError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.additionalLessonsErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.additionalLessonsProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun enableSwipe(enable: Boolean) { + binding.additionalLessonsSwipe.isEnabled = enable + } + + override fun showContent(show: Boolean) { + binding.additionalLessonsRecycler.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showPreButton(show: Boolean) { + binding.additionalLessonsPreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE + } + + override fun showNextButton(show: Boolean) { + binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE + } + + override fun showDatePickerDialog(currentDate: LocalDate) { + val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth -> + presenter.onDateSet(year, month + 1, dayOfMonth) + } + val datePickerDialog = DatePickerDialog.newInstance(dateSetListener, + currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth) + + with(datePickerDialog) { + setDateRangeLimiter(SchooldaysRangeLimiter()) + version = DatePickerDialog.Version.VERSION_2 + scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL + show(this@AdditionalLessonsFragment.parentFragmentManager, null) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) + } + + override fun onDestroyView() { + presenter.onDetachView() + super.onDestroyView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt new file mode 100644 index 000000000..0de207a57 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt @@ -0,0 +1,166 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import android.annotation.SuppressLint +import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.repositories.semester.SemesterRepository +import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.AnalyticsHelper +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResourceIn +import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday +import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.nextOrSameSchoolDay +import io.github.wulkanowy.utils.nextSchoolDay +import io.github.wulkanowy.utils.previousSchoolDay +import io.github.wulkanowy.utils.toFormattedString +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import java.time.LocalDate +import javax.inject.Inject + +class AdditionalLessonsPresenter @Inject constructor( + studentRepository: StudentRepository, + errorHandler: ErrorHandler, + private val semesterRepository: SemesterRepository, + private val timetableRepository: TimetableRepository, + private val analytics: AnalyticsHelper +) : BasePresenter(errorHandler, studentRepository) { + + private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay + + lateinit var currentDate: LocalDate + private set + + private lateinit var lastError: Throwable + + fun onAttachView(view: AdditionalLessonsView, date: Long?) { + super.onAttachView(view) + view.initView() + Timber.i("Additional lessons was initialized") + errorHandler.showErrorMessage = ::showErrorViewOnError + loadData(LocalDate.ofEpochDay(date ?: baseDate.toEpochDay())) + if (currentDate.isHolidays) setBaseDateOnHolidays() + reloadView() + } + + fun onPreviousDay() { + loadData(currentDate.previousSchoolDay) + reloadView() + } + + fun onNextDay() { + loadData(currentDate.nextSchoolDay) + reloadView() + } + + fun onPickDate() { + view?.showDatePickerDialog(currentDate) + } + + fun onDateSet(year: Int, month: Int, day: Int) { + loadData(LocalDate.of(year, month, day)) + reloadView() + } + + fun onSwipeRefresh() { + Timber.i("Force refreshing the additional lessons") + loadData(currentDate, true) + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(currentDate, true) + } + + private fun setBaseDateOnHolidays() { + flow { + val student = studentRepository.getCurrentStudent() + emit(semesterRepository.getCurrentSemester(student)) + }.catch { + Timber.i("Loading semester result: An exception occurred") + }.onEach { + baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear) + currentDate = baseDate + reloadNavigation() + }.launch("holidays") + } + + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + currentDate = date + + flowWithResourceIn { + val student = studentRepository.getCurrentStudent() + val semester = semesterRepository.getCurrentSemester(student) + timetableRepository.getTimetable(student, semester, date, date, forceRefresh, true) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Loading additional lessons data started") + Status.SUCCESS -> { + Timber.i("Loading additional lessons lessons result: Success") + view?.apply { + updateData(it.data!!.second.sortedBy { item -> item.date }) + showEmpty(it.data.second.isEmpty()) + showErrorView(false) + showContent(it.data.second.isNotEmpty()) + } + analytics.logEvent( + "load_data", + "type" to "additional_lessons", + "items" to it.data!!.second.size + ) + } + Status.ERROR -> { + Timber.i("Loading additional lessons result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + }.afterLoading { + view?.run { + hideRefresh() + showProgress(false) + enableSwipe(true) + } + }.launch() + } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + if (isViewEmpty) { + lastError = error + setErrorDetails(message) + showErrorView(true) + showEmpty(false) + } else showError(message, error) + } + } + + private fun reloadView() { + Timber.i("Reload additional lessons view with the date ${currentDate.toFormattedString()}") + view?.apply { + showProgress(true) + enableSwipe(false) + showContent(false) + showEmpty(false) + showErrorView(false) + clearData() + reloadNavigation() + } + } + + @SuppressLint("DefaultLocale") + private fun reloadNavigation() { + view?.apply { + showPreButton(!currentDate.minusDays(1).isHolidays) + showNextButton(!currentDate.plusDays(1).isHolidays) + updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize()) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt new file mode 100644 index 000000000..97eb2ae7b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsView.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.ui.modules.timetable.additional + +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.ui.base.BaseView +import java.time.LocalDate + +interface AdditionalLessonsView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun updateData(data: List) + + fun clearData() + + fun updateNavigationDay(date: String) + + fun hideRefresh() + + fun showEmpty(show: Boolean) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun enableSwipe(enable: Boolean) + + fun showContent(show: Boolean) + + fun showPreButton(show: Boolean) + + fun showNextButton(show: Boolean) + + fun showDatePickerDialog(currentDate: LocalDate) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index c7b4d4a84..a21b5507e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -107,7 +107,7 @@ class TimetableWidgetFactory( val semester = semesterRepository.getCurrentSemester(student) timetableRepository.getTimetable(student, semester, date, date, false) - .toFirstResult().data.orEmpty() + .toFirstResult().data?.first.orEmpty() .sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) .filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } } diff --git a/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml b/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml new file mode 100644 index 000000000..169395e91 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_timetable_lessons_additional.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_timetable_additional.xml b/app/src/main/res/layout/fragment_timetable_additional.xml new file mode 100644 index 000000000..d70306c9d --- /dev/null +++ b/app/src/main/res/layout/fragment_timetable_additional.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_timetable_additional.xml b/app/src/main/res/layout/item_timetable_additional.xml new file mode 100644 index 000000000..cc39db5d4 --- /dev/null +++ b/app/src/main/res/layout/item_timetable_additional.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/app/src/main/res/menu/action_menu_timetable.xml b/app/src/main/res/menu/action_menu_timetable.xml index c3d0222c1..47257f5b4 100644 --- a/app/src/main/res/menu/action_menu_timetable.xml +++ b/app/src/main/res/menu/action_menu_timetable.xml @@ -1,6 +1,13 @@

+ Resources + + Additional lessons + Show additional lessons + No info about additional lessons + + Attendance summary Absent for school reasons diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt index a333e0d7a..88ecf711d 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt @@ -56,7 +56,7 @@ class TimetableRemoteTest { of(2018, 9, 15) ) } - assertEquals(2, timetable.size) + assertEquals(2, timetable.first.size) } private fun getTimetable(date: LocalDate): Timetable { From 7fe638130e26eea39e8f092e9f30d1a2a45b7682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 27 Dec 2020 18:30:50 +0100 Subject: [PATCH 052/110] Configure github actions (#1055) * Create test.yml * Change list of emulator api levels * Downgrade emulator with api 30 to 29 * Exclude jdk.internal in jacoco config * Use jdk 15 * Downgrade jdk to 11 due to jacoco incompatibility gradle/gradle#15038 * Fix tests on jdk11 * Add codecov * Add missing jacoco report generaction command * Add flags to codecov uploads * Add deploy config * Replace travis badge with gh actions badge * Add info about coroutines to readme, replace dagger with hilt * Decrease instrumentation tests to 15 minutes * Skip duplicate actions * Remove comment * Change os of instrumentation tests * Downgrade android emulator runner to v2.13.0 * Add pre-build job * Pass prebuild files between jobs * Fix gh actions yaml config * Tar build cache * Fix upload-artifact filename * Fix prebuild cache name * Add more to cache, change cache key --- .github/workflows/test.yml | 141 ++++++++++++++++++ README.en.md | 6 +- README.md | 8 +- app/jacoco.gradle | 1 + .../wulkanowy/utils/TimeExtensionTest.kt | 4 +- 5 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..d78c5099b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,141 @@ +name: Test and deploy + +on: + push: + branches: [ develop ] + tags: [ '*' ] + pull_request: + branches: [ develop ] + + workflow_dispatch: + +jobs: + build: + name: Pre-build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: fkirc/skip-duplicate-actions@master + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 + - uses: actions/setup-java@v1 + with: + java-version: 11 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} + - name: Build + run: ./gradlew compileFdroidDebugUnitTestKotlin packageFdroidDebug preFdroidDebugAndroidTestBuild + - name: Prepare build cache + run: tar -cvf prebuild.tar .gradle app/build + - uses: actions/upload-artifact@v2 + with: + name: prebuild.tar + path: prebuild.tar + + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [ build ] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 11 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} + - uses: actions/download-artifact@v2 + with: + name: prebuild.tar + - name: Extract build cache + run: tar -xvf prebuild.tar app/build + - name: Unit tests + run: | + ./gradlew -Pcoverage testFdroidDebugUnitTest --stacktrace + ./gradlew -Pcoverage jacocoTestReport --stacktrace + - uses: codecov/codecov-action@v1 + with: + flags: unit + + instrumentation-tests: + name: Instrumentation tests + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [ build ] + strategy: + fail-fast: true + matrix: + api-level: [21, 29] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 11 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} + - uses: actions/download-artifact@v2 + with: + name: prebuild.tar + - name: Extract build cache + run: tar -xvf prebuild.tar app/build + - name: Instrumentation tests + uses: reactivecircus/android-emulator-runner@v2.13.0 + with: + api-level: ${{ matrix.api-level }} + arch: x86 + script: | + ./gradlew -Pcoverage connectedFdroidDebugAndroidTest --stacktrace + ./gradlew -Pcoverage jacocoTestReport --stacktrace + - uses: codecov/codecov-action@v1 + with: + flags: instrumented,api-${{ matrix.api-level }} + + deploy-google-play: + name: Deploy to google play + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: google-play + needs: [ build, unit-tests, instrumentation-tests ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 11 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} + - uses: actions/download-artifact@v2 + with: + name: prebuild.tar + - name: Extract build cache + run: tar -xvf prebuild.tar app/build + - name: Decrypt keys + env: + ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }} + SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }} + run: | + gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg + gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg + gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg + - name: Upload apk to google play + env: + PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }} + PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }} + PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }} + run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace; diff --git a/README.en.md b/README.en.md index 4c5e53da8..accc36082 100644 --- a/README.en.md +++ b/README.en.md @@ -1,7 +1,8 @@ [Polska wersja README](README.md) # Wulkanowy -[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Test%20and%20deploy/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) @@ -50,7 +51,8 @@ You can also download a [development version](https://wulkanowy.github.io/#downl * [Wulkanowy SDK](https://github.com/wulkanowy/sdk) -* [Dagger 2](https://github.com/google/dagger) +* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) +* [Hilt](https://dagger.dev/hilt/) * [Room](https://developer.android.com/topic/libraries/architecture/room) * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) diff --git a/README.md b/README.md index 9e29cdb6c..ca3b55904 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ [English version of README](README.en.md) # Wulkanowy -[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Test%20and%20deploy/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) @@ -50,8 +51,9 @@ Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#downloa ## Zbudowana za pomocą -* [Wulkanowy SDK](https://github.com/wulkanowy/SDK) -* [Dagger 2](https://github.com/google/dagger) +* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) +* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) +* [Hilt](https://dagger.dev/hilt/) * [Room](https://developer.android.com/topic/libraries/architecture/room) * [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) diff --git a/app/jacoco.gradle b/app/jacoco.gradle index a5cf84e63..94469fbc0 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -7,6 +7,7 @@ jacoco { tasks.withType(Test) { jacoco.includeNoLocationClasses = true + jacoco.excludes = ['jdk.internal.*'] } task jacocoTestReport(type: JacocoReport) { diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt index d604dfef4..3336c0d9d 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt @@ -50,7 +50,7 @@ class TimeExtensionTest { fun monthNameTest() { Locale.setDefault(Locale.forLanguageTag("PL")) assertEquals("Styczeń", JANUARY.getFormattedName()) - Locale.setDefault(Locale.forLanguageTag("US")) + Locale.setDefault(Locale.forLanguageTag("EN")) assertEquals("January", JANUARY.getFormattedName()) } @@ -58,7 +58,7 @@ class TimeExtensionTest { fun weekDayNameTest() { Locale.setDefault(Locale.forLanguageTag("PL")) assertEquals("poniedziałek", of(2018, 10, 1).weekDayName) - Locale.setDefault(Locale.forLanguageTag("US")) + Locale.setDefault(Locale.forLanguageTag("EN")) assertEquals("Monday", of(2018, 10, 1).weekDayName) } From 5a2a0e3d6d0b0b209a507458bf9a220ead59b61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 28 Dec 2020 22:45:21 +0100 Subject: [PATCH 053/110] Github workflow config improvements (#1056) --- .github/workflows/test.yml | 20 ++++++++++---------- .gitignore | 1 + settings.gradle | 7 +++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d78c5099b..426f22be1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,9 +28,9 @@ jobs: ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} - name: Build - run: ./gradlew compileFdroidDebugUnitTestKotlin packageFdroidDebug preFdroidDebugAndroidTestBuild + run: ./gradlew --build-cache compileFdroidDebugUnitTestKotlin preFdroidDebugAndroidTestBuild dexBuilderFdroidDebugAndroidTest packageFdroidDebug packageFdroidDebugAndroidTest - name: Prepare build cache - run: tar -cvf prebuild.tar .gradle app/build + run: tar -cf prebuild.tar .build-cache .gradle app/build - uses: actions/upload-artifact@v2 with: name: prebuild.tar @@ -56,18 +56,18 @@ jobs: with: name: prebuild.tar - name: Extract build cache - run: tar -xvf prebuild.tar app/build + run: tar -xf prebuild.tar - name: Unit tests run: | - ./gradlew -Pcoverage testFdroidDebugUnitTest --stacktrace - ./gradlew -Pcoverage jacocoTestReport --stacktrace + ./gradlew --build-cache -Pcoverage testFdroidDebugUnitTest --stacktrace + ./gradlew --build-cache -Pcoverage jacocoTestReport --stacktrace - uses: codecov/codecov-action@v1 with: flags: unit instrumentation-tests: name: Instrumentation tests - runs-on: ubuntu-latest + runs-on: macOS-latest timeout-minutes: 15 needs: [ build ] strategy: @@ -89,15 +89,15 @@ jobs: with: name: prebuild.tar - name: Extract build cache - run: tar -xvf prebuild.tar app/build + run: tar -xf prebuild.tar - name: Instrumentation tests uses: reactivecircus/android-emulator-runner@v2.13.0 with: api-level: ${{ matrix.api-level }} arch: x86 script: | - ./gradlew -Pcoverage connectedFdroidDebugAndroidTest --stacktrace - ./gradlew -Pcoverage jacocoTestReport --stacktrace + ./gradlew --build-cache -Pcoverage connectedFdroidDebugAndroidTest --stacktrace + ./gradlew --build-cache -Pcoverage jacocoTestReport --stacktrace - uses: codecov/codecov-action@v1 with: flags: instrumented,api-${{ matrix.api-level }} @@ -124,7 +124,7 @@ jobs: with: name: prebuild.tar - name: Extract build cache - run: tar -xvf prebuild.tar app/build + run: tar -xf prebuild.tar - name: Decrypt keys env: ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }} diff --git a/.gitignore b/.gitignore index 5daeb6b97..5d3321e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ out/ # Gradle files .gradle/ build/ +.build-cache # Local configuration file (sdk path, etc) local.properties diff --git a/settings.gradle b/settings.gradle index e7b4def49..058beb21e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,8 @@ include ':app' + +buildCache { + local { + directory = new File(rootDir, '.build-cache') + removeUnusedEntriesAfterDays = 30 + } +} From b0d713dc0c44d60e7801edcabfb11e21e295f7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 28 Dec 2020 23:34:35 +0100 Subject: [PATCH 054/110] New Crowdin updates (#1053) --- app/src/main/res/values-cs-rCZ/strings.xml | 4 + app/src/main/res/values-de/strings.xml | 4 + app/src/main/res/values-pl/strings.xml | 4 + app/src/main/res/values-ru/strings.xml | 4 + .../res/values-sk-rSK/preferences_values.xml | 52 ++ app/src/main/res/values-sk-rSK/strings.xml | 473 ++++++++++++++++++ app/src/main/res/values-uk/strings.xml | 4 + 7 files changed, 545 insertions(+) create mode 100644 app/src/main/res/values-sk-rSK/preferences_values.xml create mode 100644 app/src/main/res/values-sk-rSK/strings.xml diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 6ddc561d6..19b9bb6e7 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -159,6 +159,10 @@ Téma Nepřítomnost Zdroje + + Další lekce + Zobrazit další lekce + Žádné informace o dalších lekcích Souhrn docházky Nepřítomen ze školních důvodů diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a16b18e56..069644583 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -145,6 +145,10 @@ Thema Abwesenheit Ressourcen + + Additional lessons + Show additional lessons + No info about additional lessons Übersicht über die Schulbesuch Aus schulischen Gründen abwesend diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index dc877eafa..14c93d1a2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -159,6 +159,10 @@ Temat Nieobecność Zasoby + + Dodatkowe lekcje + Pokaż dodatkowe lekcje + Brak informacji o dodatkowych lekcjach Podsumowanie frekwencji Nieobecność z przyczyn szkolnych diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e2620b603..e17124d7d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -159,6 +159,10 @@ Тема Отсутствие Ресурсы + + Additional lessons + Show additional lessons + No info about additional lessons Итоговая посещаемость Отсутствие по школьным причинам diff --git a/app/src/main/res/values-sk-rSK/preferences_values.xml b/app/src/main/res/values-sk-rSK/preferences_values.xml new file mode 100644 index 000000000..d57e603b1 --- /dev/null +++ b/app/src/main/res/values-sk-rSK/preferences_values.xml @@ -0,0 +1,52 @@ + + + + Svetlý + Tmavý + Čierny (AMOLED) + + + Jazyk systému + Polski + English + Pусский + Українська + Deutsch + Čeština + + + 15 minút + 30 minút + 1 hodina + 2 hodiny + 6 hodín + 12 hodín + 24 hodín + + + 0,00 + 0,25 + 0,33 + 0,5 + 0,75 + + + Abecedne + Podľa dátumu + + + Dzienniczek+ + Wulkanowy + Farby známok v denníku + + + Priemer známok až od druhého semestra + Priemer známok z oboch semestrov + Priemer známok z celého roka + + + Neukaz + Zobrazit vše + Zobrazit menší + + diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml new file mode 100644 index 000000000..f5574c9bd --- /dev/null +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -0,0 +1,473 @@ + + + + Prihlásenie + Wulkanowy + Známky + Dochádzka + Skúšky + Plán lekcie + Nastavenia + Viac + O aplikácii + Prehliadač protokolov + Tvorcovia + Licencie + Správy + Nová správa + Poznámky a úspechy + Domáce úlohy + Vyberte účet + + Semester %1$d, %2$d/%3$d + + Prihláste sa pomocou študentského alebo rodičovského konta + Zadajte symbol zo stránky denníka + Užívateľské meno + Email + Prihlásenie, číslo PESEL alebo e-mail + Heslo + Variácie denníka UONET+ + Mobile API + Scraper + Hybridné + Token + PIN + Kľúč API + Symbol + Prihlásiť + Toto heslo je príliš krátke + Prihlasovacie údaje sú nesprávne. Uistite sa, že je v poli nižšie vybraná správna variácie denníka UONET+ + Neplatný PIN + Neplatný token + Platnosť tokenu vypršala + Neplatný e-mail + Namiesto e-mailu použite priradené prihlasovacie údaje + Neplatný symbol + Študent nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ + Toto pole je povinné + Vybraný študent je už prihlásený + The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen. Wulkanowy does not detect pre-school students at the moment + Vyberte studenty, kteří se mají do aplikace přihlásit + Iné možnosti + In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices + This mode displays the same data as it appears on the register website + The combination of the best features of the other two modes. It works faster than scraper and provides features not available in the Mobile API mode. It is in the experimental phase + Zásady ochrany osobných údajov + Problémy s prihlásením? Napíšte nám! + Email + Discord + Poslať e-mail + Opíšte podrobnosti problému: + Uistite sa, že ste vybrali správny variant denníka UONET+! + Zabudol som heslo + Obnovte svoj účet + Obnoviť + Študent je už prihlásený + + Manažér účtov + Prihlásiť sa + Platnosť relácie vypršala + Vaša relácia vypršala, prihláste sa prosím znovu + + Známka + Semester %d + Zmeniť semester + Žiadne známky + Váha + Váha: %s + Komentár + Žiadne nové známky + Number of new ratings: %1$d + Priemer: %1$.2f + Body: %s + Bez priemeru + Predpovedané: %1$s + Konečná: %1$s + Celkom bodov + Konečná známka + Predpokladaná známka + Vypočítaný priemer + Konečný priemer + Súhrn + Trieda + Označiť ako prečítané + Čiastočne + Semester + Body + + %d známka + %d známky + %d známok + %d známok + + + Nová známka + Nové známky + Nové známky + Nové známky + + + Nová predpokladaná známka + Nové predpovedanej známky + Nové predpovedanej známky + Nové predpovedanej známky + + + Nová konečná známka + Nové konečnej známky + Nové konečnej známky + Nové konečnej známky + + + Máte %1$d novú známku + Máte %1$d nové známky + Máte %1$d nové známky + Máte %1$d nové známky + + + Máte %1$d novú predpokladanú známku + Máte %1$d nové predpokladané známky + Máte %1$d nové predpokladané známky + Máte %1$d nové predpokladané známky + + + Máte %1$d novú konečnú známku + You received %1$d final grades + You received %1$d final grades + You received %1$d final grades + + + Lekcia + Room + Skupina + Hodiny + Zmeny + Žiadne lekcie tento deň + %s min + %s sec + %1$s left + in %1$s + Lekcia skončila + Now: %s + Next: %s + Later: %s + + Completed lessons + Show completed lessons + No info about completed lessons + Téma + Neprítomnosť + Resources + + Additional lessons + Show additional lessons + No info about additional lessons + + Attendance summary + Absent for school reasons + Excused absence + Unexcused absence + Exemption + Excused lateness + Unexcused lateness + Present + Deleted + Unknown + Number of lesson + No entries + + %1$d absence + %1$d absences + %1$d absences + %1$d absences + + Absence reason (optional) + Send + Absence excused successfully! + You must select at least one absence! + Excuse + + Attendance + Total + + No exams this week + Type + Entry date + + Inbox + Sent + Trash + (no subject) + No messages + An error occurred while downloading message content + From: + To: + Date: %s + Reply + Forward + Delete + Move to trash + Delete permanently + Message deleted successfully + Share + Print + Subject + Content + Message sent successfully + You need to choose at least 1 recipient + The message content must be at least 3 characters + + %d message + %d messages + %d messages + %d messages + + + New message + New messages + New messages + New messages + + + You received %1$d message + You received %1$d messages + You received %1$d messages + You received %1$d messages + + + No info about notes + Points + + %d note + %d notes + %d notes + %d notes + + + New note + New notes + New notes + New notes + + + You received %1$d note + You received %1$d notes + You received %1$d notes + You received %1$d notes + + + + %d praise + %d praises + %d praises + %d praises + + + New praise + New praises + New praises + New praises + + + You received %1$d praise + You received %1$d praises + You received %1$d praises + You received %1$d praises + + + + %d neutral note + %d neutral notes + %d neutral notes + %d neutral notes + + + New neutral note + New neutral notes + New neutral notes + New neutral notes + + + You received %1$d neutral note + You received %1$d neutral notes + You received %1$d neutral notes + You received %1$d neutral notes + + + No info about homework + Mark as done + Mark as undone + Attachments + + Lucky number + Today\'s lucky number is + No info about the lucky number + Lucky number for today + Today\'s lucky number is: %d + + Mobile devices + No devices + Deregister + Device removed + QR code + Token + Symbol + PIN + + School and teachers + + School + No info about school + School name + School address + Telephone + Name of headmaster + Name of pedagogue + Show on map + Call + + Teachers + No info about teachers + No subject + + Conferences + No info about conferences + + Add account + Logout + Do you want to log out of an active student? + Student logout + Student account + Parent account + Mobile API mode + Hybrid mode + + App version + Contributors + List of Wulkanowy developers + Report a bug + Send a bug report via e-mail + FAQ + Read Frequently Asked Questions + Discord server + Join the Wulkanowy community + Facebook fanpage + Like our facebook fanpage + Privacy policy + Rules for collecting personal data + Homepage + Visit the website and help develop the application + Licenses + Licenses of libraries used in the application + + License + + Avatar + See more on GitHub + + Share logs + Refresh + + Check for updates + Before reporting a bug, check first if an update with the bug fix is available + + Content + Retry + Description + No description + Teacher + Date + Entry date + Color + Details + Category + Close + No data + Subject + Prev + Next + Search + Search… + + No lessons + Choose theme + Light + Dark + System Theme + + Appearance + Default view + Calculation of the end-of-year average + Force average calculation by app + Show presence in attendance + Application theme + Expand grades + Mark current lesson in timetable + Show groups next to subjects in timetable + Show chart list in class grades + Show whole class lessons + Show subjects without grades in Grades + Grades color scheme + Subjects sorting in \"Grades\" + App language + Notifications + Show notifications + Show upcoming lesson notifications + Fix synchronization & notifications issues + Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings. + Go to settings + Show debug notifications + Synchronization + Automatic update + Suspended on holidays + Updates interval + Wi-Fi only + Sync now + Synced! + Sync failed + Sync in progress + Synchronization + Manual sync doesn\'t refresh app views. + \nTo see the synced data relaunch the app after syncing. + + Other + Value of the plus + Value of the minus + Reply with message history + + New entries in register + New grades + Lucky number + New messages + New notes + Push notifications + Upcoming lessons + Debug + + Black + Red + Blue + Green + Purple + No color + + Copied + Undo + + Download of updates has started… + An update has just been downloaded. + Restart + Update failed! Wulkanowy may not function properly. Consider updating + + No internet connection + Connection to register failed. Servers can be overloaded. Please try again later + Loading data failed. Please try again later + Register password change required + Maintenance underway UONET + register. Try again later + Unknown UONET + register error. Try again later + Unknown application error. Please try again later + An unexpected error occurred + Feature disabled by your school + Feature not available. Login in a mode other than Mobile API + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index cc26b24fb..a911405f7 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -159,6 +159,10 @@ Тема Відсутність Ресурси + + Additional lessons + Show additional lessons + No info about additional lessons Підсумок відвідуваності Відсутність зі шкільних причин From a2c4f4a51de0c8e1b6f964e8e326b0e8a97f3be8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 2 Jan 2021 12:49:57 +0000 Subject: [PATCH 055/110] Bump mockk from 1.10.3-jdk8 to 1.10.4 (#1059) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e77d03c97..475937743 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,7 +133,7 @@ ext { work_manager = "2.4.0" room = "2.2.5" chucker = "3.4.0" - mockk = "1.10.3-jdk8" + mockk = "1.10.4" moshi = "1.11.0" } From fb36fb379a077db3be36fe50d2ce3ebc3a82dfa6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 3 Jan 2021 22:57:41 +0000 Subject: [PATCH 056/110] Bump room from 2.2.5 to 2.2.6 (#1058) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 475937743..4e6bdda9d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -131,7 +131,7 @@ play { ext { work_manager = "2.4.0" - room = "2.2.5" + room = "2.2.6" chucker = "3.4.0" mockk = "1.10.4" moshi = "1.11.0" From bee62d476923b8095f5c8308f57f02828af02e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 4 Jan 2021 00:13:50 +0100 Subject: [PATCH 057/110] Remove remote and local repositories (#1065) * Remove remote and local repos * Move repositories out of sub-packages * Update chucker config * Move repositories tests from androidTest to unit tests * Rewrite grades tests * Fix more tests * Update grade statistics tests --- app/build.gradle | 2 +- .../data/repositories/TestEntityCreator.kt | 45 ---- .../attendance/AttendanceLocalTest.kt | 83 ------ .../CompletedLessonsLocalTest.kt | 59 ---- .../data/repositories/exam/ExamLocalTest.kt | 52 ---- .../data/repositories/grade/GradeLocalTest.kt | 55 ---- .../repositories/grade/GradeRepositoryTest.kt | 221 --------------- .../grade/TestGradeEntityCreator.kt | 41 --- .../GradeStatisticsLocalTest.kt | 110 -------- .../luckynumber/LuckyNumberLocalTest.kt | 51 ---- .../recipient/RecipientLocalTest.kt | 65 ----- .../repositories/student/StudentLocalTest.kt | 46 ---- .../timetable/TestTimetableEntityCreator.kt | 48 ---- .../timetable/TimetableLocalTest.kt | 59 ---- .../timetable/TimetableRepositoryTest.kt | 155 ----------- .../java/io/github/wulkanowy/WulkanowyApp.kt | 2 +- .../github/wulkanowy/data/RepositoryModule.kt | 12 +- .../wulkanowy/data/db/dao/RecipientDao.kt | 2 +- .../message => enums}/MessageFolder.kt | 2 +- .../attendance => enums}/SentExcuseStatus.kt | 2 +- .../data/mappers/AttendanceMapper.kt | 43 +++ .../data/mappers/CompletedLessonsMapper.kt | 21 ++ .../data/mappers/ConferenceMapper.kt | 18 ++ .../wulkanowy/data/mappers/ExamMapper.kt | 20 ++ .../wulkanowy/data/mappers/GradeMapper.kt | 42 +++ .../data/mappers/GradeStatisticsMapper.kt | 80 ++++++ .../wulkanowy/data/mappers/HomeworkMapper.kt | 21 ++ .../data/mappers/LuckyNumberMapper.kt | 12 + .../wulkanowy/data/mappers/MessageMapper.kt | 53 ++++ .../data/mappers/MobileDeviceMapper.kt | 23 ++ .../wulkanowy/data/mappers/NoteMapper.kt | 19 ++ .../wulkanowy/data/mappers/RecipientMapper.kt | 18 ++ .../data/mappers/ReportingUnitMapper.kt | 16 ++ .../data/mappers/SchoolInfoMapper.kt | 15 ++ .../wulkanowy/data/mappers/SubjectMapper.kt | 14 + .../wulkanowy/data/mappers/TeacherMapper.kt | 15 ++ .../wulkanowy/data/mappers/TimetableMapper.kt | 41 +++ .../{appcreator => }/AppCreatorRepository.kt | 2 +- .../data/repositories/AttendanceRepository.kt | 50 ++++ .../AttendanceSummaryRepository.kt | 33 +++ .../CompletedLessonsRepository.kt | 22 +- .../data/repositories/ConferenceRepository.kt | 33 +++ .../repositories/{exam => }/ExamRepository.kt | 22 +- .../{grade => }/GradeRepository.kt | 44 +-- .../GradeStatisticsRepository.kt | 86 +++--- .../{homework => }/HomeworkRepository.kt | 24 +- .../{logger => }/LoggerRepository.kt | 2 +- .../repositories/LuckyNumberRepository.kt | 41 +++ .../{message => }/MessageRepository.kt | 55 ++-- .../repositories/MobileDeviceRepository.kt | 49 ++++ .../repositories/{note => }/NoteRepository.kt | 28 +- .../PreferencesRepository.kt | 2 +- .../data/repositories/RecipientRepository.kt | 40 +++ .../RecoverRemote.kt => RecoverRepository.kt} | 5 +- .../repositories/ReportingUnitRepository.kt | 42 +++ .../data/repositories/SchoolRepository.kt | 31 +++ .../{semester => }/SemesterRepository.kt | 21 +- .../data/repositories/StudentRepository.kt | 85 ++++++ .../data/repositories/SubjectRepository.kt | 32 +++ .../data/repositories/TeacherRepository.kt | 33 +++ .../{timetable => }/TimetableRepository.kt | 35 ++- .../attendance/AttendanceLocal.kt | 25 -- .../attendance/AttendanceRemote.kt | 50 ---- .../attendance/AttendanceRepository.kt | 34 --- .../AttendanceSummaryLocal.kt | 24 -- .../AttendanceSummaryRemote.kt | 33 --- .../AttendanceSummaryRepository.kt | 25 -- .../completedlessons/CompletedLessonsLocal.kt | 25 -- .../CompletedLessonsRemote.kt | 35 --- .../conference/ConferenceLocal.kt | 25 -- .../conference/ConferenceRemote.kt | 31 --- .../conference/ConferenceRepository.kt | 25 -- .../data/repositories/exam/ExamLocal.kt | 25 -- .../data/repositories/exam/ExamRemote.kt | 33 --- .../data/repositories/grade/GradeLocal.kt | 49 ---- .../data/repositories/grade/GradeRemote.kt | 53 ---- .../gradestatistics/GradeStatisticsLocal.kt | 59 ---- .../gradestatistics/GradeStatisticsRemote.kt | 63 ----- .../repositories/homework/HomeworkLocal.kt | 29 -- .../repositories/homework/HomeworkRemote.kt | 32 --- .../luckynumber/LuckyNumberLocal.kt | 29 -- .../luckynumber/LuckyNumberRemote.kt | 23 -- .../luckynumber/LuckyNumberRepository.kt | 36 --- .../data/repositories/message/MessageLocal.kt | 42 --- .../repositories/message/MessageRemote.kt | 78 ------ .../mobiledevice/MobileDeviceLocal.kt | 24 -- .../mobiledevice/MobileDeviceRemote.kt | 45 ---- .../mobiledevice/MobileDeviceRepository.kt | 36 --- .../data/repositories/note/NoteLocal.kt | 28 -- .../data/repositories/note/NoteRemote.kt | 31 --- .../repositories/recipient/RecipientLocal.kt | 24 -- .../repositories/recipient/RecipientRemote.kt | 38 --- .../recipient/RecipientRepository.kt | 36 --- .../repositories/recover/RecoverRepository.kt | 16 -- .../reportingunit/ReportingUnitLocal.kt | 27 -- .../reportingunit/ReportingUnitRemote.kt | 25 -- .../reportingunit/ReportingUnitRepository.kt | 38 --- .../data/repositories/school/SchoolLocal.kt | 22 -- .../data/repositories/school/SchoolRemote.kt | 27 -- .../repositories/school/SchoolRepository.kt | 27 -- .../repositories/semester/SemesterLocal.kt | 23 -- .../repositories/semester/SemesterRemote.kt | 29 -- .../data/repositories/student/StudentLocal.kt | 59 ---- .../repositories/student/StudentRemote.kt | 23 -- .../repositories/student/StudentRepository.kt | 57 ---- .../data/repositories/subject/SubjectLocal.kt | 24 -- .../repositories/subject/SubjectRemote.kt | 26 -- .../repositories/subject/SubjectRepository.kt | 25 -- .../data/repositories/teacher/TeacherLocal.kt | 22 -- .../repositories/teacher/TeacherRemote.kt | 27 -- .../repositories/teacher/TeacherRepository.kt | 25 -- .../repositories/timetable/TimetableLocal.kt | 42 --- .../repositories/timetable/TimetableRemote.kt | 52 ---- .../alarm/TimetableNotificationReceiver.kt | 2 +- .../TimetableNotificationSchedulerHelper.kt | 2 +- .../wulkanowy/services/sync/SyncManager.kt | 2 +- .../wulkanowy/services/sync/SyncWorker.kt | 6 +- .../sync/works/AttendanceSummaryWork.kt | 2 +- .../services/sync/works/AttendanceWork.kt | 2 +- .../sync/works/CompletedLessonWork.kt | 2 +- .../wulkanowy/services/sync/works/ExamWork.kt | 2 +- .../sync/works/GradeStatisticsWork.kt | 2 +- .../services/sync/works/GradeWork.kt | 4 +- .../services/sync/works/HomeworkWork.kt | 2 +- .../services/sync/works/LuckyNumberWork.kt | 4 +- .../services/sync/works/MessageWork.kt | 6 +- .../wulkanowy/services/sync/works/NoteWork.kt | 4 +- .../services/sync/works/RecipientWork.kt | 4 +- .../services/sync/works/TeacherWork.kt | 2 +- .../services/sync/works/TimetableWork.kt | 2 +- .../widgets/TimetableWidgetService.kt | 8 +- .../github/wulkanowy/ui/base/BasePresenter.kt | 2 +- .../github/wulkanowy/ui/base/ErrorHandler.kt | 4 +- .../github/wulkanowy/ui/base/ThemeManager.kt | 2 +- .../ui/modules/about/AboutPresenter.kt | 2 +- .../about/contributor/ContributorPresenter.kt | 4 +- .../modules/about/license/LicensePresenter.kt | 2 +- .../about/logviewer/LogViewerPresenter.kt | 4 +- .../ui/modules/account/AccountPresenter.kt | 2 +- .../modules/attendance/AttendanceAdapter.kt | 2 +- .../modules/attendance/AttendancePresenter.kt | 8 +- .../summary/AttendanceSummaryPresenter.kt | 8 +- .../modules/conference/ConferencePresenter.kt | 6 +- .../ui/modules/exam/ExamPresenter.kt | 6 +- .../ui/modules/grade/GradeAverageProvider.kt | 6 +- .../ui/modules/grade/GradePresenter.kt | 4 +- .../grade/details/GradeDetailsPresenter.kt | 8 +- .../statistics/GradeStatisticsPresenter.kt | 10 +- .../grade/summary/GradeSummaryPresenter.kt | 2 +- .../ui/modules/homework/HomeworkPresenter.kt | 6 +- .../details/HomeworkDetailsPresenter.kt | 6 +- .../ui/modules/login/LoginErrorHandler.kt | 6 +- .../ui/modules/login/LoginPresenter.kt | 2 +- .../login/advanced/LoginAdvancedPresenter.kt | 2 +- .../modules/login/form/LoginFormPresenter.kt | 2 +- .../login/recover/LoginRecoverPresenter.kt | 4 +- .../login/recover/RecoverErrorHandler.kt | 6 +- .../LoginStudentSelectPresenter.kt | 2 +- .../login/symbol/LoginSymbolPresenter.kt | 2 +- .../luckynumber/LuckyNumberPresenter.kt | 4 +- .../LuckyNumberWidgetConfigurePresenter.kt | 2 +- .../LuckyNumberWidgetProvider.kt | 4 +- .../ui/modules/main/MainPresenter.kt | 4 +- .../ui/modules/message/MessageFragment.kt | 6 +- .../ui/modules/message/MessagePresenter.kt | 2 +- .../preview/MessagePreviewPresenter.kt | 6 +- .../message/send/SendMessagePresenter.kt | 12 +- .../modules/message/tab/MessageTabAdapter.kt | 2 +- .../modules/message/tab/MessageTabFragment.kt | 2 +- .../message/tab/MessageTabPresenter.kt | 8 +- .../mobiledevice/MobileDevicePresenter.kt | 6 +- .../token/MobileDeviceTokenPresenter.kt | 6 +- .../ui/modules/more/MorePresenter.kt | 2 +- .../ui/modules/note/NotePresenter.kt | 6 +- .../SchoolAndTeachersPresenter.kt | 2 +- .../school/SchoolPresenter.kt | 6 +- .../teacher/TeacherPresenter.kt | 6 +- .../ui/modules/settings/SettingsPresenter.kt | 4 +- .../ui/modules/splash/SplashPresenter.kt | 2 +- .../modules/timetable/TimetablePresenter.kt | 8 +- .../additional/AdditionalLessonsPresenter.kt | 6 +- .../completed/CompletedLessonsErrorHandler.kt | 6 +- .../completed/CompletedLessonsPresenter.kt | 6 +- .../TimetableWidgetConfigurePresenter.kt | 2 +- .../timetablewidget/TimetableWidgetFactory.kt | 8 +- .../TimetableWidgetProvider.kt | 2 +- .../io/github/wulkanowy/utils/FlowUtils.kt | 5 +- .../io/github/wulkanowy/TestEnityCreator.kt | 130 ++++----- .../repositories/AttendanceRepositoryTest.kt | 148 ++++++++++ .../CompletedLessonsRepositoryTest.kt | 143 ++++++++++ .../data/repositories/ExamRemoteTest.kt | 143 ++++++++++ .../data/repositories/GradeRepositoryTest.kt | 254 ++++++++++++++++++ .../GradeStatisticsRepositoryTest.kt | 94 +++++++ .../repositories/LuckyNumberRemoteTest.kt | 124 +++++++++ .../repositories/MessageRepositoryTest.kt | 133 +++++++++ .../MobileDeviceRepositoryTest.kt | 134 +++++++++ .../data/repositories/RecipientLocalTest.kt | 85 ++++++ .../repositories/SemesterRepositoryTest.kt | 221 +++++++++++++++ .../StudentRemoteTest.kt => StudentTest.kt} | 19 +- .../repositories/TimetableRepositoryTest.kt | 224 +++++++++++++++ .../attendance/AttendanceRemoteTest.kt | 82 ------ .../CompletedLessonsRemoteTest.kt | 76 ------ .../data/repositories/exam/ExamRemoteTest.kt | 77 ------ .../GradeStatisticsRemoteTest.kt | 93 ------- .../luckynumber/LuckyNumberRemoteTest.kt | 44 --- .../message/MessageRepositoryTest.kt | 106 -------- .../MobileDeviceRepositoryTest.kt | 66 ----- .../semester/SemesterRepositoryTest.kt | 216 --------------- .../timetable/TimetableRemoteTest.kt | 81 ------ .../modules/grade/GradeAverageProviderTest.kt | 14 +- .../ui/modules/login/LoginPresenterTest.kt | 2 +- .../login/form/LoginFormPresenterTest.kt | 4 +- .../LoginStudentSelectPresenterTest.kt | 2 +- .../ui/modules/main/MainPresenterTest.kt | 4 +- .../ui/modules/splash/SplashPresenterTest.kt | 2 +- .../wulkanowy/utils/TimetableExtensionTest.kt | 29 +- 216 files changed, 3115 insertions(+), 4109 deletions(-) delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt delete mode 100644 app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt rename app/src/main/java/io/github/wulkanowy/data/{repositories/message => enums}/MessageFolder.kt (63%) rename app/src/main/java/io/github/wulkanowy/data/{repositories/attendance => enums}/SentExcuseStatus.kt (60%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/ReportingUnitMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{appcreator => }/AppCreatorRepository.kt (93%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{completedlessons => }/CompletedLessonsRepository.kt (50%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{exam => }/ExamRepository.kt (51%) rename app/src/main/java/io/github/wulkanowy/data/repositories/{grade => }/GradeRepository.kt (64%) rename app/src/main/java/io/github/wulkanowy/data/repositories/{gradestatistics => }/GradeStatisticsRepository.kt (66%) rename app/src/main/java/io/github/wulkanowy/data/repositories/{homework => }/HomeworkRepository.kt (52%) rename app/src/main/java/io/github/wulkanowy/data/repositories/{logger => }/LoggerRepository.kt (96%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{message => }/MessageRepository.kt (51%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{note => }/NoteRepository.kt (56%) rename app/src/main/java/io/github/wulkanowy/data/repositories/{preferences => }/PreferencesRepository.kt (98%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{recover/RecoverRemote.kt => RecoverRepository.kt} (79%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/ReportingUnitRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{semester => }/SemesterRepository.kt (73%) create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt rename app/src/main/java/io/github/wulkanowy/data/repositories/{timetable => }/TimetableRepository.kt (62%) delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt rename app/src/test/java/io/github/wulkanowy/data/repositories/{student/StudentRemoteTest.kt => StudentTest.kt} (69%) create mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemoteTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemoteTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/exam/ExamRemoteTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemoteTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt delete mode 100644 app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt diff --git a/app/build.gradle b/app/build.gradle index 4e6bdda9d..84ca502be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -142,7 +142,7 @@ configurations.all { } dependencies { - implementation "io.github.wulkanowy:sdk:6edc8531" + implementation "io.github.wulkanowy:sdk:edc165bb" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt deleted file mode 100644 index 04d13be4b..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/TestEntityCreator.kt +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.wulkanowy.data.repositories - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import java.time.LocalDate.now -import java.time.LocalDateTime - -fun getStudent(): Student { - return Student( - email = "test", - password = "test123", - schoolSymbol = "23", - scrapperBaseUrl = "fakelog.cf", - loginType = "AUTO", - isCurrent = true, - userName = "", - studentName = "", - schoolShortName = "", - schoolName = "", - studentId = 0, - classId = 1, - symbol = "", - registrationDate = LocalDateTime.now(), - className = "", - loginMode = "API", - certificateKey = "", - privateKey = "", - mobileBaseUrl = "", - userLoginId = 0, - isParent = false - ) -} - -fun getSemester() = Semester( - semesterId = 1, - studentId = 1, - classId = 1, - diaryId = 2, - diaryName = "", - end = now(), - schoolYear = 2019, - semesterName = 1, - start = now(), - unitId = 1 -) diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt deleted file mode 100644 index fa1289869..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocalTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate -import java.time.LocalDate.now -import java.time.LocalDate.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class AttendanceLocalTest { - - private lateinit var attendanceLocal: AttendanceLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build() - attendanceLocal = AttendanceLocal(testDb.attendanceDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val list = listOf( - getAttendanceEntity( - of(2018, 9, 10), - SentExcuseStatus.ACCEPTED - ), - getAttendanceEntity( - of(2018, 9, 14), - SentExcuseStatus.WAITING - ), - getAttendanceEntity( - of(2018, 9, 17), - SentExcuseStatus.ACCEPTED - ) - ) - runBlocking { attendanceLocal.saveAttendance(list) } - - val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1) - val attendance = runBlocking { attendanceLocal.getAttendance(semester, of(2018, 9, 10), of(2018, 9, 14)).first() } - assertEquals(2, attendance.size) - assertEquals(attendance[0].date, of(2018, 9, 10)) - assertEquals(attendance[1].date, of(2018, 9, 14)) - } - - private fun getAttendanceEntity( - date: LocalDate, - excuseStatus: SentExcuseStatus - ) = Attendance( - studentId = 1, - diaryId = 2, - timeId = 3, - date = date, - number = 0, - subject = "", - name = "", - presence = false, - absence = false, - exemption = false, - lateness = false, - excused = false, - deleted = false, - excusable = false, - excuseStatus = excuseStatus.name - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt deleted file mode 100644 index ca7d0b1be..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocalTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate -import java.time.LocalDate.now -import java.time.LocalDate.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class CompletedLessonsLocalTest { - - private lateinit var completedLessonsLocal: CompletedLessonsLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room - .inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - completedLessonsLocal = CompletedLessonsLocal(testDb.completedLessonsDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val list = listOf( - getCompletedLesson(of(2018, 9, 10), 1), - getCompletedLesson(of(2018, 9, 14), 2), - getCompletedLesson(of(2018, 9, 17), 3) - ) - runBlocking { completedLessonsLocal.saveCompletedLessons(list) } - - val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1) - val completed = runBlocking { completedLessonsLocal.getCompletedLessons(semester, of(2018, 9, 10), of(2018, 9, 14)).first() } - assertEquals(2, completed.size) - assertEquals(completed[0].date, of(2018, 9, 10)) - assertEquals(completed[1].date, of(2018, 9, 14)) - } - - private fun getCompletedLesson(date: LocalDate, number: Int): CompletedLesson { - return CompletedLesson(1, 2, date, number, "", "", "", "", "", "", "") - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt deleted file mode 100644 index 14b29c9f6..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/exam/ExamLocalTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate.now -import java.time.LocalDate.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class ExamLocalTest { - - private lateinit var examLocal: ExamLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build() - examLocal = ExamLocal(testDb.examsDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val list = listOf( - Exam(1, 2, of(2018, 9, 10), now(), "", "", "", "", "", ""), - Exam(1, 2, of(2018, 9, 14), now(), "", "", "", "", "", ""), - Exam(1, 2, of(2018, 9, 17), now(), "", "", "", "", "", "") - ) - runBlocking { examLocal.saveExams(list) } - - val semester = Semester(1, 2, "", 1, 3, 2019, now(), now(), 1, 1) - val exams = runBlocking { examLocal.getExams(semester, of(2018, 9, 10), of(2018, 9, 14)).first() } - assertEquals(2, exams.size) - assertEquals(exams[0].date, of(2018, 9, 10)) - assertEquals(exams[1].date, of(2018, 9, 14)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt deleted file mode 100644 index 946ebf8ea..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeLocalTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate -import java.time.LocalDate.now -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class GradeLocalTest { - - private lateinit var gradeLocal: GradeLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room - .inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val list = listOf( - createGradeLocal(5, 3.0, LocalDate.of(2018, 9, 10), "", 1), - createGradeLocal(4, 4.0, LocalDate.of(2019, 2, 27), "", 2), - createGradeLocal(3, 5.0, LocalDate.of(2019, 2, 28), "", 2) - ) - runBlocking { gradeLocal.saveGrades(list) } - - val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1) - - val grades = runBlocking { gradeLocal.getGradesDetails(semester).first() } - - assertEquals(2, grades.size) - assertEquals(grades[0].date, LocalDate.of(2019, 2, 27)) - assertEquals(grades[1].date, LocalDate.of(2019, 2, 28)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt deleted file mode 100644 index 5cf9c2196..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/GradeRepositoryTest.kt +++ /dev/null @@ -1,221 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import android.os.Build.VERSION_CODES.P -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider.getApplicationContext -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate.of -import java.time.LocalDateTime -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -@SdkSuppress(minSdkVersion = P) -@RunWith(AndroidJUnit4::class) -class GradeRepositoryTest { - - @MockK - private lateinit var semesterMock: Semester - - private lateinit var studentMock: Student - - @MockK - private lateinit var gradeRemote: GradeRemote - - private lateinit var gradeLocal: GradeLocal - - private lateinit var testDb: AppDatabase - - @Before - fun initApi() { - MockKAnnotations.init(this) - testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() - gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao) - studentMock = getStudentMock() - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun markOlderThanRegisterDateAsRead() { - coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( - createGradeLocal(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"), - createGradeLocal(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), - createGradeLocal(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), - createGradeLocal(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") - ) to emptyList()) - - val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true) - .filter { it.status == Status.SUCCESS }.first().data!!.first.sortedByDescending { it.date } - } - - assertFalse { grades[0].isRead } - assertFalse { grades[1].isRead } - assertTrue { grades[2].isRead } - assertTrue { grades[3].isRead } - } - - @Test - fun mitigateOldGradesNotifications() { - val list = listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Jedna ocena"), - createGradeLocal(4, 4.0, of(2019, 2, 26), "Druga"), - createGradeLocal(3, 5.0, of(2019, 2, 27), "Trzecia") - ) - runBlocking { gradeLocal.saveGrades(list) } - - coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( - createGradeLocal(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"), - createGradeLocal(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), - createGradeLocal(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), - createGradeLocal(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") - ) to emptyList()) - - val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true) - .filter { it.status == Status.SUCCESS }.first().data!!.first.sortedByDescending { it.date } - } - - assertFalse { grades[0].isRead } - assertFalse { grades[1].isRead } - assertTrue { grades[2].isRead } - assertTrue { grades[3].isRead } - } - - @Test - fun subtractLocaleDuplicateGrades() { - val list = listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - ) - runBlocking { gradeLocal.saveGrades(list) } - - coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - ) to emptyList()) - - val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true) - .filter { it.status == Status.SUCCESS }.first().data!! - } - - assertEquals(2, grades.first.size) - } - - @Test - fun subtractRemoteDuplicateGrades() { - val list = listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - ) - runBlocking { gradeLocal.saveGrades(list) } - - coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - ) to emptyList()) - - val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true) - .filter { it.status == Status.SUCCESS }.first().data!! - } - - assertEquals(3, grades.first.size) - } - - @Test - fun emptyLocal() { - runBlocking { gradeLocal.saveGrades(listOf()) } - - coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - ) to emptyList()) - - val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true) - .filter { it.status == Status.SUCCESS }.first().data!! - } - - assertEquals(3, grades.first.size) - } - - @Test - fun emptyRemote() { - val list = listOf( - createGradeLocal(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), - createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") - ) - runBlocking { gradeLocal.saveGrades(list) } - - coEvery { gradeRemote.getGrades(studentMock, semesterMock) } returns (emptyList() to emptyList()) - - val grades = runBlocking { - GradeRepository(gradeLocal, gradeRemote) - .getGrades(studentMock, semesterMock, true) - .filter { it.status == Status.SUCCESS }.first().data!! - } - - assertEquals(0, grades.first.size) - } - - private fun getStudentMock() = Student( - scrapperBaseUrl = "http://fakelog.cf", - email = "jan@fakelog.cf", - certificateKey = "", - classId = 0, - className = "", - isCurrent = false, - isParent = false, - loginMode = Sdk.Mode.SCRAPPER.name, - loginType = "STANDARD", - mobileBaseUrl = "", - password = "", - privateKey = "", - registrationDate = LocalDateTime.of(2019, 2, 27, 12, 0), - schoolName = "", - schoolShortName = "test", - schoolSymbol = "", - studentId = 0, - studentName = "", - symbol = "", - userLoginId = 0, - userName = "" - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt deleted file mode 100644 index 629c24321..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/grade/TestGradeEntityCreator.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import java.time.LocalDate -import io.github.wulkanowy.sdk.pojo.Grade as GradeRemote -import io.github.wulkanowy.data.db.entities.Grade as GradeLocal - -fun createGradeLocal(value: Int, weight: Double, date: LocalDate, desc: String, semesterId: Int = 1): GradeLocal { - return GradeLocal( - semesterId = semesterId, - studentId = 1, - modifier = .0, - teacher = "", - subject = "", - date = date, - color = "", - comment = "", - description = desc, - entry = "", - gradeSymbol = "", - value = value.toDouble(), - weight = "", - weightValue = weight - ) -} - -fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String): GradeRemote { - return GradeRemote( - subject = "", - color = "", - comment = "", - date = date, - description = desc, - entry = "", - modifier = .0, - symbol = "", - teacher = "", - value = value.toDouble(), - weight = weight.toString(), - weightValue = weight - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt deleted file mode 100644 index db0834325..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocalTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.GradePartialStatistics -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate.now -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class GradeStatisticsLocalTest { - - private lateinit var gradeStatisticsLocal: GradeStatisticsLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradePartialStatisticsDao, testDb.gradePointsStatisticsDao, testDb.gradeSemesterStatisticsDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndRead_subject() { - val list = listOf( - getGradeStatistics("Matematyka", 2, 1), - getGradeStatistics("Fizyka", 1, 2) - ) - runBlocking { gradeStatisticsLocal.saveGradePartialStatistics(list) } - - val stats = runBlocking { gradeStatisticsLocal.getGradePartialStatistics(getSemester()).first() } - assertEquals(1, stats.size) - assertEquals(stats[0].subject, "Matematyka") - } - - @Test - fun saveAndRead_all() { - val list = listOf( - getGradeStatistics("Matematyka", 2, 1), - getGradeStatistics("Chemia", 2, 1), - getGradeStatistics("Fizyka", 1, 2) - ) - runBlocking { gradeStatisticsLocal.saveGradePartialStatistics(list) } - - val stats = runBlocking { gradeStatisticsLocal.getGradePartialStatistics(getSemester()).first() } - assertEquals(2, stats.size) - assertEquals(stats[0].subject, "Matematyka") - assertEquals(stats[1].subject, "Chemia") - } - - @Test - fun saveAndRead_points() { - val list = listOf( - getGradePointsStatistics("Matematyka", 2, 1), - getGradePointsStatistics("Chemia", 2, 1), - getGradePointsStatistics("Fizyka", 1, 2) - ) - runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(list) } - - val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() } - with(stats[0]) { - assertEquals(subject, "Matematyka") - assertEquals(others, 5.0) - assertEquals(student, 5.0) - } - } - - @Test - fun saveAndRead_subjectEmpty() { - runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(listOf()) } - - val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() } - assertEquals(emptyList(), stats) - } - - @Test - fun saveAndRead_allEmpty() { - runBlocking { gradeStatisticsLocal.saveGradePointsStatistics(listOf()) } - - val stats = runBlocking { gradeStatisticsLocal.getGradePointsStatistics(getSemester()).first() } - assertEquals(emptyList(), stats) - } - - private fun getSemester(): Semester { - return Semester(2, 2, "", 2019, 1, 2, now(), now(), 1, 1) - } - - private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradePartialStatistics { - return GradePartialStatistics(studentId, semesterId, subject, "", "", listOf(5), listOf(5)) - } - - private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics { - return GradePointsStatistics(studentId, semesterId, subject, 5.0, 5.0) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt deleted file mode 100644 index ca38b1fee..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocalTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate -import java.time.LocalDateTime.now -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class LuckyNumberLocalTest { - - private lateinit var luckyNumberLocal: LuckyNumberLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room - .inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - luckyNumberLocal = LuckyNumberLocal(testDb.luckyNumberDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val number = LuckyNumber(1, LocalDate.of(2019, 1, 20), 14) - runBlocking { luckyNumberLocal.saveLuckyNumber(number) } - - val student = Student("", "", "", "", "", "", false, "", "", "", 1, 1, "", "", "", "", "", "", 1, false, now()) - val luckyNumber = runBlocking { luckyNumberLocal.getLuckyNumber(student, LocalDate.of(2019, 1, 20)).first() } - - assertEquals(1, luckyNumber?.studentId) - assertEquals(LocalDate.of(2019, 1, 20), luckyNumber?.date) - assertEquals(14, luckyNumber?.luckyNumber) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt deleted file mode 100644 index 6bd1959bd..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocalTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDateTime -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class RecipientLocalTest { - - private lateinit var recipientLocal: RecipientLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - recipientLocal = RecipientLocal(testDb.recipientDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val list = listOf( - Recipient(1, "2rPracownik", "Kowalski Jan", "Kowalski Jan [KJ] - Pracownik (Fake123456)", 3, 4, 2, "hash"), - Recipient(1, "3rPracownik", "Kowalska Karolina", "Kowalska Karolina [KK] - Pracownik (Fake123456)", 4, 4, 2, "hash"), - Recipient(1, "4rPracownik", "Krupa Stanisław", "Krupa Stanisław [KS] - Uczeń (Fake123456)", 5, 4, 1, "hash") - ) - runBlocking { recipientLocal.saveRecipients(list) } - - val student = Student("fakelog.cf", "AUTO", "", "", "", "", false, "", "", "", 1, 0, "", "", "", "", "", "", 1, true, LocalDateTime.now()) - val recipients = runBlocking { - recipientLocal.getRecipients( - student = student, - role = 2, - unit = ReportingUnit(1, 4, "", 0, "", emptyList()) - ) - } - - assertEquals(2, recipients.size) - assertEquals(1, recipients[0].studentId) - assertEquals("3rPracownik", recipients[1].realId) - assertEquals("Kowalski Jan", recipients[0].name) - assertEquals("Kowalska Karolina [KK] - Pracownik (Fake123456)", recipients[1].realName) - assertEquals(3, recipients[0].loginId) - assertEquals(4, recipients[1].unitId) - assertEquals(2, recipients[0].role) - assertEquals("hash", recipients[1].hash) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt deleted file mode 100644 index d68f15a80..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/student/StudentLocalTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import android.content.Context -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.TestDispatchersProvider -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.repositories.getStudent -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class StudentLocalTest { - - private lateinit var studentLocal: StudentLocal - - private lateinit var testDb: AppDatabase - - private val student = getStudent() - - @Before - fun createDb() { - val context = ApplicationProvider.getApplicationContext() - testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) - .build() - studentLocal = StudentLocal(testDb.studentDao, TestDispatchersProvider(), context) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - runBlocking { studentLocal.saveStudents(listOf(student)) } - - val student = runBlocking { studentLocal.getCurrentStudent(true) } - assertEquals("23", student?.schoolSymbol) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt deleted file mode 100644 index dddf6464c..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TestTimetableEntityCreator.kt +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import java.time.LocalDateTime -import java.time.LocalDateTime.now -import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal -import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote - -fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal { - return TimetableLocal( - studentId = 1, - diaryId = 2, - number = number, - start = start, - end = now(), - date = start.toLocalDate(), - subject = subject, - subjectOld = "", - group = "", - room = room, - roomOld = "", - teacher = teacher, - teacherOld = "", - info = "", - isStudentPlan = true, - changes = changes, - canceled = false - ) -} - -fun createTimetableRemote(start: LocalDateTime, number: Int = 1, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableRemote { - return TimetableRemote( - number = number, - start = start, - end = start.plusMinutes(45), - date = start.toLocalDate(), - subject = subject, - group = "", - room = room, - teacher = teacher, - info = "", - changes = changes, - canceled = false, - roomOld = "", - subjectOld = "", - teacherOld = "", - studentPlan = true - ) -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt deleted file mode 100644 index f4257f257..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocalTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate -import java.time.LocalDateTime.of -import kotlin.test.assertEquals - -@RunWith(AndroidJUnit4::class) -class TimetableLocalTest { - - private lateinit var timetableDb: TimetableLocal - - private lateinit var testDb: AppDatabase - - @Before - fun createDb() { - testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) - .build() - timetableDb = TimetableLocal(testDb.timetableDao, testDb.timetableAdditionalDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun saveAndReadTest() { - val list = listOf( - createTimetableLocal(of(2018, 9, 10, 0, 0, 0), 1), - createTimetableLocal(of(2018, 9, 14, 0, 0, 0), 1), - createTimetableLocal(of(2018, 9, 17, 0, 0, 0), 1) - ) - runBlocking { timetableDb.saveTimetable(list) } - - val semester = Semester(1, 2, "", 1, 1, 2019, LocalDate.now(), LocalDate.now(), 1, 1) - val exams = runBlocking { - timetableDb.getTimetable( - semester = semester, - startDate = LocalDate.of(2018, 9, 10), - endDate = LocalDate.of(2018, 9, 14) - ).first() - } - - assertEquals(2, exams.size) - assertEquals(exams[0].date, LocalDate.of(2018, 9, 10)) - assertEquals(exams[1].date, LocalDate.of(2018, 9, 14)) - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt deleted file mode 100644 index 9f1e29720..000000000 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepositoryTest.kt +++ /dev/null @@ -1,155 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import android.os.Build.VERSION_CODES.P -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider.getApplicationContext -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.db.AppDatabase -import io.github.wulkanowy.data.repositories.getSemester -import io.github.wulkanowy.data.repositories.getStudent -import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.time.LocalDate -import java.time.LocalDateTime.of -import kotlin.test.assertEquals - -@SdkSuppress(minSdkVersion = P) -@RunWith(AndroidJUnit4::class) -class TimetableRepositoryTest { - - @MockK(relaxed = true) - private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper - - @MockK - private lateinit var timetableRemote: TimetableRemote - - private lateinit var timetableLocal: TimetableLocal - - private lateinit var testDb: AppDatabase - - private val student = getStudent() - - private val semester = getSemester() - - @Before - fun initApi() { - MockKAnnotations.init(this) - testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build() - timetableLocal = TimetableLocal(testDb.timetableDao, testDb.timetableAdditionalDao) - } - - @After - fun closeDb() { - testDb.close() - } - - @Test - fun copyRoomToCompletedFromPrevious() { - runBlocking { - timetableLocal.saveTimetable(listOf( - createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "123", "Przyroda"), - createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "321", "Religia"), - createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "213", "W-F"), - createTimetableLocal(of(2019, 3, 5, 10, 30), 3, "213", "W-F", "Jan Kowalski") - )) - } - - coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns (listOf( - createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"), - createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "", "Religia"), - createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "", "W-F"), - createTimetableLocal(of(2019, 3, 5, 10, 30), 4, "", "W-F") - ) to emptyList()) - - val lessons = runBlocking { - TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable( - student = student, - semester = semester, - start = LocalDate.of(2019, 3, 5), - end = LocalDate.of(2019, 3, 5), - forceRefresh = true - ).filter { it.status == Status.SUCCESS }.first().data?.first.orEmpty() - } - - assertEquals(4, lessons.size) - assertEquals("123", lessons[0].room) - assertEquals("321", lessons[1].room) - assertEquals("213", lessons[2].room) - } - - @Test - fun copyTeacherToCompletedFromPrevious() { - val list = listOf( - createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Wtorkowska", true), - createTimetableLocal(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), - - createTimetableLocal(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "Joanna Wtorkowska", false), - createTimetableLocal(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "Joanna Wtorkowska", false), - createTimetableLocal(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "Joanna Środowska", true), - createTimetableLocal(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "Joanna Środowska", true), - - createTimetableLocal(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "", false), - createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "", false), - createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "", true), - createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "", true) - ) - runBlocking { timetableLocal.saveTimetable(list) } - - coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns (listOf( - createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), - createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true), - createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false), - createTimetableLocal(of(2019, 12, 23, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), - - createTimetableLocal(of(2019, 12, 24, 8, 0), 1, "123", "Język polski", "", false), - createTimetableLocal(of(2019, 12, 24, 8, 50), 2, "124", "Język polski", "", true), - createTimetableLocal(of(2019, 12, 24, 9, 40), 3, "125", "Język polski", "", false), - createTimetableLocal(of(2019, 12, 24, 10, 40), 4, "126", "Język polski", "", true), - - createTimetableLocal(of(2019, 12, 25, 8, 0), 1, "123", "Matematyka", "Paweł Środowski", false), - createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true), - createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false), - createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) - ) to emptyList()) - - val lessons = runBlocking { - TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable( - student = student, - semester = semester, - start = LocalDate.of(2019, 12, 23), - end = LocalDate.of(2019, 12, 25), - forceRefresh = true - ).filter { it.status == Status.SUCCESS }.first().data?.first.orEmpty() - } - - assertEquals(12, lessons.size) - - assertEquals("Paweł Poniedziałkowski", lessons[0].teacher) - assertEquals("Jakub Wtorkowski", lessons[1].teacher) - assertEquals("Joanna Poniedziałkowska", lessons[2].teacher) - assertEquals("Joanna Wtorkowska", lessons[3].teacher) - - assertEquals("Joanna Wtorkowska", lessons[4].teacher) - assertEquals("", lessons[5].teacher) - assertEquals("", lessons[6].teacher) - assertEquals("", lessons[7].teacher) - - assertEquals("Paweł Środowski", lessons[8].teacher) - assertEquals("Paweł Czwartkowski", lessons[9].teacher) - assertEquals("Paweł Środowski", lessons[10].teacher) - assertEquals("Paweł Czwartkowski", lessons[11].teacher) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt index 3661ac0a2..f1c61e82f 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.kt @@ -11,7 +11,7 @@ import androidx.work.Configuration import com.yariksoffice.lingver.Lingver import dagger.hilt.android.HiltAndroidApp import fr.bipi.tressence.file.FileLoggerTree -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.utils.ActivityLifecycleLogger import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index b6dfb534b..324cb89fb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -15,7 +15,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.sdk.Sdk import timber.log.Timber import javax.inject.Singleton @@ -33,11 +33,11 @@ internal class RepositoryModule { setSimpleHttpLogger { Timber.d(it) } // for debug only - addInterceptor(ChuckerInterceptor( - context = context, - collector = chuckerCollector, - alwaysReadResponseBody = true - ), true) + addInterceptor(ChuckerInterceptor.Builder(context) + .collector(chuckerCollector) + .alwaysReadResponseBody(true) + .build(), network = true + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt index 419efde0d..abf3beac3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/RecipientDao.kt @@ -10,5 +10,5 @@ import javax.inject.Singleton interface RecipientDao : BaseDao { @Query("SELECT * FROM Recipients WHERE student_id = :studentId AND role = :role AND unit_id = :unitId") - suspend fun load(studentId: Int, role: Int, unitId: Int): List + suspend fun loadAll(studentId: Int, role: Int, unitId: Int): List } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageFolder.kt b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt similarity index 63% rename from app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageFolder.kt rename to app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt index 06f5a1e05..899ba9085 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageFolder.kt +++ b/app/src/main/java/io/github/wulkanowy/data/enums/MessageFolder.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.data.repositories.message +package io.github.wulkanowy.data.enums enum class MessageFolder(val id: Int = 1) { RECEIVED(1), diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/SentExcuseStatus.kt b/app/src/main/java/io/github/wulkanowy/data/enums/SentExcuseStatus.kt similarity index 60% rename from app/src/main/java/io/github/wulkanowy/data/repositories/attendance/SentExcuseStatus.kt rename to app/src/main/java/io/github/wulkanowy/data/enums/SentExcuseStatus.kt index 50d6b8ed5..99878152f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/SentExcuseStatus.kt +++ b/app/src/main/java/io/github/wulkanowy/data/enums/SentExcuseStatus.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.data.repositories.attendance +package io.github.wulkanowy.data.enums enum class SentExcuseStatus(val id: Int = 0) { WAITING, diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt new file mode 100644 index 000000000..46e67fdaa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/AttendanceMapper.kt @@ -0,0 +1,43 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance +import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary + +fun List.mapToEntities(semester: Semester) = map { + Attendance( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + timeId = it.timeId, + number = it.number, + subject = it.subject, + name = it.name, + presence = it.presence, + absence = it.absence, + exemption = it.exemption, + lateness = it.lateness, + excused = it.excused, + deleted = it.deleted, + excusable = it.excusable, + excuseStatus = it.excuseStatus?.name + ) +} + +fun List.mapToEntities(semester: Semester, subjectId: Int) = map { + AttendanceSummary( + studentId = semester.studentId, + diaryId = semester.diaryId, + subjectId = subjectId, + month = it.month, + presence = it.presence, + absence = it.absence, + absenceExcused = it.absenceExcused, + absenceForSchoolReasons = it.absenceForSchoolReasons, + lateness = it.lateness, + latenessExcused = it.latenessExcused, + exemption = it.exemption + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt new file mode 100644 index 000000000..c42126eb7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/CompletedLessonsMapper.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.CompletedLesson +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.CompletedLesson as SdkCompletedLesson + +fun List.mapToEntities(semester: Semester) = map { + CompletedLesson( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + number = it.number, + subject = it.subject, + topic = it.topic, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol, + substitution = it.substitution, + absence = it.absence, + resources = it.resources + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt new file mode 100644 index 000000000..52dc9b30b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ConferenceMapper.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Conference +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Conference as SdkConference + +fun List.mapToEntities(semester: Semester) = map { + Conference( + studentId = semester.studentId, + diaryId = semester.diaryId, + agenda = it.agenda, + conferenceId = it.id, + date = it.date, + presentOnConference = it.presentOnConference, + subject = it.subject, + title = it.title + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt new file mode 100644 index 000000000..bdb5efbba --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ExamMapper.kt @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Exam +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Exam as SdkExam + +fun List.mapToEntities(semester: Semester) = map { + Exam( + studentId = semester.studentId, + diaryId = semester.diaryId, + date = it.date, + entryDate = it.entryDate, + subject = it.subject, + group = it.group, + type = it.type, + description = it.description, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt new file mode 100644 index 000000000..178de682a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeMapper.kt @@ -0,0 +1,42 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Grade +import io.github.wulkanowy.data.db.entities.GradeSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary +import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade + +fun List.mapToEntities(semester: Semester) = map { + Grade( + studentId = semester.studentId, + semesterId = semester.semesterId, + subject = it.subject, + entry = it.entry, + value = it.value, + modifier = it.modifier, + comment = it.comment, + color = it.color, + gradeSymbol = it.symbol, + description = it.description, + weight = it.weight, + weightValue = it.weightValue, + date = it.date, + teacher = it.teacher + ) +} + +@JvmName("mapGradeSummaryToEntities") +fun List.mapToEntities(semester: Semester) = map { + GradeSummary( + semesterId = semester.semesterId, + studentId = semester.studentId, + position = 0, + subject = it.name, + predictedGrade = it.predicted, + finalGrade = it.final, + pointsSum = it.pointsSum, + proposedPoints = it.proposedPoints, + finalPoints = it.finalPoints, + average = it.average + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt new file mode 100644 index 000000000..fbd40433c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/GradeStatisticsMapper.kt @@ -0,0 +1,80 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.GradePartialStatistics +import io.github.wulkanowy.data.db.entities.GradePointsStatistics +import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.pojos.GradeStatisticsItem +import io.github.wulkanowy.ui.modules.grade.statistics.ViewType +import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject as SdkGradeStatisticsSubject +import io.github.wulkanowy.sdk.pojo.GradeStatisticsSemester as SdkGradeStatisticsSemester +import io.github.wulkanowy.sdk.pojo.GradePointsStatistics as SdkGradePointsStatistics + +@JvmName("mapToEntitiesSubject") +fun List.mapToEntities(semester: Semester) = map { + GradePartialStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + classAverage = it.classAverage, + studentAverage = it.studentAverage, + classAmounts = it.classItems + .sortedBy { item -> item.grade } + .map { item -> item.amount }, + studentAmounts = it.studentItems.map { item -> item.amount } + ) +} + +@JvmName("mapToEntitiesSemester") +fun List.mapToEntities(semester: Semester) = map { + GradeSemesterStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + amounts = it.items + .sortedBy { item -> item.grade } + .map { item -> item.amount }, + studentGrade = it.items.singleOrNull { item -> item.isStudentHere }?.grade ?: 0 + ) +} + +@JvmName("mapToEntitiesPoints") +fun List.mapToEntities(semester: Semester) = map { + GradePointsStatistics( + semesterId = semester.semesterId, + studentId = semester.studentId, + subject = it.subject, + others = it.others, + student = it.student + ) +} + +fun List.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map { + GradeStatisticsItem( + type = ViewType.PARTIAL, + average = it.classAverage, + partial = it, + points = null, + semester = null + ) +} + +fun List.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map { + GradeStatisticsItem( + type = ViewType.SEMESTER, + partial = null, + points = null, + average = "", + semester = it + ) +} + +fun List.mapPointsToStatisticsItems() = map { + GradeStatisticsItem( + type = ViewType.POINTS, + partial = null, + semester = null, + average = "", + points = it + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt new file mode 100644 index 000000000..880a26d6d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/HomeworkMapper.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.sdk.pojo.Homework as SdkHomework +import io.github.wulkanowy.data.db.entities.Homework +import io.github.wulkanowy.data.db.entities.Semester + +fun List.mapToEntities(semester: Semester) = map { + Homework( + semesterId = semester.semesterId, + studentId = semester.studentId, + date = it.date, + entryDate = it.entryDate, + subject = it.subject, + content = it.content, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol, + attachments = it.attachments.map { attachment -> + attachment.url to attachment.name + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt new file mode 100644 index 000000000..78ebe1d6e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/LuckyNumberMapper.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Student +import java.time.LocalDate +import io.github.wulkanowy.sdk.pojo.LuckyNumber as SdkLuckyNumber + +fun SdkLuckyNumber.mapToEntity(student: Student) = LuckyNumber( + studentId = student.studentId, + date = LocalDate.now(), + luckyNumber = number +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt new file mode 100644 index 000000000..2c815b305 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MessageMapper.kt @@ -0,0 +1,53 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageAttachment +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient +import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment +import java.time.LocalDateTime +import io.github.wulkanowy.sdk.pojo.Message as SdkMessage + +fun List.mapToEntities(student: Student) = map { + Message( + studentId = student.id.toInt(), + realId = it.id ?: 0, + messageId = it.messageId ?: 0, + sender = it.sender?.name.orEmpty(), + senderId = it.sender?.loginId ?: 0, + recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów", + subject = it.subject.trim(), + date = it.date ?: LocalDateTime.now(), + folderId = it.folderId, + unread = it.unread ?: false, + removed = it.removed, + hasAttachments = it.hasAttachments + ).apply { + content = it.content.orEmpty() + unreadBy = it.unreadBy ?: 0 + readBy = it.readBy ?: 0 + } +} + +fun List.mapToEntities() = map { + MessageAttachment( + realId = it.id, + messageId = it.messageId, + oneDriveId = it.oneDriveId, + url = it.url, + filename = it.filename + ) +} + +fun List.mapFromEntities() = map { + SdkRecipient( + id = it.realId, + name = it.realName, + loginId = it.loginId, + reportingUnitId = it.unitId, + role = it.role, + hash = it.hash, + shortName = it.name + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt new file mode 100644 index 000000000..80f9e0549 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/MobileDeviceMapper.kt @@ -0,0 +1,23 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.sdk.pojo.Token as SdkToken +import io.github.wulkanowy.sdk.pojo.Device as SdkDevice + +fun List.mapToEntities(semester: Semester) = map { + MobileDevice( + studentId = semester.studentId, + date = it.createDate, + deviceId = it.id, + name = it.name + ) +} + +fun SdkToken.mapToMobileDeviceToken() = MobileDeviceToken( + token = token, + symbol = symbol, + pin = pin, + qr = qrCodeImage +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt new file mode 100644 index 000000000..70941799b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/NoteMapper.kt @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Note +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.Note as SdkNote + +fun List.mapToEntities(semester: Semester) = map { + Note( + studentId = semester.studentId, + date = it.date, + teacher = it.teacher, + teacherSymbol = it.teacherSymbol, + category = it.category, + categoryType = it.categoryType.id, + isPointsShow = it.showPoints, + points = it.points, + content = it.content + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt new file mode 100644 index 000000000..71792afa1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/RecipientMapper.kt @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient + +fun List.mapToEntities(student: Student) = map { + Recipient( + studentId = student.studentId, + realId = it.id, + realName = it.name, + name = it.shortName, + hash = it.hash, + loginId = it.loginId, + role = it.role, + unitId = it.reportingUnitId ?: 0 + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/ReportingUnitMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/ReportingUnitMapper.kt new file mode 100644 index 000000000..95483e46a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/ReportingUnitMapper.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit + +fun List.mapToEntities(student: Student) = map { + ReportingUnit( + studentId = student.studentId, + realId = it.id, + roles = it.roles, + senderId = it.senderId, + senderName = it.senderName, + shortName = it.short + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt new file mode 100644 index 000000000..dc3a5a9e9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/SchoolInfoMapper.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.School +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.sdk.pojo.School as SdkSchool + +fun SdkSchool.mapToEntity(semester: Semester) = School( + studentId = semester.studentId, + classId = semester.classId, + name = name, + address = address, + contact = contact, + headmaster = headmaster, + pedagogue = pedagogue +) diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt new file mode 100644 index 000000000..4dc95aaa7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/SubjectMapper.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.sdk.pojo.Subject as SdkSubject + +fun List.mapToEntities(semester: Semester) = map { + Subject( + studentId = semester.studentId, + diaryId = semester.diaryId, + name = it.name, + realId = it.id + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt new file mode 100644 index 000000000..49cb7c294 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/TeacherMapper.kt @@ -0,0 +1,15 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Teacher +import io.github.wulkanowy.sdk.pojo.Teacher as SdkTeacher + +fun List.mapToEntities(semester: Semester) = map { + Teacher( + studentId = semester.studentId, + name = it.name, + subject = it.subject, + shortName = it.short, + classId = semester.classId + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt new file mode 100644 index 000000000..ffd2ae34e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/mappers/TimetableMapper.kt @@ -0,0 +1,41 @@ +package io.github.wulkanowy.data.mappers + +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable +import io.github.wulkanowy.sdk.pojo.TimetableAdditional as SdkTimetableAdditional + +fun List.mapToEntities(semester: Semester) = map { + Timetable( + studentId = semester.studentId, + diaryId = semester.diaryId, + number = it.number, + start = it.start, + end = it.end, + date = it.date, + subject = it.subject, + subjectOld = it.subjectOld, + group = it.group, + room = it.room, + roomOld = it.roomOld, + teacher = it.teacher, + teacherOld = it.teacherOld, + info = it.info, + isStudentPlan = it.studentPlan, + changes = it.changes, + canceled = it.canceled + ) +} + +@JvmName("mapToEntitiesTimetableAdditional") +fun List.mapToEntities(semester: Semester) = map { + TimetableAdditional( + studentId = semester.studentId, + diaryId = semester.diaryId, + subject = it.subject, + date = it.date, + start = it.start, + end = it.end + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt similarity index 93% rename from app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt index ff538969b..aea8632ac 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/appcreator/AppCreatorRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AppCreatorRepository.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.data.repositories.appcreator +package io.github.wulkanowy.data.repositories import android.content.res.AssetManager import com.squareup.moshi.Moshi diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt new file mode 100644 index 000000000..699a325d7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -0,0 +1,50 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.Absent +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.monday +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.utils.uniqueSubtract +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AttendanceRepository @Inject constructor( + private val attendanceDb: AttendanceDao, + private val sdk: Sdk +) { + + fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getAttendance(start.monday, end.sunday, semester.semesterId) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + attendanceDb.deleteAll(old uniqueSubtract new) + attendanceDb.insertAll(new uniqueSubtract old) + }, + filterResult = { it.filter { item -> item.date in start..end } } + ) + + suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List, reason: String? = null) { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance -> + Absent( + date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)), + timeId = attendance.timeId + ) + }, reason) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt new file mode 100644 index 000000000..4aa51fba3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AttendanceSummaryRepository @Inject constructor( + private val attendanceDb: AttendanceSummaryDao, + private val sdk: Sdk +) { + + fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getAttendanceSummary(subjectId) + .mapToEntities(semester, subjectId) + }, + saveFetchResult = { old, new -> + attendanceDb.deleteAll(old uniqueSubtract new) + attendanceDb.insertAll(new uniqueSubtract old) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt similarity index 50% rename from app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt index 61268a66a..e9d6de6af 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepository.kt @@ -1,7 +1,11 @@ -package io.github.wulkanowy.data.repositories.completedlessons +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday @@ -12,17 +16,21 @@ import javax.inject.Singleton @Singleton class CompletedLessonsRepository @Inject constructor( - private val local: CompletedLessonsLocal, - private val remote: CompletedLessonsRemote + private val completedLessonsDb: CompletedLessonsDao, + private val sdk: Sdk ) { fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getCompletedLessons(semester, start.monday, end.sunday) }, - fetch = { remote.getCompletedLessons(student, semester, start.monday, end.sunday) }, + query = { completedLessonsDb.loadAll(semester.diaryId, semester.studentId, start, end) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getCompletedLessons(start.monday, end.sunday) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteCompleteLessons(old uniqueSubtract new) - local.saveCompletedLessons(new uniqueSubtract old) + completedLessonsDb.deleteAll(old uniqueSubtract new) + completedLessonsDb.insertAll(new uniqueSubtract old) }, filterResult = { it.filter { item -> item.date in start..end } } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt new file mode 100644 index 000000000..3a0d944b1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ConferenceRepository.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.ConferenceDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ConferenceRepository @Inject constructor( + private val conferenceDb: ConferenceDao, + private val sdk: Sdk +) { + + fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { conferenceDb.loadAll(semester.diaryId, student.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getConferences() + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + conferenceDb.deleteAll(old uniqueSubtract new) + conferenceDb.insertAll(new uniqueSubtract old) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt similarity index 51% rename from app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index 3f4591a25..63e118072 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -1,8 +1,12 @@ -package io.github.wulkanowy.data.repositories.exam +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.endExamsDay +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.startExamsDay import io.github.wulkanowy.utils.uniqueSubtract @@ -12,17 +16,21 @@ import javax.inject.Singleton @Singleton class ExamRepository @Inject constructor( - private val local: ExamLocal, - private val remote: ExamRemote + private val examDb: ExamDao, + private val sdk: Sdk ) { fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getExams(semester, start.startExamsDay, start.endExamsDay) }, - fetch = { remote.getExams(student, semester, start.startExamsDay, start.endExamsDay) }, + query = { examDb.loadAll(semester.diaryId, semester.studentId, start.startExamsDay, start.endExamsDay) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getExams(start.startExamsDay, start.endExamsDay, semester.semesterId) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteExams(old uniqueSubtract new) - local.saveExams(new uniqueSubtract old) + examDb.deleteAll(old uniqueSubtract new) + examDb.insertAll(new uniqueSubtract old) }, filterResult = { it.filter { item -> item.date in start..end } } ) diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt similarity index 64% rename from app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt index 8c9fe3089..d7bdc564c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeRepository.kt @@ -1,9 +1,14 @@ -package io.github.wulkanowy.data.repositories.grade +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow @@ -15,18 +20,25 @@ import javax.inject.Singleton @Singleton class GradeRepository @Inject constructor( - private val local: GradeLocal, - private val remote: GradeRemote + private val gradeDb: GradeDao, + private val gradeSummaryDb: GradeSummaryDao, + private val sdk: Sdk ) { fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh }, query = { - local.getGradesDetails(semester).combine(local.getGradesSummary(semester)) { details, summaries -> + gradeDb.loadAll(semester.semesterId, semester.studentId).combine(gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)) { details, summaries -> details to summaries } }, - fetch = { remote.getGrades(student, semester) }, + fetch = { + val (details, summary) = sdk.init(student) + .switchDiary(semester.diaryId, semester.schoolYear) + .getGrades(semester.semesterId) + + details.mapToEntities(semester) to summary.mapToEntities(semester) + }, saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) -> refreshGradeDetails(student, oldDetails, newDetails, notify) refreshGradeSummaries(oldSummary, newSummary, notify) @@ -35,8 +47,8 @@ class GradeRepository @Inject constructor( private suspend fun refreshGradeDetails(student: Student, oldGrades: List, newDetails: List, notify: Boolean) { val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate() - local.deleteGrades(oldGrades uniqueSubtract newDetails) - local.saveGrades((newDetails uniqueSubtract oldGrades).onEach { + gradeDb.deleteAll(oldGrades uniqueSubtract newDetails) + gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { if (it.date >= notifyBreakDate) it.apply { isRead = false if (notify) isNotified = false @@ -45,8 +57,8 @@ class GradeRepository @Inject constructor( } private suspend fun refreshGradeSummaries(oldSummaries: List, newSummary: List, notify: Boolean) { - local.deleteGradesSummary(oldSummaries uniqueSubtract newSummary) - local.saveGradesSummary((newSummary uniqueSubtract oldSummaries).onEach { summary -> + gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary) + gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary -> val oldSummary = oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject } summary.isPredictedGradeNotified = when { summary.predictedGrade.isEmpty() -> true @@ -73,30 +85,30 @@ class GradeRepository @Inject constructor( } fun getUnreadGrades(semester: Semester): Flow> { - return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isRead } } + return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isRead } } } fun getNotNotifiedGrades(semester: Semester): Flow> { - return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } } + return gradeDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { grade -> !grade.isNotified } } } fun getNotNotifiedPredictedGrades(semester: Semester): Flow> { - return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } } + return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified } } } fun getNotNotifiedFinalGrades(semester: Semester): Flow> { - return local.getGradesSummary(semester).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } } + return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified } } } suspend fun updateGrade(grade: Grade) { - return local.updateGrades(listOf(grade)) + return gradeDb.updateAll(listOf(grade)) } suspend fun updateGrades(grades: List) { - return local.updateGrades(grades) + return gradeDb.updateAll(grades) } suspend fun updateGradesSummary(gradesSummary: List) { - return local.updateGradesSummary(gradesSummary) + return gradeSummaryDb.updateAll(gradesSummary) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt similarity index 66% rename from app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt index ca0f6ffb9..70c0ada61 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepository.kt @@ -1,12 +1,18 @@ -package io.github.wulkanowy.data.repositories.gradestatistics +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao +import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao import io.github.wulkanowy.data.db.entities.GradePartialStatistics -import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.GradeStatisticsItem -import io.github.wulkanowy.ui.modules.grade.statistics.ViewType +import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems +import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems +import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import java.util.Locale @@ -15,17 +21,23 @@ import javax.inject.Singleton @Singleton class GradeStatisticsRepository @Inject constructor( - private val local: GradeStatisticsLocal, - private val remote: GradeStatisticsRemote + private val gradePartialStatisticsDb: GradePartialStatisticsDao, + private val gradePointsStatisticsDb: GradePointsStatisticsDao, + private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao, + private val sdk: Sdk ) { fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getGradePartialStatistics(semester) }, - fetch = { remote.getGradePartialStatistics(student, semester) }, + query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getGradesPartialStatistics(semester.semesterId) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteGradePartialStatistics(old uniqueSubtract new) - local.saveGradePartialStatistics(new uniqueSubtract old) + gradePartialStatisticsDb.deleteAll(old uniqueSubtract new) + gradePartialStatisticsDb.insertAll(new uniqueSubtract old) }, mapResult = { items -> when (subjectName) { @@ -52,11 +64,15 @@ class GradeStatisticsRepository @Inject constructor( fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getGradeSemesterStatistics(semester) }, - fetch = { remote.getGradeSemesterStatistics(student, semester) }, + query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getGradesSemesterStatistics(semester.semesterId) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteGradeSemesterStatistics(old uniqueSubtract new) - local.saveGradeSemesterStatistics(new uniqueSubtract old) + gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new) + gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old) }, mapResult = { items -> val itemsWithAverage = items.map { item -> @@ -88,11 +104,15 @@ class GradeStatisticsRepository @Inject constructor( fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getGradePointsStatistics(semester) }, - fetch = { remote.getGradePointsStatistics(student, semester) }, + query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getGradesPointsStatistics(semester.semesterId) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteGradePointsStatistics(old uniqueSubtract new) - local.saveGradePointsStatistics(new uniqueSubtract old) + gradePointsStatisticsDb.deleteAll(old uniqueSubtract new) + gradePointsStatisticsDb.insertAll(new uniqueSubtract old) }, mapResult = { items -> when (subjectName) { @@ -111,34 +131,4 @@ class GradeStatisticsRepository @Inject constructor( } return result } - - private fun List.mapPartialToStatisticItems() = filterNot { it.classAmounts.isEmpty() }.map { - GradeStatisticsItem( - type = ViewType.PARTIAL, - average = it.classAverage, - partial = it, - points = null, - semester = null - ) - } - - private fun List.mapSemesterToStatisticItems() = filterNot { it.amounts.isEmpty() }.map { - GradeStatisticsItem( - type = ViewType.SEMESTER, - partial = null, - points = null, - average = "", - semester = it - ) - } - - private fun List.mapPointsToStatisticsItems() = map { - GradeStatisticsItem( - type = ViewType.POINTS, - partial = null, - semester = null, - average = "", - points = it - ) - } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt similarity index 52% rename from app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt index 54397ea02..cec0d7f62 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/HomeworkRepository.kt @@ -1,8 +1,12 @@ -package io.github.wulkanowy.data.repositories.homework +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday @@ -13,22 +17,26 @@ import javax.inject.Singleton @Singleton class HomeworkRepository @Inject constructor( - private val local: HomeworkLocal, - private val remote: HomeworkRemote + private val homeworkDb: HomeworkDao, + private val sdk: Sdk ) { fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getHomework(semester, start.monday, end.sunday) }, - fetch = { remote.getHomework(student, semester, start.monday, end.sunday) }, + query = { homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getHomework(start.monday, end.sunday) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteHomework(old uniqueSubtract new) - local.saveHomework(new uniqueSubtract old) + homeworkDb.deleteAll(old uniqueSubtract new) + homeworkDb.insertAll(new uniqueSubtract old) } ) suspend fun toggleDone(homework: Homework) { - local.updateHomework(listOf(homework.apply { + homeworkDb.updateAll(listOf(homework.apply { isDone = !isDone })) } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt similarity index 96% rename from app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt index 85168fee5..6d509b026 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/logger/LoggerRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LoggerRepository.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.data.repositories.logger +package io.github.wulkanowy.data.repositories import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt new file mode 100644 index 000000000..801292b42 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/LuckyNumberRepository.kt @@ -0,0 +1,41 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.db.entities.LuckyNumber +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import java.time.LocalDate.now +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LuckyNumberRepository @Inject constructor( + private val luckyNumberDb: LuckyNumberDao, + private val sdk: Sdk +) { + + fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( + shouldFetch = { it == null || forceRefresh }, + query = { luckyNumberDb.load(student.studentId, now()) }, + fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) }, + saveFetchResult = { old, new -> + if (new != old) { + old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) } + luckyNumberDb.insertAll(listOfNotNull((new?.apply { + if (notify) isNotified = false + }))) + } + } + ) + + suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map { + if (it?.isNotified == false) it else null + }.first() + + suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = luckyNumberDb.updateAll(listOfNotNull(luckyNumber)) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt similarity index 51% rename from app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt index bb9326992..a203e6594 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MessageRepository.kt @@ -1,32 +1,43 @@ -package io.github.wulkanowy.data.repositories.message +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.MessageAttachmentDao +import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED +import io.github.wulkanowy.data.mappers.mapFromEntities +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.SentMessage +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import timber.log.Timber +import java.time.LocalDateTime.now import javax.inject.Inject import javax.inject.Singleton @Singleton class MessageRepository @Inject constructor( - private val local: MessageLocal, - private val remote: MessageRemote + private val messagesDb: MessagesDao, + private val messageAttachmentDao: MessageAttachmentDao, + private val sdk: Sdk ) { + @Suppress("UNUSED_PARAMETER") fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getMessages(student, folder) }, - fetch = { remote.getMessages(student, semester, folder) }, + query = { messagesDb.loadAll(student.id.toInt(), folder.id) }, + fetch = { sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).mapToEntities(student) }, saveFetchResult = { old, new -> - local.deleteMessages(old uniqueSubtract new) - local.saveMessages((new uniqueSubtract old).onEach { + messagesDb.deleteAll(old uniqueSubtract new) + messagesDb.insertAll((new uniqueSubtract old).onEach { it.isNotified = !notify }) } @@ -38,39 +49,47 @@ class MessageRepository @Inject constructor( Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") it.message.unread || it.message.content.isEmpty() }, - query = { local.getMessageWithAttachment(student, message) }, - fetch = { remote.getMessagesContentDetails(student, it!!.message, markAsRead) }, + query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) }, + fetch = { + sdk.init(student).getMessageDetails(it!!.message.messageId, message.folderId, markAsRead, message.realId).let { details -> + details.content to details.attachments.mapToEntities() + } + }, saveFetchResult = { old, (downloadedMessage, attachments) -> checkNotNull(old, { "Fetched message no longer exist!" }) - local.updateMessages(listOf(old.message.copy(unread = !markAsRead).apply { + messagesDb.updateAll(listOf(old.message.copy(unread = !markAsRead).apply { id = old.message.id content = content.ifBlank { downloadedMessage } })) - local.saveMessageAttachments(attachments) + messageAttachmentDao.insertAttachments(attachments) Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read") } ) fun getNotNotifiedMessages(student: Student): Flow> { - return local.getMessages(student, RECEIVED).map { it.filter { message -> !message.isNotified && message.unread } } + return messagesDb.loadAll(student.id.toInt(), RECEIVED.id).map { it.filter { message -> !message.isNotified && message.unread } } } suspend fun updateMessages(messages: List) { - return local.updateMessages(messages) + return messagesDb.updateAll(messages) } suspend fun sendMessage(student: Student, subject: String, content: String, recipients: List): SentMessage { - return remote.sendMessage(student, subject, content, recipients) + return sdk.init(student).sendMessage( + subject = subject, + content = content, + recipients = recipients.mapFromEntities() + ) } suspend fun deleteMessage(student: Student, message: Message) { - val isDeleted = remote.deleteMessage(student, message) + val isDeleted = sdk.init(student).deleteMessages(listOf(message.messageId), message.folderId) if (message.folderId != MessageFolder.TRASHED.id) { - if (isDeleted) local.updateMessages(listOf(message.copy(folderId = MessageFolder.TRASHED.id).apply { + if (isDeleted) messagesDb.updateAll(listOf(message.copy(folderId = MessageFolder.TRASHED.id).apply { id = message.id content = message.content })) - } else local.deleteMessages(listOf(message)) + } else messagesDb.deleteAll(listOf(message)) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt new file mode 100644 index 000000000..bbcf7f533 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/MobileDeviceRepository.kt @@ -0,0 +1,49 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.MobileDeviceDao +import io.github.wulkanowy.data.db.entities.MobileDevice +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken +import io.github.wulkanowy.data.pojos.MobileDeviceToken +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class MobileDeviceRepository @Inject constructor( + private val mobileDb: MobileDeviceDao, + private val sdk: Sdk +) { + + fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { mobileDb.loadAll(semester.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getRegisteredDevices() + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + mobileDb.deleteAll(old uniqueSubtract new) + mobileDb.insertAll(new uniqueSubtract old) + } + ) + + suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .unregisterDevice(device.deviceId) + + mobileDb.deleteAll(listOf(device)) + } + + suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { + return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getToken() + .mapToMobileDeviceToken() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt similarity index 56% rename from app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt index 6cf62ba22..9009d887c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/NoteRepository.kt @@ -1,8 +1,12 @@ -package io.github.wulkanowy.data.repositories.note +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.flow.Flow @@ -12,17 +16,21 @@ import javax.inject.Singleton @Singleton class NoteRepository @Inject constructor( - private val local: NoteLocal, - private val remote: NoteRemote + private val noteDb: NoteDao, + private val sdk: Sdk ) { fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getNotes(student) }, - fetch = { remote.getNotes(student, semester) }, + query = { noteDb.loadAll(student.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getNotes(semester.semesterId) + .mapToEntities(semester) + }, saveFetchResult = { old, new -> - local.deleteNotes(old uniqueSubtract new) - local.saveNotes((new uniqueSubtract old).onEach { + noteDb.deleteAll(old uniqueSubtract new) + noteDb.insertAll((new uniqueSubtract old).onEach { if (it.date >= student.registrationDate.toLocalDate()) it.apply { isRead = false if (notify) isNotified = false @@ -32,14 +40,14 @@ class NoteRepository @Inject constructor( ) fun getNotNotifiedNotes(student: Student): Flow> { - return local.getNotes(student).map { it.filter { note -> !note.isNotified } } + return noteDb.loadAll(student.studentId).map { it.filter { note -> !note.isNotified } } } suspend fun updateNote(note: Note) { - local.updateNotes(listOf(note)) + noteDb.updateAll(listOf(note)) } suspend fun updateNotes(notes: List) { - return local.updateNotes(notes) + noteDb.updateAll(notes) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt similarity index 98% rename from app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt index da31751a1..8cb815cc3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/preferences/PreferencesRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/PreferencesRepository.kt @@ -1,4 +1,4 @@ -package io.github.wulkanowy.data.repositories.preferences +package io.github.wulkanowy.data.repositories import android.content.Context import android.content.SharedPreferences diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt new file mode 100644 index 000000000..1fedb4792 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecipientRepository.kt @@ -0,0 +1,40 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.RecipientDao +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.Recipient +import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class RecipientRepository @Inject constructor( + private val recipientDb: RecipientDao, + private val sdk: Sdk +) { + + suspend fun refreshRecipients(student: Student, role: Int, unit: ReportingUnit) { + val new = sdk.init(student).getRecipients(unit.realId, role).mapToEntities(student) + val old = recipientDb.loadAll(student.studentId, role, unit.realId) + + recipientDb.deleteAll(old uniqueSubtract new) + recipientDb.insertAll(new uniqueSubtract old) + } + + suspend fun getRecipients(student: Student, role: Int, unit: ReportingUnit): List { + return recipientDb.loadAll(student.studentId, role, unit.realId).ifEmpty { + refreshRecipients(student, role, unit) + + recipientDb.loadAll(student.studentId, role, unit.realId) + } + } + + suspend fun getMessageRecipients(student: Student, message: Message): List { + return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt similarity index 79% rename from app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRemote.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt index 11eac71e0..5e1063558 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/RecoverRepository.kt @@ -1,11 +1,11 @@ -package io.github.wulkanowy.data.repositories.recover +package io.github.wulkanowy.data.repositories import io.github.wulkanowy.sdk.Sdk import javax.inject.Inject import javax.inject.Singleton @Singleton -class RecoverRemote @Inject constructor(private val sdk: Sdk) { +class RecoverRepository @Inject constructor(private val sdk: Sdk) { suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair { return sdk.getPasswordResetCaptchaCode(host, symbol) @@ -15,4 +15,3 @@ class RecoverRemote @Inject constructor(private val sdk: Sdk) { return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) } } - diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ReportingUnitRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ReportingUnitRepository.kt new file mode 100644 index 000000000..792e66b5e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ReportingUnitRepository.kt @@ -0,0 +1,42 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.ReportingUnitDao +import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ReportingUnitRepository @Inject constructor( + private val reportingUnitDb: ReportingUnitDao, + private val sdk: Sdk +) { + + suspend fun refreshReportingUnits(student: Student) { + val new = sdk.init(student).getReportingUnits().mapToEntities(student) + val old = reportingUnitDb.load(student.studentId) + + reportingUnitDb.deleteAll(old.uniqueSubtract(new)) + reportingUnitDb.insertAll(new.uniqueSubtract(old)) + } + + suspend fun getReportingUnits(student: Student): List { + return reportingUnitDb.load(student.studentId).ifEmpty { + refreshReportingUnits(student) + + reportingUnitDb.load(student.studentId) + } + } + + suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? { + return reportingUnitDb.loadOne(student.studentId, unitId) ?: run { + refreshReportingUnits(student) + + return reportingUnitDb.loadOne(student.studentId, unitId) + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt new file mode 100644 index 000000000..36d5c974a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SchoolRepository.kt @@ -0,0 +1,31 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.SchoolDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SchoolRepository @Inject constructor( + private val schoolDb: SchoolDao, + private val sdk: Sdk +) { + + fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it == null || forceRefresh }, + query = { schoolDb.load(semester.studentId, semester.classId) }, + fetch = { sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool().mapToEntity(semester) }, + saveFetchResult = { old, new -> + if (new != old && old != null) { + schoolDb.deleteAll(listOf(old)) + schoolDb.insertAll(listOf(new)) + } + schoolDb.insertAll(listOf(new)) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt similarity index 73% rename from app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt index 2748f1df5..8942391c8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SemesterRepository.kt @@ -1,10 +1,13 @@ -package io.github.wulkanowy.data.repositories.semester +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.getCurrentOrLast +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.isCurrent import io.github.wulkanowy.utils.uniqueSubtract import kotlinx.coroutines.withContext @@ -14,17 +17,17 @@ import javax.inject.Singleton @Singleton class SemesterRepository @Inject constructor( - private val remote: SemesterRemote, - private val local: SemesterLocal, + private val semesterDb: SemesterDao, + private val sdk: Sdk, private val dispatchers: DispatchersProvider ) { suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) { - val semesters = local.getSemesters(student) + val semesters = semesterDb.loadAll(student.studentId, student.classId) if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { refreshSemesters(student) - local.getSemesters(student) + semesterDb.loadAll(student.studentId, student.classId) } else semesters } @@ -41,12 +44,12 @@ class SemesterRepository @Inject constructor( } private suspend fun refreshSemesters(student: Student) { - val new = remote.getSemesters(student) + val new = sdk.init(student).getSemesters().mapToEntities(student.studentId) if (new.isEmpty()) return Timber.i("Empty semester list!") - val old = local.getSemesters(student) - local.deleteSemesters(old.uniqueSubtract(new)) - local.saveSemesters(new.uniqueSubtract(old)) + val old = semesterDb.loadAll(student.studentId, student.classId) + semesterDb.deleteAll(old.uniqueSubtract(new)) + semesterDb.insertSemesters(new.uniqueSubtract(old)) } suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt new file mode 100644 index 000000000..5b80035ba --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -0,0 +1,85 @@ +package io.github.wulkanowy.data.repositories + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentWithSemesters +import io.github.wulkanowy.data.exceptions.NoCurrentStudentException +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.security.decrypt +import io.github.wulkanowy.utils.security.encrypt +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class StudentRepository @Inject constructor( + @ApplicationContext private val context: Context, + private val dispatchers: DispatchersProvider, + private val studentDb: StudentDao, + private val semesterDb: SemesterDao, + private val sdk: Sdk +) { + + suspend fun isStudentSaved(): Boolean = getSavedStudents(false).isNotEmpty() + + suspend fun isCurrentStudentSet(): Boolean = studentDb.loadCurrent()?.isCurrent ?: false + + suspend fun getStudentsApi(pin: String, symbol: String, token: String): List { + return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities() + } + + suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List { + return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password) + } + + suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List { + return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password) + } + + suspend fun getSavedStudents(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) { + studentDb.loadStudentsWithSemesters().map { + it.apply { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password) + } + } + } + + suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) { + studentDb.loadById(id)?.apply { + if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) + } + } ?: throw NoCurrentStudentException() + + suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) { + studentDb.loadCurrent()?.apply { + if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) + } + } ?: throw NoCurrentStudentException() + + suspend fun saveStudents(studentsWithSemesters: List): List { + semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters }) + + return withContext(dispatchers.backgroundThread) { + studentDb.insertAll(studentsWithSemesters.map { it.student }.map { + if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context)) + else it + }) + } + } + + suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) { + with(studentDb) { + resetCurrent() + updateCurrent(studentWithSemesters.student.id) + } + } + + suspend fun logoutStudent(student: Student) { + studentDb.delete(student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt new file mode 100644 index 000000000..ef07a1d47 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepository.kt @@ -0,0 +1,32 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.SubjectDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubjectRepository @Inject constructor( + private val subjectDao: SubjectDao, + private val sdk: Sdk +) { + + fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getSubjects().mapToEntities(semester) + }, + saveFetchResult = { old, new -> + subjectDao.deleteAll(old uniqueSubtract new) + subjectDao.insertAll(new uniqueSubtract old) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt new file mode 100644 index 000000000..25da718ca --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TeacherRepository.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.TeacherDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.init +import io.github.wulkanowy.utils.networkBoundResource +import io.github.wulkanowy.utils.uniqueSubtract +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TeacherRepository @Inject constructor( + private val teacherDb: TeacherDao, + private val sdk: Sdk +) { + + fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( + shouldFetch = { it.isEmpty() || forceRefresh }, + query = { teacherDb.loadAll(semester.studentId, semester.classId) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getTeachers(semester.semesterId) + .mapToEntities(semester) + }, + saveFetchResult = { old, new -> + teacherDb.deleteAll(old uniqueSubtract new) + teacherDb.insertAll(new uniqueSubtract old) + } + ) +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt similarity index 62% rename from app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt rename to app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 6e98ed7ce..84eca78f8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -1,10 +1,15 @@ -package io.github.wulkanowy.data.repositories.timetable +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao +import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.TimetableAdditional +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper +import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday @@ -17,21 +22,27 @@ import javax.inject.Singleton @Singleton class TimetableRepository @Inject constructor( - private val local: TimetableLocal, - private val remote: TimetableRemote, + private val timetableDb: TimetableDao, + private val timetableAdditionalDb: TimetableAdditionalDao, + private val sdk: Sdk, private val schedulerHelper: TimetableNotificationSchedulerHelper ) { fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource( shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh }, query = { - local.getTimetable(semester, start.monday, end.sunday) + timetableDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) .map { schedulerHelper.scheduleNotifications(it, student); it } - .combine(local.getTimetableAdditional(semester, start.monday, end.sunday)) { timetable, additional -> + .combine(timetableAdditionalDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)) { timetable, additional -> timetable to additional } }, - fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) }, + fetch = { + sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) + .getTimetable(start.monday, end.sunday) + .let { (normal, additional) -> normal.mapToEntities(semester) to additional.mapToEntities(semester) } + + }, saveFetchResult = { (oldTimetable, oldAdditional), (newTimetable, newAdditional) -> refreshTimetable(student, oldTimetable, newTimetable) refreshAdditional(oldAdditional, newAdditional) @@ -40,13 +51,15 @@ class TimetableRepository @Inject constructor( filterResult = { (timetable, additional) -> timetable.filter { item -> item.date in start..end - } to additional.filter { item -> item.date in start..end } + } to additional.filter { item -> + item.date in start..end + } } ) private suspend fun refreshTimetable(student: Student, old: List, new: List) { - local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) - local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> + timetableDb.deleteAll(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) + timetableDb.insertAll(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> item.also { new -> old.singleOrNull { new.start == it.start }?.let { old -> return@map new.copy( @@ -59,7 +72,7 @@ class TimetableRepository @Inject constructor( } private suspend fun refreshAdditional(old: List, new: List) { - local.deleteTimetableAdditional(old.uniqueSubtract(new)) - local.saveTimetableAdditional(new.uniqueSubtract(old)) + timetableAdditionalDb.deleteAll(old.uniqueSubtract(new)) + timetableAdditionalDb.insertAll(new.uniqueSubtract(old)) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt deleted file mode 100644 index 9aaa52304..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import io.github.wulkanowy.data.db.dao.AttendanceDao -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) { - - suspend fun saveAttendance(attendance: List) { - attendanceDb.insertAll(attendance) - } - - suspend fun deleteAttendance(attendance: List) { - attendanceDb.deleteAll(attendance) - } - - fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { - return attendanceDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt deleted file mode 100644 index 870690ec0..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemote.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Absent -import io.github.wulkanowy.utils.init -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalTime -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getAttendance(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getAttendance(startDate, endDate, semester.semesterId) - .map { - Attendance( - studentId = semester.studentId, - diaryId = semester.diaryId, - date = it.date, - timeId = it.timeId, - number = it.number, - subject = it.subject, - name = it.name, - presence = it.presence, - absence = it.absence, - exemption = it.exemption, - lateness = it.lateness, - excused = it.excused, - deleted = it.deleted, - excusable = it.excusable, - excuseStatus = it.excuseStatus?.name - ) - } - } - - suspend fun excuseAbsence(student: Student, semester: Semester, absenceList: List, reason: String?): Boolean { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance -> - Absent( - date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)), - timeId = attendance.timeId - ) - }, reason) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt deleted file mode 100644 index 60f864f27..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRepository.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.sunday -import io.github.wulkanowy.utils.uniqueSubtract -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceRepository @Inject constructor( - private val local: AttendanceLocal, - private val remote: AttendanceRemote -) { - - fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getAttendance(semester, start.monday, end.sunday) }, - fetch = { remote.getAttendance(student, semester, start.monday, end.sunday) }, - saveFetchResult = { old, new -> - local.deleteAttendance(old uniqueSubtract new) - local.saveAttendance(new uniqueSubtract old) - }, - filterResult = { it.filter { item -> item.date in start..end } } - ) - - suspend fun excuseForAbsence(student: Student, semester: Semester, attendanceList: List, reason: String? = null) { - remote.excuseAbsence(student, semester, attendanceList, reason) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt deleted file mode 100644 index 703bc9474..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendancesummary - -import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao -import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceSummaryLocal @Inject constructor(private val attendanceDb: AttendanceSummaryDao) { - - suspend fun saveAttendanceSummary(attendance: List) { - attendanceDb.insertAll(attendance) - } - - suspend fun deleteAttendanceSummary(attendance: List) { - attendanceDb.deleteAll(attendance) - } - - fun getAttendanceSummary(semester: Semester, subjectId: Int): Flow> { - return attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt deleted file mode 100644 index 29a0b9a7b..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRemote.kt +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendancesummary - -import io.github.wulkanowy.data.db.entities.AttendanceSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceSummaryRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getAttendanceSummary(subjectId) - .map { - AttendanceSummary( - studentId = semester.studentId, - diaryId = semester.diaryId, - subjectId = subjectId, - month = it.month, - presence = it.presence, - absence = it.absence, - absenceExcused = it.absenceExcused, - absenceForSchoolReasons = it.absenceForSchoolReasons, - lateness = it.lateness, - latenessExcused = it.latenessExcused, - exemption = it.exemption - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt deleted file mode 100644 index 5dbe1ab05..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/attendancesummary/AttendanceSummaryRepository.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendancesummary - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class AttendanceSummaryRepository @Inject constructor( - private val local: AttendanceSummaryLocal, - private val remote: AttendanceSummaryRemote -) { - - fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getAttendanceSummary(semester, subjectId) }, - fetch = { remote.getAttendanceSummary(student, semester, subjectId) }, - saveFetchResult = { old, new -> - local.deleteAttendanceSummary(old uniqueSubtract new) - local.saveAttendanceSummary(new uniqueSubtract old) - } - ) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt deleted file mode 100644 index 51a1bdbfc..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import io.github.wulkanowy.data.db.dao.CompletedLessonsDao -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class CompletedLessonsLocal @Inject constructor(private val completedLessonsDb: CompletedLessonsDao) { - - suspend fun saveCompletedLessons(completedLessons: List) { - completedLessonsDb.insertAll(completedLessons) - } - - suspend fun deleteCompleteLessons(completedLessons: List) { - completedLessonsDb.deleteAll(completedLessons) - } - - fun getCompletedLessons(semester: Semester, start: LocalDate, end: LocalDate): Flow> { - return completedLessonsDb.loadAll(semester.diaryId, semester.studentId, start, end) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt deleted file mode 100644 index d15a27623..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemote.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class CompletedLessonsRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getCompletedLessons(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getCompletedLessons(startDate, endDate) - .map { - it.absence - CompletedLesson( - studentId = semester.studentId, - diaryId = semester.diaryId, - date = it.date, - number = it.number, - subject = it.subject, - topic = it.topic, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol, - substitution = it.substitution, - absence = it.absence, - resources = it.resources - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt deleted file mode 100644 index 9f56641f7..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.conference - -import io.github.wulkanowy.data.db.dao.ConferenceDao -import io.github.wulkanowy.data.db.entities.Conference -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ConferenceLocal @Inject constructor(private val conferenceDb: ConferenceDao) { - - fun getConferences(student: Student, semester: Semester): Flow> { - return conferenceDb.loadAll(semester.diaryId, student.studentId) - } - - suspend fun saveConferences(items: List) { - conferenceDb.insertAll(items) - } - - suspend fun deleteConferences(items: List) { - conferenceDb.deleteAll(items) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt deleted file mode 100644 index 50e869a99..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRemote.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.wulkanowy.data.repositories.conference - -import io.github.wulkanowy.data.db.entities.Conference -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ConferenceRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getConferences(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getConferences() - .map { - it.agenda - Conference( - studentId = student.studentId, - diaryId = semester.diaryId, - agenda = it.agenda, - conferenceId = it.id, - date = it.date, - presentOnConference = it.presentOnConference, - subject = it.subject, - title = it.title - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt deleted file mode 100644 index 187ecf58f..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/conference/ConferenceRepository.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.conference - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ConferenceRepository @Inject constructor( - private val local: ConferenceLocal, - private val remote: ConferenceRemote -) { - - fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getConferences(student, semester) }, - fetch = { remote.getConferences(student, semester) }, - saveFetchResult = { old, new -> - local.deleteConferences(old uniqueSubtract new) - local.saveConferences(new uniqueSubtract old) - } - ) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt deleted file mode 100644 index acc55b5ec..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamLocal.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import io.github.wulkanowy.data.db.dao.ExamDao -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ExamLocal @Inject constructor(private val examDb: ExamDao) { - - fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { - return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - } - - suspend fun saveExams(exams: List) { - examDb.insertAll(exams) - } - - suspend fun deleteExams(exams: List) { - examDb.deleteAll(exams) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt deleted file mode 100644 index ac4aa93d7..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/exam/ExamRemote.kt +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ExamRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getExams(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getExams(startDate, endDate, semester.semesterId) - .map { - Exam( - studentId = semester.studentId, - diaryId = semester.diaryId, - date = it.date, - entryDate = it.entryDate, - subject = it.subject, - group = it.group, - type = it.type, - description = it.description, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt deleted file mode 100644 index ed3635423..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeLocal.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import io.github.wulkanowy.data.db.dao.GradeDao -import io.github.wulkanowy.data.db.dao.GradeSummaryDao -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeLocal @Inject constructor( - private val gradeDb: GradeDao, - private val gradeSummaryDb: GradeSummaryDao -) { - - suspend fun saveGrades(grades: List) { - gradeDb.insertAll(grades) - } - - suspend fun deleteGrades(grades: List) { - gradeDb.deleteAll(grades) - } - - suspend fun updateGrades(grades: List) { - gradeDb.updateAll(grades) - } - - suspend fun updateGradesSummary(gradesSummary: List) { - gradeSummaryDb.updateAll(gradesSummary) - } - - fun getGradesDetails(semester: Semester): Flow> { - return gradeDb.loadAll(semester.semesterId, semester.studentId) - } - - suspend fun saveGradesSummary(gradesSummary: List) { - gradeSummaryDb.insertAll(gradesSummary) - } - - suspend fun deleteGradesSummary(gradesSummary: List) { - gradeSummaryDb.deleteAll(gradesSummary) - } - - fun getGradesSummary(semester: Semester): Flow> { - return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt deleted file mode 100644 index 9534a8910..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/grade/GradeRemote.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.wulkanowy.data.repositories.grade - -import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getGrades(student: Student, semester: Semester): Pair, List> { - val (details, summary) = sdk - .init(student) - .switchDiary(semester.diaryId, semester.schoolYear) - .getGrades(semester.semesterId) - - return details.map { - Grade( - studentId = semester.studentId, - semesterId = semester.semesterId, - subject = it.subject, - entry = it.entry, - value = it.value, - modifier = it.modifier, - comment = it.comment, - color = it.color, - gradeSymbol = it.symbol, - description = it.description, - weight = it.weight, - weightValue = it.weightValue, - date = it.date, - teacher = it.teacher - ) - } to summary.map { - GradeSummary( - semesterId = semester.semesterId, - studentId = semester.studentId, - position = 0, - subject = it.name, - predictedGrade = it.predicted, - finalGrade = it.final, - pointsSum = it.pointsSum, - proposedPoints = it.proposedPoints, - finalPoints = it.finalPoints, - average = it.average - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt deleted file mode 100644 index 3e3c819f7..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsLocal.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao -import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao -import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao -import io.github.wulkanowy.data.db.entities.GradePartialStatistics -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeStatisticsLocal @Inject constructor( - private val gradePartialStatisticsDb: GradePartialStatisticsDao, - private val gradePointsStatisticsDb: GradePointsStatisticsDao, - private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao -) { - - // partial - fun getGradePartialStatistics(semester: Semester): Flow> { - return gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) - } - - suspend fun saveGradePartialStatistics(items: List) { - gradePartialStatisticsDb.insertAll(items) - } - - suspend fun deleteGradePartialStatistics(items: List) { - gradePartialStatisticsDb.deleteAll(items) - } - - // points - fun getGradePointsStatistics(semester: Semester): Flow> { - return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) - } - - suspend fun saveGradePointsStatistics(gradePointsStatistics: List) { - gradePointsStatisticsDb.insertAll(gradePointsStatistics) - } - - suspend fun deleteGradePointsStatistics(gradesPointsStatistics: List) { - gradePointsStatisticsDb.deleteAll(gradesPointsStatistics) - } - - // semester - fun getGradeSemesterStatistics(semester: Semester): Flow> { - return gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) - } - - suspend fun saveGradeSemesterStatistics(items: List) { - gradeSemesterStatisticsDb.insertAll(items) - } - - suspend fun deleteGradeSemesterStatistics(items: List) { - gradeSemesterStatisticsDb.deleteAll(items) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt deleted file mode 100644 index 144df8a06..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemote.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import io.github.wulkanowy.data.db.entities.GradePartialStatistics -import io.github.wulkanowy.data.db.entities.GradePointsStatistics -import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class GradeStatisticsRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getGradePartialStatistics(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGradesPartialStatistics(semester.semesterId) - .map { - GradePartialStatistics( - semesterId = semester.semesterId, - studentId = student.studentId, - subject = it.subject, - classAverage = it.classAverage, - studentAverage = it.studentAverage, - classAmounts = it.classItems - .sortedBy { item -> item.grade } - .map { item -> item.amount }, - studentAmounts = it.studentItems.map { item -> item.amount } - ) - } - } - - suspend fun getGradeSemesterStatistics(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGradesSemesterStatistics(semester.semesterId) - .map { - GradeSemesterStatistics( - semesterId = semester.semesterId, - studentId = semester.studentId, - subject = it.subject, - amounts = it.items - .sortedBy { item -> item.grade } - .map { item -> item.amount }, - studentGrade = it.items.singleOrNull { item -> item.isStudentHere }?.grade ?: 0 - ) - } - } - - suspend fun getGradePointsStatistics(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getGradesPointsStatistics(semester.semesterId) - .map { - GradePointsStatistics( - semesterId = semester.semesterId, - studentId = semester.studentId, - subject = it.subject, - others = it.others, - student = it.student - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt deleted file mode 100644 index f2cbb8031..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkLocal.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.data.repositories.homework - -import io.github.wulkanowy.data.db.dao.HomeworkDao -import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) { - - suspend fun saveHomework(homework: List) { - homeworkDb.insertAll(homework) - } - - suspend fun deleteHomework(homework: List) { - homeworkDb.deleteAll(homework) - } - - suspend fun updateHomework(homework: List) { - homeworkDb.updateAll(homework) - } - - fun getHomework(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { - return homeworkDb.loadAll(semester.semesterId, semester.studentId, startDate, endDate) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt deleted file mode 100644 index 32109877a..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/homework/HomeworkRemote.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.wulkanowy.data.repositories.homework - -import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeworkRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getHomework(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getHomework(startDate, endDate) - .map { - Homework( - semesterId = semester.semesterId, - studentId = semester.studentId, - date = it.date, - entryDate = it.entryDate, - subject = it.subject, - content = it.content, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol, - attachments = it.attachments.map { attachment -> attachment.url to attachment.name } - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt deleted file mode 100644 index 0c3156d1e..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberLocal.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import io.github.wulkanowy.data.db.dao.LuckyNumberDao -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.coroutines.flow.Flow -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumberDao) { - - suspend fun saveLuckyNumber(luckyNumber: LuckyNumber?) { - luckyNumberDb.insertAll(listOfNotNull(luckyNumber)) - } - - suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) { - luckyNumberDb.updateAll(listOfNotNull(luckyNumber)) - } - - suspend fun deleteLuckyNumber(luckyNumber: LuckyNumber?) { - luckyNumberDb.deleteAll(listOfNotNull(luckyNumber)) - } - - fun getLuckyNumber(student: Student, date: LocalDate): Flow { - return luckyNumberDb.load(student.studentId, date) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt deleted file mode 100644 index 2872957d0..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemote.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LuckyNumberRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getLuckyNumber(student: Student): LuckyNumber? { - return sdk.init(student).getLuckyNumber(student.schoolShortName)?.let { - LuckyNumber( - studentId = student.studentId, - date = LocalDate.now(), - luckyNumber = it - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt deleted file mode 100644 index 173ce7e45..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRepository.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.networkBoundResource -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import java.time.LocalDate.now -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class LuckyNumberRepository @Inject constructor( - private val local: LuckyNumberLocal, - private val remote: LuckyNumberRemote -) { - - fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( - shouldFetch = { it == null || forceRefresh }, - query = { local.getLuckyNumber(student, now()) }, - fetch = { remote.getLuckyNumber(student) }, - saveFetchResult = { old, new -> - if (new != old) { - old?.let { local.deleteLuckyNumber(it) } - local.saveLuckyNumber(new?.apply { - if (notify) isNotified = false - }) - } - } - ) - - suspend fun getNotNotifiedLuckyNumber(student: Student) = - local.getLuckyNumber(student, now()).map { if (it?.isNotified == false) it else null }.first() - - suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = local.updateLuckyNumber(luckyNumber) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt deleted file mode 100644 index f1c8eaf08..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageLocal.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -import io.github.wulkanowy.data.db.dao.MessageAttachmentDao -import io.github.wulkanowy.data.db.dao.MessagesDao -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageAttachment -import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MessageLocal @Inject constructor( - private val messagesDb: MessagesDao, - private val messageAttachmentDao: MessageAttachmentDao -) { - - suspend fun saveMessages(messages: List) { - messagesDb.insertAll(messages) - } - - suspend fun updateMessages(messages: List) { - messagesDb.updateAll(messages) - } - - suspend fun deleteMessages(messages: List) { - messagesDb.deleteAll(messages) - } - - fun getMessageWithAttachment(student: Student, message: Message): Flow { - return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) - } - - suspend fun saveMessageAttachments(attachments: List) { - messageAttachmentDao.insertAttachments(attachments) - } - - fun getMessages(student: Student, folder: MessageFolder): Flow> { - return messagesDb.loadAll(student.id.toInt(), folder.id) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt deleted file mode 100644 index 044a13a28..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/message/MessageRemote.kt +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.MessageAttachment -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Folder -import io.github.wulkanowy.sdk.pojo.SentMessage -import io.github.wulkanowy.utils.init -import java.time.LocalDateTime.now -import javax.inject.Inject -import javax.inject.Singleton -import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient - -@Singleton -class MessageRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getMessages(student: Student, semester: Semester, folder: MessageFolder): List { - return sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).map { - Message( - studentId = student.id.toInt(), - realId = it.id ?: 0, - messageId = it.messageId ?: 0, - sender = it.sender?.name.orEmpty(), - senderId = it.sender?.loginId ?: 0, - recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów", - subject = it.subject.trim(), - date = it.date ?: now(), - folderId = it.folderId, - unread = it.unread ?: false, - removed = it.removed, - hasAttachments = it.hasAttachments - ).apply { - content = it.content.orEmpty() - unreadBy = it.unreadBy ?: 0 - readBy = it.readBy ?: 0 - } - } - } - - suspend fun getMessagesContentDetails(student: Student, message: Message, markAsRead: Boolean = false): Pair> { - return sdk.init(student).getMessageDetails(message.messageId, message.folderId, markAsRead, message.realId).let { details -> - details.content to details.attachments.map { - MessageAttachment( - realId = it.id, - messageId = it.messageId, - oneDriveId = it.oneDriveId, - url = it.url, - filename = it.filename - ) - } - } - } - - suspend fun sendMessage(student: Student, subject: String, content: String, recipients: List): SentMessage { - return sdk.init(student).sendMessage( - subject = subject, - content = content, - recipients = recipients.map { - SdkRecipient( - id = it.realId, - name = it.realName, - loginId = it.loginId, - reportingUnitId = it.unitId, - role = it.role, - hash = it.hash, - shortName = it.name - ) - } - ) - } - - suspend fun deleteMessage(student: Student, message: Message): Boolean { - return sdk.init(student).deleteMessages(listOf(message.messageId), message.folderId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt deleted file mode 100644 index 0ccb3d7ef..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import io.github.wulkanowy.data.db.dao.MobileDeviceDao -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MobileDeviceLocal @Inject constructor(private val mobileDb: MobileDeviceDao) { - - suspend fun saveDevices(devices: List) { - mobileDb.insertAll(devices) - } - - suspend fun deleteDevices(devices: List) { - mobileDb.deleteAll(devices) - } - - fun getDevices(semester: Semester): Flow> { - return mobileDb.loadAll(semester.studentId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt deleted file mode 100644 index 907e965ce..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRemote.kt +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MobileDeviceRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getDevices(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getRegisteredDevices() - .map { - MobileDevice( - studentId = semester.studentId, - date = it.createDate, - deviceId = it.id, - name = it.name - ) - } - } - - suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice): Boolean { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .unregisterDevice(device.deviceId) - } - - suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getToken() - .let { - MobileDeviceToken( - token = it.token, - symbol = it.symbol, - pin = it.pin, - qr = it.qrCodeImage - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt deleted file mode 100644 index 65526ef86..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepository.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MobileDeviceToken -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MobileDeviceRepository @Inject constructor( - private val local: MobileDeviceLocal, - private val remote: MobileDeviceRemote -) { - - fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getDevices(semester) }, - fetch = { remote.getDevices(student, semester) }, - saveFetchResult = { old, new -> - local.deleteDevices(old uniqueSubtract new) - local.saveDevices(new uniqueSubtract old) - } - ) - - suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { - remote.unregisterDevice(student, semester, device) - local.deleteDevices(listOf(device)) - } - - suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { - return remote.getToken(student, semester) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt deleted file mode 100644 index 85ba5e223..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteLocal.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.data.repositories.note - -import io.github.wulkanowy.data.db.dao.NoteDao -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.db.entities.Student -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class NoteLocal @Inject constructor(private val noteDb: NoteDao) { - - suspend fun saveNotes(notes: List) { - noteDb.insertAll(notes) - } - - suspend fun updateNotes(notes: List) { - noteDb.updateAll(notes) - } - - suspend fun deleteNotes(notes: List) { - noteDb.deleteAll(notes) - } - - fun getNotes(student: Student): Flow> { - return noteDb.loadAll(student.studentId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt deleted file mode 100644 index 0e488b7d9..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/note/NoteRemote.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.wulkanowy.data.repositories.note - -import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class NoteRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getNotes(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getNotes(semester.semesterId) - .map { - Note( - studentId = semester.studentId, - date = it.date, - teacher = it.teacher, - teacherSymbol = it.teacherSymbol, - category = it.category, - categoryType = it.categoryType.id, - isPointsShow = it.showPoints, - points = it.points, - content = it.content - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt deleted file mode 100644 index fac1645e4..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import io.github.wulkanowy.data.db.dao.RecipientDao -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao) { - - suspend fun getRecipients(student: Student, role: Int, unit: ReportingUnit): List { - return recipientDb.load(student.studentId, role, unit.realId) - } - - suspend fun saveRecipients(recipients: List): List { - return recipientDb.insertAll(recipients) - } - - suspend fun deleteRecipients(recipients: List) { - recipientDb.deleteAll(recipients) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt deleted file mode 100644 index a5318e77f..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRemote.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton -import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient - -@Singleton -class RecipientRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getRecipients(student: Student, role: Int, unit: ReportingUnit): List { - return sdk.init(student).getRecipients(unit.realId, role) - .map { it.toRecipient() } - } - - suspend fun getMessageRecipients(student: Student, message: Message): List { - return sdk.init(student).getMessageRecipients(message.messageId, message.senderId) - .map { it.toRecipient() } - } - - private fun SdkRecipient.toRecipient(): Recipient { - return Recipient( - studentId = sdk.studentId, - realId = id, - realName = name, - name = shortName, - hash = hash, - loginId = loginId, - role = role, - unitId = reportingUnitId ?: 0 - ) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt deleted file mode 100644 index f5e876b03..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recipient/RecipientRepository.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.data.repositories.recipient - -import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecipientRepository @Inject constructor( - private val local: RecipientLocal, - private val remote: RecipientRemote -) { - - suspend fun refreshRecipients(student: Student, role: Int, unit: ReportingUnit) { - val new = remote.getRecipients(student, role, unit) - val old = local.getRecipients(student, role, unit) - - local.deleteRecipients(old uniqueSubtract new) - local.saveRecipients(new uniqueSubtract old) - } - - suspend fun getRecipients(student: Student, role: Int, unit: ReportingUnit): List { - return local.getRecipients(student, role, unit).ifEmpty { - refreshRecipients(student, role, unit) - - local.getRecipients(student, role, unit) - } - } - - suspend fun getMessageRecipients(student: Student, message: Message): List { - return remote.getMessageRecipients(student, message) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt deleted file mode 100644 index 3117a606a..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/recover/RecoverRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.wulkanowy.data.repositories.recover - -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class RecoverRepository @Inject constructor(private val remote: RecoverRemote) { - - suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair { - return remote.getReCaptchaSiteKey(host, symbol) - } - - suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String { - return remote.sendRecoverRequest(url, symbol, email, reCaptchaResponse) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt deleted file mode 100644 index 737f1a04f..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitLocal.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.wulkanowy.data.repositories.reportingunit - -import io.github.wulkanowy.data.db.dao.ReportingUnitDao -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: ReportingUnitDao) { - - suspend fun getReportingUnits(student: Student): List { - return reportingUnitDb.load(student.studentId) - } - - suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? { - return reportingUnitDb.loadOne(student.studentId, unitId) - } - - suspend fun saveReportingUnits(reportingUnits: List): List { - return reportingUnitDb.insertAll(reportingUnits) - } - - suspend fun deleteReportingUnits(reportingUnits: List) { - reportingUnitDb.deleteAll(reportingUnits) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt deleted file mode 100644 index 6b11c2cc9..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRemote.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.reportingunit - -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ReportingUnitRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getReportingUnits(student: Student): List { - return sdk.init(student).getReportingUnits().map { unit -> - ReportingUnit( - studentId = sdk.studentId, - realId = unit.id, - roles = unit.roles, - senderId = unit.senderId, - senderName = unit.senderName, - shortName = unit.short - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt deleted file mode 100644 index ff5839460..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/reportingunit/ReportingUnitRepository.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.data.repositories.reportingunit - -import io.github.wulkanowy.data.db.entities.ReportingUnit -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ReportingUnitRepository @Inject constructor( - private val local: ReportingUnitLocal, - private val remote: ReportingUnitRemote -) { - - suspend fun refreshReportingUnits(student: Student) { - val new = remote.getReportingUnits(student) - val old = local.getReportingUnits(student) - - local.deleteReportingUnits(old.uniqueSubtract(new)) - local.saveReportingUnits(new.uniqueSubtract(old)) - } - - suspend fun getReportingUnits(student: Student): List { - return local.getReportingUnits(student).ifEmpty { - refreshReportingUnits(student) - - local.getReportingUnits(student) - } - } - - suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? { - return local.getReportingUnit(student, unitId) ?: run { - refreshReportingUnits(student) - - return local.getReportingUnit(student, unitId) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt deleted file mode 100644 index bc1b2f446..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolLocal.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.data.repositories.school - -import io.github.wulkanowy.data.db.dao.SchoolDao -import io.github.wulkanowy.data.db.entities.School -import io.github.wulkanowy.data.db.entities.Semester -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) { - - suspend fun saveSchool(school: School) { - schoolDb.insertAll(listOf(school)) - } - - suspend fun deleteSchool(school: School) { - schoolDb.deleteAll(listOf(school)) - } - - fun getSchool(semester: Semester): Flow { - return schoolDb.load(semester.studentId, semester.classId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt deleted file mode 100644 index 4d2e0cd6c..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRemote.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.wulkanowy.data.repositories.school - -import io.github.wulkanowy.data.db.entities.School -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject - -class SchoolRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getSchoolInfo(student: Student, semester: Semester): School { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getSchool() - .let { - School( - studentId = semester.studentId, - classId = semester.classId, - name = it.name, - address = it.address, - contact = it.contact, - headmaster = it.headmaster, - pedagogue = it.pedagogue - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt deleted file mode 100644 index 4c84c3194..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/school/SchoolRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.wulkanowy.data.repositories.school - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.networkBoundResource -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SchoolRepository @Inject constructor( - private val local: SchoolLocal, - private val remote: SchoolRemote -) { - - fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it == null || forceRefresh }, - query = { local.getSchool(semester) }, - fetch = { remote.getSchoolInfo(student, semester) }, - saveFetchResult = { old, new -> - if (new != old && old != null) { - local.deleteSchool(old) - local.saveSchool(new) - } - local.saveSchool(new) - } - ) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt deleted file mode 100644 index 8ecf1595a..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterLocal.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.wulkanowy.data.repositories.semester - -import io.github.wulkanowy.data.db.dao.SemesterDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) { - - suspend fun saveSemesters(semesters: List) { - semesterDb.insertSemesters(semesters) - } - - suspend fun deleteSemesters(semesters: List) { - semesterDb.deleteAll(semesters) - } - - suspend fun getSemesters(student: Student): List { - return semesterDb.loadAll(student.studentId, student.classId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt deleted file mode 100644 index e1a920b6f..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/semester/SemesterRemote.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.wulkanowy.data.repositories.semester - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SemesterRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getSemesters(student: Student): List { - return sdk.init(student).getSemesters().map { - Semester( - studentId = student.studentId, - diaryId = it.diaryId, - diaryName = it.diaryName, - schoolYear = it.schoolYear, - semesterId = it.semesterId, - semesterName = it.semesterNumber, - start = it.start, - end = it.end, - classId = it.classId, - unitId = it.unitId - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt deleted file mode 100644 index c01d1d049..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentLocal.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import io.github.wulkanowy.data.db.dao.StudentDao -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.DispatchersProvider -import io.github.wulkanowy.utils.security.decrypt -import io.github.wulkanowy.utils.security.encrypt -import kotlinx.coroutines.withContext -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class StudentLocal @Inject constructor( - private val studentDb: StudentDao, - private val dispatchers: DispatchersProvider, - @ApplicationContext private val context: Context -) { - - suspend fun saveStudents(students: List) = withContext(dispatchers.backgroundThread) { - studentDb.insertAll(students.map { - if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context)) - else it - }) - } - - suspend fun getStudents(decryptPass: Boolean) = withContext(dispatchers.backgroundThread) { - studentDb.loadStudentsWithSemesters().map { - it.apply { - if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password) - } - } - } - - suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) { - studentDb.loadById(id)?.apply { - if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) - } - } - - suspend fun getCurrentStudent(decryptPass: Boolean) = withContext(dispatchers.backgroundThread) { - studentDb.loadCurrent()?.apply { - if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) - } - } - - suspend fun setCurrentStudent(student: Student) = withContext(dispatchers.backgroundThread) { - studentDb.run { - resetCurrent() - updateCurrent(student.id) - } - } - - suspend fun logoutStudent(student: Student) = withContext(dispatchers.backgroundThread) { - studentDb.delete(student) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt deleted file mode 100644 index dcb126233..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRemote.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import io.github.wulkanowy.data.mappers.mapToEntities -import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.sdk.Sdk -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class StudentRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getStudentsMobileApi(token: String, pin: String, symbol: String): List { - return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities() - } - - suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List { - return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password) - } - - suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List { - return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt deleted file mode 100644 index 19e0e2086..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/student/StudentRepository.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.wulkanowy.data.repositories.student - -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.semester.SemesterLocal -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class StudentRepository @Inject constructor( - private val local: StudentLocal, - private val semestersLocal: SemesterLocal, - private val remote: StudentRemote -) { - - suspend fun isStudentSaved(): Boolean = local.getStudents(false).isNotEmpty() - - suspend fun isCurrentStudentSet(): Boolean = local.getCurrentStudent(false)?.isCurrent ?: false - - suspend fun getStudentsApi(pin: String, symbol: String, token: String): List { - return remote.getStudentsMobileApi(token, pin, symbol) - } - - suspend fun getStudentsScrapper(email: String, password: String, endpoint: String, symbol: String): List { - return remote.getStudentsScrapper(email, password, endpoint, symbol) - } - - suspend fun getStudentsHybrid(email: String, password: String, endpoint: String, symbol: String): List { - return remote.getStudentsHybrid(email, password, endpoint, symbol) - } - - suspend fun getSavedStudents(decryptPass: Boolean = true): List { - return local.getStudents(decryptPass) - } - - suspend fun getStudentById(id: Int): Student { - return local.getStudentById(id) ?: throw NoCurrentStudentException() - } - - suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { - return local.getCurrentStudent(decryptPass) ?: throw NoCurrentStudentException() - } - - suspend fun saveStudents(studentsWithSemesters: List): List { - semestersLocal.saveSemesters(studentsWithSemesters.flatMap { it.semesters }) - return local.saveStudents(studentsWithSemesters.map { it.student }) - } - - suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) { - return local.setCurrentStudent(studentWithSemesters.student) - } - - suspend fun logoutStudent(student: Student) { - return local.logoutStudent(student) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt deleted file mode 100644 index e225a381e..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectLocal.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.wulkanowy.data.repositories.subject - -import io.github.wulkanowy.data.db.dao.SubjectDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Subject -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SubjectLocal @Inject constructor(private val subjectDao: SubjectDao) { - - fun getSubjects(semester: Semester): Flow> { - return subjectDao.loadAll(semester.diaryId, semester.studentId) - } - - suspend fun saveSubjects(subjects: List) { - subjectDao.insertAll(subjects) - } - - suspend fun deleteSubjects(subjects: List) { - subjectDao.deleteAll(subjects) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt deleted file mode 100644 index 624a5a00a..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRemote.kt +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.wulkanowy.data.repositories.subject - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SubjectRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getSubjects(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getSubjects() - .map { - Subject( - studentId = semester.studentId, - diaryId = semester.diaryId, - name = it.name, - realId = it.id - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt deleted file mode 100644 index 60a0c3e71..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/subject/SubjectRepository.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.subject - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SubjectRepository @Inject constructor( - private val local: SubjectLocal, - private val remote: SubjectRemote -) { - - fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getSubjects(semester) }, - fetch = { remote.getSubjects(student, semester) }, - saveFetchResult = { old, new -> - local.deleteSubjects(old uniqueSubtract new) - local.saveSubjects(new uniqueSubtract old) - } - ) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt deleted file mode 100644 index 908f45a1a..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherLocal.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.data.repositories.teacher - -import io.github.wulkanowy.data.db.dao.TeacherDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Teacher -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -class TeacherLocal @Inject constructor(private val teacherDb: TeacherDao) { - - suspend fun saveTeachers(teachers: List) { - teacherDb.insertAll(teachers) - } - - suspend fun deleteTeachers(teachers: List) { - teacherDb.deleteAll(teachers) - } - - fun getTeachers(semester: Semester): Flow> { - return teacherDb.loadAll(semester.studentId, semester.classId) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt deleted file mode 100644 index 1d1caa686..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRemote.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.wulkanowy.data.repositories.teacher - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Teacher -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TeacherRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getTeachers(student: Student, semester: Semester): List { - return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) - .getTeachers(semester.semesterId) - .map { - Teacher( - studentId = semester.studentId, - name = it.name, - subject = it.subject, - shortName = it.short, - classId = semester.classId - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt deleted file mode 100644 index df25a53ec..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/teacher/TeacherRepository.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.data.repositories.teacher - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.networkBoundResource -import io.github.wulkanowy.utils.uniqueSubtract -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TeacherRepository @Inject constructor( - private val local: TeacherLocal, - private val remote: TeacherRemote -) { - - fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( - shouldFetch = { it.isEmpty() || forceRefresh }, - query = { local.getTeachers(semester) }, - fetch = { remote.getTeachers(student, semester) }, - saveFetchResult = { old, new -> - local.deleteTeachers(old uniqueSubtract new) - local.saveTeachers(new uniqueSubtract old) - } - ) -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt deleted file mode 100644 index 6e211d063..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableLocal.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao -import io.github.wulkanowy.data.db.dao.TimetableDao -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.db.entities.TimetableAdditional -import kotlinx.coroutines.flow.Flow -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TimetableLocal @Inject constructor( - private val timetableDb: TimetableDao, - private val timetableAdditionalDb: TimetableAdditionalDao -) { - - suspend fun saveTimetable(timetables: List) { - timetableDb.insertAll(timetables) - } - - suspend fun saveTimetableAdditional(additional: List) { - timetableAdditionalDb.insertAll(additional) - } - - suspend fun deleteTimetable(timetables: List) { - timetableDb.deleteAll(timetables) - } - - suspend fun deleteTimetableAdditional(additional: List) { - timetableAdditionalDb.deleteAll(additional) - } - - fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { - return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - } - - fun getTimetableAdditional(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow> { - return timetableAdditionalDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt deleted file mode 100644 index cfc700aed..000000000 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemote.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.db.entities.TimetableAdditional -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import java.time.LocalDate -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class TimetableRemote @Inject constructor(private val sdk: Sdk) { - - suspend fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Pair, List> { - val (normal, additional) = sdk.init(student) - .switchDiary(semester.diaryId, semester.schoolYear) - .getTimetable(startDate, endDate) - - return normal.map { - Timetable( - studentId = semester.studentId, - diaryId = semester.diaryId, - number = it.number, - start = it.start, - end = it.end, - date = it.date, - subject = it.subject, - subjectOld = it.subjectOld, - group = it.group, - room = it.room, - roomOld = it.roomOld, - teacher = it.teacher, - teacherOld = it.teacherOld, - info = it.info, - isStudentPlan = it.studentPlan, - changes = it.changes, - canceled = it.canceled - ) - } to additional.map { - TimetableAdditional( - studentId = semester.studentId, - diaryId = semester.diaryId, - subject = it.subject, - date = it.date, - start = it.start, - end = it.end - ) - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index 592d0919e..8eefc032f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -12,7 +12,7 @@ import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID import io.github.wulkanowy.ui.modules.main.MainActivity diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index 2593a555b..c0eba2f92 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -11,7 +11,7 @@ import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_END import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_ROOM import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_TITLE diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt index 47a949273..b94d97e33 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncManager.kt @@ -18,7 +18,7 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.isHolidays diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt index 13326ca06..a428b702b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/SyncWorker.kt @@ -11,9 +11,9 @@ import androidx.work.CoroutineWorker import androidx.work.Data import androidx.work.WorkerParameters import io.github.wulkanowy.R -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.services.sync.channels.DebugChannel diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt index 9e4ab9025..cbe1fe6bd 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceSummaryWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository import io.github.wulkanowy.utils.waitForResult import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index ecf0be018..788e4ea2c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository +import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.waitForResult diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt index 212ea6327..17bd61292 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/CompletedLessonWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository +import io.github.wulkanowy.data.repositories.CompletedLessonsRepository import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.waitForResult diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt index 899d45cba..b75499301 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/ExamWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.exam.ExamRepository +import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.utils.waitForResult import java.time.LocalDate.now import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt index 461836b8d..4575b419b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeStatisticsWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository +import io.github.wulkanowy.data.repositories.GradeStatisticsRepository import io.github.wulkanowy.utils.waitForResult import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt index 00bce109e..19c26edd0 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/GradeWork.kt @@ -13,8 +13,8 @@ import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.channels.NewGradesChannel import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt index a07f11073..a16841e9a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/HomeworkWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.homework.HomeworkRepository +import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.waitForResult diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt index 7b9f5ab30..9f5365535 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/LuckyNumberWork.kt @@ -12,8 +12,8 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt index 7b32f13bb..93c30b57c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/MessageWork.kt @@ -12,9 +12,9 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt index b08896966..50f418ed3 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/NoteWork.kt @@ -12,8 +12,8 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.note.NoteRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.NoteRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.NEUTRAL import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.POSITIVE diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt index c433c0ac8..dc4cd2a7b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/RecipientWork.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.recipient.RecipientRepository -import io.github.wulkanowy.data.repositories.reportingunit.ReportingUnitRepository +import io.github.wulkanowy.data.repositories.RecipientRepository +import io.github.wulkanowy.data.repositories.ReportingUnitRepository import javax.inject.Inject class RecipientWork @Inject constructor( diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt index a9abdaa72..7c614c6c5 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TeacherWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.teacher.TeacherRepository +import io.github.wulkanowy.data.repositories.TeacherRepository import io.github.wulkanowy.utils.waitForResult import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 3b8c6f5dc..2df2c9dcb 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.waitForResult diff --git a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt index 42c6d3bfd..45cd2b04e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt +++ b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt @@ -4,10 +4,10 @@ import android.content.Intent import android.widget.RemoteViewsService import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetFactory import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index ba8bfd85f..b222b0abb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.base import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index 946e661b1..34dd3ec16 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -1,7 +1,6 @@ package io.github.wulkanowy.ui.base import android.content.res.Resources -import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.utils.getString @@ -9,7 +8,7 @@ import io.github.wulkanowy.utils.security.ScramblerException import timber.log.Timber import javax.inject.Inject -open class ErrorHandler @Inject constructor(protected val resources: Resources, private val chuckerCollector: ChuckerCollector) { +open class ErrorHandler @Inject constructor(protected val resources: Resources) { var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> } @@ -18,7 +17,6 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources, var onNoCurrentStudent: () -> Unit = {} fun dispatch(error: Throwable) { - chuckerCollector.onError(error.javaClass.simpleName, error) Timber.e(error, "An exception occurred while the Wulkanowy was running") proceed(error) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt index 501348612..a2379c3e7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt @@ -7,7 +7,7 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES import io.github.wulkanowy.R -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt index 76ba82129..fde20267d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/AboutPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.about -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt index 78fdb43a1..ef4b540e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/contributor/ContributorPresenter.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.ui.modules.about.contributor import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.pojos.Contributor -import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.AppCreatorRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.flowWithResource diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt index 18d257eed..cc430fc2c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/license/LicensePresenter.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.about.license import com.mikepenz.aboutlibraries.entity.Library import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.DispatchersProvider diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt index 85eae8e6e..80020c81d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/about/logviewer/LogViewerPresenter.kt @@ -1,8 +1,8 @@ package io.github.wulkanowy.ui.modules.about.logviewer import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.logger.LoggerRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.LoggerRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.flowWithResource diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 4eaee4291..be01f7c54 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.account import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index 8e8a6149d..03ec1c842 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -7,7 +7,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus +import io.github.wulkanowy.data.enums.SentExcuseStatus import io.github.wulkanowy.databinding.ItemAttendanceBinding import io.github.wulkanowy.utils.description import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 19f8523d4..9c4a88074 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -3,10 +3,10 @@ package io.github.wulkanowy.ui.modules.attendance import android.annotation.SuppressLint import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.AttendanceRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt index 4ce49d960..242b09382 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -2,10 +2,10 @@ package io.github.wulkanowy.ui.modules.attendance.summary import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.subject.SubjectRepository +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt index 71b412da3..125aca2ff 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferencePresenter.kt @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.modules.conference import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.conference.ConferenceRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.ConferenceRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 406d011c0..7234b7e9a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.exam import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.repositories.exam.ExamRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.ExamRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt index 5d0a310f2..a608ac3bb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt @@ -6,9 +6,9 @@ import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.ALL_YEAR import io.github.wulkanowy.ui.modules.grade.GradeAverageMode.BOTH_SEMESTERS diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index e91c94eb2..d64613c01 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.ui.modules.grade import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index a34dd7acb..782006ac9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -3,10 +3,10 @@ package io.github.wulkanowy.ui.modules.grade.details import android.annotation.SuppressLint import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Grade -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt index aca3ec965..f94edd8af 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/statistics/GradeStatisticsPresenter.kt @@ -2,11 +2,11 @@ package io.github.wulkanowy.ui.modules.grade.statistics import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Subject -import io.github.wulkanowy.data.repositories.gradestatistics.GradeStatisticsRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.subject.SubjectRepository +import io.github.wulkanowy.data.repositories.GradeStatisticsRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.SubjectRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 7ce98d105..a3f33dd98 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.grade.summary import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.GradeSummary -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index f264d58e0..266b00b74 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.homework import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index 1d4dac2cb..ca6fc71ee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.homework.details import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Homework -import io.github.wulkanowy.data.repositories.homework.HomeworkRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.HomeworkRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt index f30825cff..ed4563246 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.login import android.content.res.Resources import android.database.sqlite.SQLiteConstraintException -import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.R import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException @@ -12,10 +11,7 @@ import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject -class LoginErrorHandler @Inject constructor( - resources: Resources, - chuckerCollector: ChuckerCollector -) : ErrorHandler(resources, chuckerCollector) { +class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) { var onBadCredentials: () -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt index 7e731772f..aa1e7eced 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.login import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import timber.log.Timber diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt index 9dfc3011b..891a6b0bb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/advanced/LoginAdvancedPresenter.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.login.advanced import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index e165d264f..f6a528ae3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.login.form import androidx.core.net.toUri import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt index 319541b8e..271e8a8a0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/LoginRecoverPresenter.kt @@ -1,8 +1,8 @@ package io.github.wulkanowy.ui.modules.login.recover import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.recover.RecoverRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.RecoverRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt index 110dd82e5..8619369dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/recover/RecoverErrorHandler.kt @@ -1,17 +1,13 @@ package io.github.wulkanowy.ui.modules.login.recover import android.content.res.Resources -import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.sdk.scrapper.exception.InvalidCaptchaException import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject -class RecoverErrorHandler @Inject constructor( - resources: Resources, - chuckerCollector: ChuckerCollector -) : ErrorHandler(resources, chuckerCollector) { +class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) { var onInvalidUsername: (String) -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt index dc8241992..c344bf441 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenter.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.login.studentselect import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt index 9f19539a0..4593d880a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/symbol/LoginSymbolPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.login.symbol import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt index cfd793f20..fd0598d8f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/LuckyNumberPresenter.kt @@ -1,8 +1,8 @@ package io.github.wulkanowy.ui.modules.luckynumber import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index 574120b36..f4041e9e4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index b7f4b740e..49a199431 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -16,8 +16,8 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.data.repositories.luckynumber.LuckyNumberRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.LuckyNumberRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.toFirstResult diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index cc525c29c..59937b332 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt index 16e0184a0..72fc627f8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessageFragment.kt @@ -6,9 +6,9 @@ import android.view.View.INVISIBLE import android.view.View.VISIBLE import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R -import io.github.wulkanowy.data.repositories.message.MessageFolder.RECEIVED -import io.github.wulkanowy.data.repositories.message.MessageFolder.SENT -import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED +import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED +import io.github.wulkanowy.data.enums.MessageFolder.SENT +import io.github.wulkanowy.data.enums.MessageFolder.TRASHED import io.github.wulkanowy.databinding.FragmentMessageBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt index 0fb454b31..7b8c3d0f5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/MessagePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.message -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import kotlinx.coroutines.delay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 039c7f140..17aba10f8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -5,9 +5,9 @@ import android.os.Build import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.MessageAttachment -import io.github.wulkanowy.data.repositories.message.MessageFolder -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AppInfo diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt index e06c49635..471feaae1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessagePresenter.kt @@ -3,12 +3,12 @@ package io.github.wulkanowy.ui.modules.message.send import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Recipient -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.recipient.RecipientRepository -import io.github.wulkanowy.data.repositories.reportingunit.ReportingUnitRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.RecipientRepository +import io.github.wulkanowy.data.repositories.ReportingUnitRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index e9b34da33..f0f65e087 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_POSITION import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.databinding.ItemMessageBinding import io.github.wulkanowy.utils.toFormattedString import javax.inject.Inject diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt index 44ad4bf18..48a2cee26 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabFragment.kt @@ -12,7 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder +import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.databinding.FragmentMessageTabBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt index 2143a4882..5a8ad2116 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabPresenter.kt @@ -2,10 +2,10 @@ package io.github.wulkanowy.ui.modules.message.tab import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Message -import io.github.wulkanowy.data.repositories.message.MessageFolder -import io.github.wulkanowy.data.repositories.message.MessageRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.enums.MessageFolder +import io.github.wulkanowy.data.repositories.MessageRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt index 456609e43..dce670a35 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/MobileDevicePresenter.kt @@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.mobiledevice import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.MobileDeviceRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt index 8270693d9..5e7110ee5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenPresenter.kt @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.MobileDeviceRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt index d083c9819..d119000d3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MorePresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.more -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import timber.log.Timber diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index 2e8bec5d1..ed3945335 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.note import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Note -import io.github.wulkanowy.data.repositories.note.NoteRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.NoteRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt index 305f4e7d5..915cc4213 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/SchoolAndTeachersPresenter.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.schoolandteachers -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import kotlinx.coroutines.delay diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt index 554d422df..491a19ec7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/school/SchoolPresenter.kt @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.school.SchoolRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.SchoolRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt index 0c58317b1..c83cfe766 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolandteachers/teacher/TeacherPresenter.kt @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.teacher.TeacherRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TeacherRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt index 758bedf0b..f9ad74a10 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsPresenter.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.ui.modules.settings import androidx.work.WorkInfo import com.chuckerteam.chucker.api.ChuckerCollector -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt index d16ca4a80..87392b7bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/splash/SplashPresenter.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.splash import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.flowWithResource diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index c1361fd40..3d9838a3d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -3,10 +3,10 @@ package io.github.wulkanowy.ui.modules.timetable import android.annotation.SuppressLint import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt index 0de207a57..623160e7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsPresenter.kt @@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.annotation.SuppressLint import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt index 767c2f313..00ba0bad8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsErrorHandler.kt @@ -1,15 +1,11 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.content.res.Resources -import com.chuckerteam.chucker.api.ChuckerCollector import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.ui.base.ErrorHandler import javax.inject.Inject -class CompletedLessonsErrorHandler @Inject constructor( - resources: Resources, - chuckerCollector: ChuckerCollector -) : ErrorHandler(resources, chuckerCollector) { +class CompletedLessonsErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) { var onFeatureDisabled: () -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt index e5551ed5f..7f9799b7f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsPresenter.kt @@ -3,9 +3,9 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.annotation.SuppressLint import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.CompletedLesson -import io.github.wulkanowy.data.repositories.completedlessons.CompletedLessonsRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.CompletedLessonsRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.afterLoading diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index 2274ecb90..67805fe0b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.timetablewidget import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index a21b5507e..df656c008 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -14,10 +14,10 @@ import android.widget.RemoteViewsService import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository -import io.github.wulkanowy.data.repositories.timetable.TimetableRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getCurrentThemeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 18e0ea562..938be98da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -19,7 +19,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.exceptions.NoCurrentStudentException -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.ui.modules.main.MainActivity diff --git a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt index 724458c04..d3bc40c02 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FlowUtils.kt @@ -18,7 +18,7 @@ inline fun networkBoundResource( crossinline query: () -> Flow, crossinline fetch: suspend (ResultType) -> RequestType, crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, - crossinline onFetchFailed: (Throwable) -> Unit = { Unit }, + crossinline onFetchFailed: (Throwable) -> Unit = { }, crossinline shouldFetch: (ResultType) -> Boolean = { true }, crossinline filterResult: (ResultType) -> ResultType = { it } ) = flow { @@ -29,7 +29,8 @@ inline fun networkBoundResource( if (showSavedOnLoading) emit(Resource.loading(filterResult(data))) try { - saveFetchResult(data, fetch(data)) + val newData = fetch(data) + saveFetchResult(data, newData) query().map { Resource.success(filterResult(it)) } } catch (throwable: Throwable) { onFetchFailed(throwable) diff --git a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt index d15dc9530..a0fa209fd 100644 --- a/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt +++ b/app/src/test/java/io/github/wulkanowy/TestEnityCreator.kt @@ -1,99 +1,59 @@ package io.github.wulkanowy -import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.sdk.Sdk import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalDateTime.now +import io.github.wulkanowy.sdk.pojo.Semester as SdkSemester -fun createSemesterEntity(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalDate, semesterName: Int = 1): Semester { - return Semester( - studentId = 1, - diaryId = diaryId, - semesterId = semesterId, - diaryName = "$semesterId", - schoolYear = 1970, - classId = 0, - semesterName = semesterName, - unitId = 1, - start = start, - end = end - ) -} - -fun getStudentEntity(mode: Sdk.Mode = Sdk.Mode.API): Student { - return Student( - scrapperBaseUrl = "http://fakelog.cf", - email = "jan@fakelog.cf", - certificateKey = "", - classId = 0, - className = "", - isCurrent = false, - isParent = false, - loginMode = mode.name, - loginType = "STANDARD", - mobileBaseUrl = "", - password = "", - privateKey = "", - registrationDate = now(), - schoolName = "", - schoolShortName = "test", - schoolSymbol = "", - studentId = 0, - studentName = "", - symbol = "", - userLoginId = 0, - userName = "", - ) -} - -fun getTimetableEntity( - isStudentPlan: Boolean = false, - canceled: Boolean = false, - start: LocalDateTime = now(), - end: LocalDateTime = now() -) = Timetable( - studentId = 0, - subject = "", - number = 0, - diaryId = 0, - canceled = canceled, - changes = false, - date = LocalDate.now(), - end = end, - group = "", - info = "", - isStudentPlan = isStudentPlan, - room = "", - roomOld = "", +fun getSemesterEntity(diaryId: Int = 1, semesterId: Int = 1, start: LocalDate = LocalDate.now(), end: LocalDate = LocalDate.now(), semesterName: Int = 1) = Semester( + studentId = 1, + diaryId = diaryId, + semesterId = semesterId, + diaryName = "$semesterId", + schoolYear = 1970, + classId = 0, + semesterName = semesterName, + unitId = 1, start = start, - subjectOld = "", - teacher = "", - teacherOld = "" + end = end ) -fun getMessageEntity( - messageId: Int, - content: String, - unread: Boolean -) = Message( +fun getSemesterPojo(diaryId: Int, semesterId: Int, start: LocalDate, end: LocalDate, semesterName: Int = 1) = SdkSemester( + diaryId = diaryId, + semesterId = semesterId, + diaryName = "$semesterId", + schoolYear = 1970, + classId = 0, + semesterNumber = semesterName, + unitId = 1, + start = start, + end = end, +) + +fun getStudentEntity(mode: Sdk.Mode = Sdk.Mode.API) = Student( + scrapperBaseUrl = "http://fakelog.cf", + email = "jan@fakelog.cf", + certificateKey = "", + classId = 0, + className = "", + isCurrent = false, + isParent = false, + loginMode = mode.name, + loginType = "STANDARD", + mobileBaseUrl = "", + password = "", + privateKey = "", + registrationDate = now(), + schoolName = "", + schoolShortName = "test", + schoolSymbol = "", studentId = 1, - realId = 1, - messageId = messageId, - sender = "", - senderId = 1, - recipient = "", - subject = "", - date = now(), - folderId = 1, - unread = unread, - removed = false, - hasAttachments = false + studentName = "", + symbol = "", + userLoginId = 1, + userName = "", ).apply { - this.content = content - unreadBy = 1 - readBy = 1 + id = 1 } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt new file mode 100644 index 000000000..c6c3613e5 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/AttendanceRepositoryTest.kt @@ -0,0 +1,148 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate +import java.time.LocalDate.of +import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance + +class AttendanceRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var attendanceDb: AttendanceDao + + private val semester = getSemesterEntity() + + private val student = getStudentEntity() + + private lateinit var attendanceRepository: AttendanceRepository + + private val remoteList = listOf( + getAttendanceRemote(of(2021, 1, 4)), + getAttendanceRemote(of(2021, 1, 7)) + ) + + private val startDate = of(2021, 1, 4) + + private val endDate = of(2021, 1, 10) + + @Before + fun setUp() { + MockKAnnotations.init(this) + + attendanceRepository = AttendanceRepository(attendanceDb, sdk) + } + + @Test + fun `force refresh without difference`() { + // prepare + coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList + coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { attendanceDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.size) + coVerify { sdk.getAttendance(startDate, endDate, 1) } + coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } + coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } + coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in remote`() { + // prepare + coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList + coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.dropLast(1).mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { attendanceDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.size) + coVerify { sdk.getAttendance(startDate, endDate, 1) } + coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } + coVerify { + attendanceDb.insertAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in local`() { + // prepare + coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1) + coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.dropLast(1).mapToEntities(semester)) + ) + coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { attendanceDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(1, res.data?.size) + coVerify { sdk.getAttendance(startDate, endDate, 1) } + coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } + coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } + coVerify { + attendanceDb.deleteAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + } + + private fun getAttendanceRemote(date: LocalDate) = SdkAttendance( + subject = "Fizyka", + name = "Obecność", + date = date, + timeId = 0, + number = 0, + deleted = false, + excusable = false, + excused = false, + exemption = false, + lateness = false, + presence = false, + categoryId = 1, + absence = false, + excuseStatus = null + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt new file mode 100644 index 000000000..7b20e6474 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/CompletedLessonsRepositoryTest.kt @@ -0,0 +1,143 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.CompletedLessonsDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate +import java.time.LocalDate.of +import io.github.wulkanowy.sdk.pojo.CompletedLesson as SdkCompletedLesson + +class CompletedLessonsRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var completedLessonDb: CompletedLessonsDao + + private val semester = getSemesterEntity() + + private val student = getStudentEntity() + + private lateinit var completedLessonRepository: CompletedLessonsRepository + + private val remoteList = listOf( + getCompletedLesson(of(2021, 1, 4)), + getCompletedLesson(of(2021, 1, 7)) + ) + + private val startDate = of(2021, 1, 4) + + private val endDate = of(2021, 1, 10) + + @Before + fun initApi() { + MockKAnnotations.init(this) + + completedLessonRepository = CompletedLessonsRepository(completedLessonDb, sdk) + } + + @Test + fun `force refresh without difference`() { + // prepare + coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList + coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { completedLessonDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.size) + coVerify { sdk.getCompletedLessons(startDate, endDate) } + coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } + coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } + coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in remote`() { + // prepare + coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList + coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.dropLast(1).mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { completedLessonDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.size) + coVerify { sdk.getCompletedLessons(startDate, endDate) } + coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } + coVerify { + completedLessonDb.insertAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in local`() { + // prepare + coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList.dropLast(1) + coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.dropLast(1).mapToEntities(semester)) + ) + coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { completedLessonDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(1, res.data?.size) + coVerify { sdk.getCompletedLessons(startDate, endDate) } + coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } + coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) } + coVerify { + completedLessonDb.deleteAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + } + + private fun getCompletedLesson(date: LocalDate) = SdkCompletedLesson( + date = date, + subject = "", + absence = "", + resources = "", + substitution = "", + teacherSymbol = "", + teacher = "", + topic = "", + number = 1 + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt new file mode 100644 index 000000000..5966bc423 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/ExamRemoteTest.kt @@ -0,0 +1,143 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.ExamDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate +import io.github.wulkanowy.sdk.pojo.Exam as SdkExam + +class ExamRemoteTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var examDb: ExamDao + + private val semester = getSemesterEntity() + + private val student = getStudentEntity() + + private lateinit var examRepository: ExamRepository + + private val remoteList = listOf( + getExam(LocalDate.of(2021, 1, 4)), + getExam(LocalDate.of(2021, 1, 7)) + ) + + private val startDate = LocalDate.of(2021, 1, 4) + + private val endDate = LocalDate.of(2021, 1, 10) + + private val realEndDate = LocalDate.of(2021, 1, 31) + + @Before + fun setUp() { + MockKAnnotations.init(this) + + examRepository = ExamRepository(examDb, sdk) + } + + @Test + fun `force refresh without difference`() { + // prepare + coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList + coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { examDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.size) + coVerify { sdk.getExams(startDate, realEndDate, 1) } + coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } + coVerify { examDb.insertAll(match { it.isEmpty() }) } + coVerify { examDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in remote`() { + // prepare + coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList + coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( + flowOf(remoteList.dropLast(1).mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { examDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.size) + coVerify { sdk.getExams(startDate, realEndDate, 1) } + coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } + coVerify { + examDb.insertAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + coVerify { examDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in local`() { + // prepare + coEvery { sdk.getExams(startDate, realEndDate, 1) } returns remoteList.dropLast(1) + coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.dropLast(1).mapToEntities(semester)) + ) + coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { examDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(1, res.data?.size) + coVerify { sdk.getExams(startDate, realEndDate, 1) } + coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } + coVerify { examDb.insertAll(match { it.isEmpty() }) } + coVerify { + examDb.deleteAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + } + + private fun getExam(date: LocalDate) = SdkExam( + subject = "", + group = "", + type = "", + description = "", + teacher = "", + teacherSymbol = "", + date = date, + entryDate = date + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt new file mode 100644 index 000000000..d6516a357 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeRepositoryTest.kt @@ -0,0 +1,254 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.GradeDao +import io.github.wulkanowy.data.db.dao.GradeSummaryDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.time.LocalDate +import java.time.LocalDate.of +import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade + +class GradeRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var gradeDb: GradeDao + + @MockK + private lateinit var gradeSummaryDb: GradeSummaryDao + + private val semester = getSemesterEntity() + + private val student = getStudentEntity() + + private lateinit var gradeRepository: GradeRepository + + @Before + fun initApi() { + MockKAnnotations.init(this) + + gradeRepository = GradeRepository(gradeDb, gradeSummaryDb, sdk) + + coEvery { gradeDb.deleteAll(any()) } just Runs + coEvery { gradeDb.insertAll(any()) } returns listOf() + + coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf(flowOf(listOf()), flowOf(listOf())) + coEvery { gradeSummaryDb.deleteAll(any()) } just Runs + coEvery { gradeSummaryDb.insertAll(any()) } returns listOf() + } + + @Test + fun `mark grades older than registration date as read`() { + // prepare + val boundaryDate = of(2019, 2, 27).atStartOfDay() + val remoteList = listOf( + createGradeApi(5, 4.0, of(2019, 2, 25), "Ocena pojawiła się"), + createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"), + createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"), + createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza") + ) + coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + + coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( + flowOf(listOf()), // empty because it is new user + flowOf(remoteList.mapToEntities(semester)) + ) + + // execute + val res = runBlocking { gradeRepository.getGrades(student.copy(registrationDate = boundaryDate), semester, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(4, res.data?.first?.size) + coVerify { + gradeDb.insertAll(withArg { + assertEquals(4, it.size) + assertTrue(it[0].isRead) + assertTrue(it[1].isRead) + assertFalse(it[2].isRead) + assertFalse(it[3].isRead) + }) + } + } + + @Test + fun `mitigate mark grades as unread when old grades changed`() { + // prepare + val remoteList = listOf( + createGradeApi(5, 2.0, of(2019, 2, 25), "Ocena ma datę, jest inna, ale nie zostanie powiadomiona"), + createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"), + createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"), + createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa") + ) + coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + + val localList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Jedna ocena"), + createGradeApi(4, 4.0, of(2019, 2, 26), "Druga"), + createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie") + ) + coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( + flowOf(localList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + + // execute + val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(4, res.data?.first?.size) + coVerify { + gradeDb.insertAll(withArg { + assertEquals(3, it.size) + assertTrue(it[0].isRead) + assertTrue(it[1].isRead) + assertFalse(it[2].isRead) + assertEquals(remoteList.mapToEntities(semester).last(), it[2]) + }) + } + coVerify { + gradeDb.deleteAll(withArg { + assertEquals(2, it.size) + }) + } + } + + @Test + fun `force refresh when local contains duplicated grades`() { + // prepare + val remoteList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + ) + coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + + val localList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), // will be removed... + createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + ) + coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( + flowOf(localList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + + // execute + val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.first?.size) + coVerify { gradeDb.insertAll(match { it.isEmpty() }) } + coVerify { gradeDb.deleteAll(match { it.size == 1 }) } // ... here + } + + @Test + fun `force refresh when remote contains duplicated grades`() { + // prepare + val remoteList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), // will be added... + createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + ) + coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + + val localList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + ) + coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( + flowOf(localList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + + // execute + val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(3, res.data?.first?.size) + coVerify { gradeDb.insertAll(match { it.size == 1 }) } // ... here + coVerify { gradeDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh when local is empty`() { + // prepare + val remoteList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + ) + coEvery { sdk.getGrades(1) } returns (remoteList to emptyList()) + + coEvery { gradeDb.loadAll(1, 1) } returnsMany listOf( + flowOf(listOf()), + flowOf(remoteList.mapToEntities(semester)) + ) + + // execute + val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(3, res.data?.first?.size) + } + + @Test + fun `force refresh when remote is empty`() { + // prepare + val remoteList = emptyList() + coEvery { sdk.getGrades(semester.semesterId) } returns (remoteList to emptyList()) + + val localList = listOf( + createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), + createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena") + ) + coEvery { gradeDb.loadAll(semester.semesterId, student.studentId) } returnsMany listOf( + flowOf(localList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)), + ) + + // execute + val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(0, res.data?.first?.size) + } + + private fun createGradeApi(value: Int, weight: Double, date: LocalDate, desc: String) = SdkGrade( + subject = "", + color = "", + comment = "", + date = date, + description = desc, + entry = "", + modifier = .0, + symbol = "", + teacher = "", + value = value.toDouble(), + weight = weight.toString(), + weightValue = weight + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt new file mode 100644 index 000000000..254155623 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/GradeStatisticsRepositoryTest.kt @@ -0,0 +1,94 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao +import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao +import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem +import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class GradeStatisticsRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var gradePartialStatisticsDb: GradePartialStatisticsDao + + @MockK + private lateinit var gradePointsStatisticsDb: GradePointsStatisticsDao + + @MockK + private lateinit var gradeSemesterStatisticsDb: GradeSemesterStatisticsDao + + private val semester = getSemesterEntity() + + private val student = getStudentEntity() + + private lateinit var gradeStatisticsRepository: GradeStatisticsRepository + + private val remotePartialList = listOf( + getGradeStatisticsPartialSubject("Fizyka"), + getGradeStatisticsPartialSubject("Matematyka") + ) + + @Before + fun setUp() { + MockKAnnotations.init(this) + + gradeStatisticsRepository = GradeStatisticsRepository(gradePartialStatisticsDb, gradePointsStatisticsDb, gradeSemesterStatisticsDb, sdk) + } + + @Test + fun `force refresh without difference`() { + // prepare + coEvery { sdk.getGradesPartialStatistics(1) } returns remotePartialList + coEvery { gradePartialStatisticsDb.loadAll(1, 1) } returnsMany listOf( + flowOf(remotePartialList.mapToEntities(semester)), + flowOf(remotePartialList.mapToEntities(semester)) + ) + coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { gradeStatisticsRepository.getGradesPartialStatistics(student, semester, "Wszystkie", true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2 + 1, res.data?.size) + coVerify { sdk.getGradesPartialStatistics(1) } + coVerify { gradePartialStatisticsDb.loadAll(1, 1) } + coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } + coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) } + } + + private fun getGradeStatisticsPartialSubject(subjectName: String) = GradeStatisticsSubject( + subject = subjectName, + studentAverage = "", + classAverage = "", + classItems = listOf( + GradeStatisticsItem( + subject = subjectName, + grade = 0, + amount = 0 + ) + ), + studentItems = listOf() + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt new file mode 100644 index 000000000..9cbad8ac5 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/LuckyNumberRemoteTest.kt @@ -0,0 +1,124 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.LuckyNumberDao +import io.github.wulkanowy.data.mappers.mapToEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate +import io.github.wulkanowy.sdk.pojo.LuckyNumber as SdkLuckyNumber + +class LuckyNumberRemoteTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var luckyNumberDb: LuckyNumberDao + + private val student = getStudentEntity() + + private lateinit var luckyNumberRepository: LuckyNumberRepository + + private val luckyNumber = SdkLuckyNumber("", "", 14) + + private val date = LocalDate.now() + + @Before + fun setUp() { + MockKAnnotations.init(this) + + luckyNumberRepository = LuckyNumberRepository(luckyNumberDb, sdk) + } + + @Test + fun `force refresh without difference`() { + // prepare + coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber + coEvery { luckyNumberDb.load(1, date) } returnsMany listOf( + flowOf(luckyNumber.mapToEntity(student)), + flowOf(luckyNumber.mapToEntity(student)) + ) + coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { luckyNumberDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(luckyNumber.number, res.data?.luckyNumber) + coVerify { sdk.getLuckyNumber(student.schoolShortName) } + coVerify { luckyNumberDb.load(1, date) } + coVerify(exactly = 0) { luckyNumberDb.insertAll(any()) } + coVerify(exactly = 0) { luckyNumberDb.deleteAll(any()) } + } + + @Test + fun `force refresh with different item on remote`() { + // prepare + coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber + coEvery { luckyNumberDb.load(1, date) } returnsMany listOf( + flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), + flowOf(luckyNumber.mapToEntity(student)) + ) + coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { luckyNumberDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(luckyNumber.number, res.data?.luckyNumber) + coVerify { sdk.getLuckyNumber(student.schoolShortName) } + coVerify { luckyNumberDb.load(1, date) } + coVerify { + luckyNumberDb.insertAll(match { + it.size == 1 && it[0] == luckyNumber.mapToEntity(student) + }) + } + coVerify { luckyNumberDb.deleteAll(match { + it.size == 1 && it[0] == luckyNumber.mapToEntity(student).copy(luckyNumber = 6666) + }) } + } + + @Test + fun `force refresh no local item`() { + // prepare + coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber + coEvery { luckyNumberDb.load(1, date) } returnsMany listOf( + flowOf(null), + flowOf(luckyNumber.mapToEntity(student)) + ) + coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { luckyNumberDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(luckyNumber.number, res.data?.luckyNumber) + coVerify { sdk.getLuckyNumber(student.schoolShortName) } + coVerify { luckyNumberDb.load(1, date) } + coVerify { + luckyNumberDb.insertAll(match { + it.size == 1 && it[0] == luckyNumber.mapToEntity(student) + }) + } + coVerify(exactly = 0) { luckyNumberDb.deleteAll(any()) } + } +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt new file mode 100644 index 000000000..1149a2f0a --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MessageRepositoryTest.kt @@ -0,0 +1,133 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.db.dao.MessageAttachmentDao +import io.github.wulkanowy.data.db.dao.MessagesDao +import io.github.wulkanowy.data.db.entities.Message +import io.github.wulkanowy.data.db.entities.MessageWithAttachment +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.MessageDetails +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.net.UnknownHostException +import java.time.LocalDateTime + +class MessageRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var messageDb: MessagesDao + + @MockK + private lateinit var messageAttachmentDao: MessageAttachmentDao + + private val student = getStudentEntity() + + private lateinit var messageRepository: MessageRepository + + @Before + fun setUp() { + MockKAnnotations.init(this) + + messageRepository = MessageRepository(messageDb, messageAttachmentDao, sdk) + } + + @Test(expected = NoSuchElementException::class) + fun `throw error when message is not in the db`() { + val testMessage = getMessageEntity(1, "", false) + coEvery { messageDb.loadMessageWithAttachment(1, 1) } throws NoSuchElementException("No message in database") + + runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + } + + @Test + fun `get message when content already in db`() { + val testMessage = getMessageEntity(123, "Test", false) + val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) + + coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } returns flowOf(messageWithAttachment) + + val res = runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + + assertEquals(null, res.error) + assertEquals(Status.SUCCESS, res.status) + assertEquals("Test", res.data!!.message.content) + } + + @Test + fun `get message when content in db is empty`() { + val testMessage = getMessageEntity(123, "", true) + val testMessageWithContent = testMessage.copy().apply { content = "Test" } + + val mWa = MessageWithAttachment(testMessage, emptyList()) + val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList()) + + coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent)) + coEvery { sdk.getMessageDetails(testMessage.messageId, 1, false, testMessage.realId) } returns MessageDetails("Test", emptyList()) + coEvery { messageDb.updateAll(any()) } just Runs + coEvery { messageAttachmentDao.insertAttachments(any()) } returns listOf(1) + + val res = runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + + assertEquals(null, res.error) + assertEquals(Status.SUCCESS, res.status) + assertEquals("Test", res.data!!.message.content) + coVerify { messageDb.updateAll(listOf(testMessageWithContent)) } + } + + @Test(expected = UnknownHostException::class) + fun `get message when content in db is empty and there is no internet connection`() { + val testMessage = getMessageEntity(123, "", false) + + coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } throws UnknownHostException() + + runBlocking { messageRepository.getMessage(student, testMessage).toFirstResult() } + } + + @Test(expected = UnknownHostException::class) + fun `get message when content in db is empty, unread and there is no internet connection`() { + val testMessage = getMessageEntity(123, "", true) + + coEvery { messageDb.loadMessageWithAttachment(1, testMessage.messageId) } throws UnknownHostException() + + runBlocking { messageRepository.getMessage(student, testMessage).toList()[1] } + } + + private fun getMessageEntity( + messageId: Int, + content: String, + unread: Boolean + ) = Message( + studentId = 1, + realId = 1, + messageId = messageId, + sender = "", + senderId = 1, + recipient = "", + subject = "", + date = LocalDateTime.now(), + folderId = 1, + unread = unread, + removed = false, + hasAttachments = false + ).apply { + this.content = content + unreadBy = 1 + readBy = 1 + } +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt new file mode 100644 index 000000000..f4d6dd4ff --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/MobileDeviceRepositoryTest.kt @@ -0,0 +1,134 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.MobileDeviceDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.sdk.pojo.Device +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.time.LocalDateTime.of + +class MobileDeviceRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var mobileDeviceDb: MobileDeviceDao + + private val semester = getSemesterEntity() + + private val student = getStudentEntity() + + private lateinit var mobileDeviceRepository: MobileDeviceRepository + + private val remoteList = listOf( + getDevicePojo(1), + getDevicePojo(2) + ) + + @Before + fun initTest() { + MockKAnnotations.init(this) + + mobileDeviceRepository = MobileDeviceRepository(mobileDeviceDb, sdk) + } + + @Test + fun `force refresh without difference`() { + // prepare + coEvery { sdk.getRegisteredDevices() } returns remoteList + coEvery { mobileDeviceDb.loadAll(student.studentId) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { mobileDeviceDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } + + // verify + Assert.assertEquals(null, res.error) + Assert.assertEquals(2, res.data?.size) + coVerify { sdk.getRegisteredDevices() } + coVerify { mobileDeviceDb.loadAll(1) } + coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } + coVerify { mobileDeviceDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in remote`() { + // prepare + coEvery { sdk.getRegisteredDevices() } returns remoteList + coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf( + flowOf(remoteList.dropLast(1).mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { mobileDeviceDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } + + // verify + Assert.assertEquals(null, res.error) + Assert.assertEquals(2, res.data?.size) + coVerify { sdk.getRegisteredDevices() } + coVerify { mobileDeviceDb.loadAll(1) } + coVerify { + mobileDeviceDb.insertAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + coVerify { mobileDeviceDb.deleteAll(match { it.isEmpty() }) } + } + + @Test + fun `force refresh with more items in local`() { + // prepare + coEvery { sdk.getRegisteredDevices() } returns remoteList.dropLast(1) + coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.dropLast(1).mapToEntities(semester)) + ) + coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { mobileDeviceDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } + + // verify + Assert.assertEquals(null, res.error) + Assert.assertEquals(1, res.data?.size) + coVerify { sdk.getRegisteredDevices() } + coVerify { mobileDeviceDb.loadAll(1) } + coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) } + coVerify { + mobileDeviceDb.deleteAll(match { + it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] + }) + } + } + + private fun getDevicePojo(day: Int) = Device( + id = 0, + name = "", + deviceId = "", + createDate = of(2019, 5, day, 0, 0, 0), + modificationDate = of(2019, 5, day, 0, 0, 0) + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt new file mode 100644 index 000000000..1e645f305 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/RecipientLocalTest.kt @@ -0,0 +1,85 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.RecipientDao +import io.github.wulkanowy.data.db.entities.ReportingUnit +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient + +class RecipientLocalTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var recipientDb: RecipientDao + + private val student = getStudentEntity() + + private lateinit var recipientRepository: RecipientRepository + + private val remoteList = listOf( + SdkRecipient("2rPracownik", "Kowalski Jan", 3, 4, 2, "hash", "Kowalski Jan [KJ] - Pracownik (Fake123456)"), + SdkRecipient("3rPracownik", "Kowalska Karolina", 4, 4, 2, "hash", "Kowalska Karolina [KK] - Pracownik (Fake123456)"), + SdkRecipient("4rPracownik", "Krupa Stanisław", 5, 4, 1, "hash", "Krupa Stanisław [KS] - Uczeń (Fake123456)") + ) + + @Before + fun setUp() { + MockKAnnotations.init(this) + + recipientRepository = RecipientRepository(recipientDb, sdk) + } + + @Test + fun `load recipients when items already in database`() { + // prepare + coEvery { recipientDb.loadAll(1, 1, 1) } returnsMany listOf( + remoteList.mapToEntities(student), + remoteList.mapToEntities(student) + ) + coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { recipientDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { recipientRepository.getRecipients(student, 1, ReportingUnit(1, 1, "", 1, "", listOf())) } + + // verify + assertEquals(3, res.size) + coVerify { recipientDb.loadAll(1, 1, 1) } + } + + @Test + fun `load recipients when database is empty`() { + // prepare + coEvery { sdk.getRecipients(1, 1) } returns remoteList + coEvery { recipientDb.loadAll(1, 1, 1) } returnsMany listOf( + emptyList(), + remoteList.mapToEntities(student) + ) + coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { recipientDb.deleteAll(any()) } just Runs + + // execute + val res = runBlocking { recipientRepository.getRecipients(student, 1, ReportingUnit(1, 1, "", 1, "", listOf())) } + + // verify + assertEquals(3, res.size) + coVerify { sdk.getRecipients(1, 1) } + coVerify { recipientDb.loadAll(1, 1, 1) } + coVerify { recipientDb.insertAll(match { it.isEmpty() }) } + coVerify { recipientDb.deleteAll(match { it.isEmpty() }) } + } +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt new file mode 100644 index 000000000..0ed008851 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/SemesterRepositoryTest.kt @@ -0,0 +1,221 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.TestDispatchersProvider +import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getSemesterPojo +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate.now + +class SemesterRepositoryTest { + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var semesterDb: SemesterDao + + private val student = getStudentEntity() + + private lateinit var semesterRepository: SemesterRepository + + @Before + fun initTest() { + MockKAnnotations.init(this) + + semesterRepository = SemesterRepository(semesterDb, sdk, TestDispatchersProvider()) + } + + @Test + fun getSemesters_noSemesters() { + val semesters = listOf( + getSemesterPojo(1, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterPojo(1, 2, now().minusMonths(3), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + coEvery { sdk.getSemesters() } returns semesters + coEvery { semesterDb.deleteAll(any()) } just Runs + coEvery { semesterDb.insertSemesters(any()) } returns emptyList() + + runBlocking { semesterRepository.getSemesters(student) } + + coVerify { semesterDb.insertSemesters(semesters.mapToEntities(student.studentId)) } + coVerify { semesterDb.deleteAll(emptyList()) } + } + + @Test + fun getSemesters_invalidDiary_api() { + val badSemesters = listOf( + getSemesterPojo(0, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterPojo(0, 2, now().minusMonths(3), now()) + ) + + val goodSemesters = listOf( + getSemesterPojo(122, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterPojo(123, 2, now().minusMonths(3), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns badSemesters.mapToEntities(student.studentId) + coEvery { sdk.getSemesters() } returns goodSemesters + coEvery { semesterDb.deleteAll(any()) } just Runs + coEvery { semesterDb.insertSemesters(any()) } returns listOf() + + val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.API.name)) } + assertEquals(2, items.size) + assertEquals(0, items[0].diaryId) + } + + @Test + fun getSemesters_invalidDiary_scrapper() { + val badSemesters = listOf( + getSemesterPojo(0, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterPojo(0, 2, now().minusMonths(3), now()) + ) + + val goodSemesters = listOf( + getSemesterPojo(1, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterPojo(1, 2, now().minusMonths(3), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returnsMany listOf( + badSemesters.mapToEntities(student.studentId), + badSemesters.mapToEntities(student.studentId), + goodSemesters.mapToEntities(student.studentId) + ) + coEvery { sdk.getSemesters() } returns goodSemesters + coEvery { semesterDb.deleteAll(any()) } just Runs + coEvery { semesterDb.insertSemesters(any()) } returns listOf() + + val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.SCRAPPER.name)) } + assertEquals(2, items.size) + assertNotEquals(0, items[0].diaryId) + } + + @Test + fun getSemesters_noCurrent() { + val semesters = listOf( + getSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), + getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + + val items = runBlocking { semesterRepository.getSemesters(student) } + assertEquals(2, items.size) + } + + @Test + fun getSemesters_oneCurrent() { + val semesters = listOf( + getSemesterEntity(1, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterEntity(1, 2, now().minusMonths(3), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + + val items = runBlocking { semesterRepository.getSemesters(student) } + assertEquals(2, items.size) + } + + @Test + fun getSemesters_doubleCurrent() { + val semesters = listOf( + getSemesterEntity(1, 1, now(), now()), + getSemesterEntity(1, 2, now(), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + + val items = runBlocking { semesterRepository.getSemesters(student) } + assertEquals(2, items.size) + } + + @Test + fun getSemesters_noSemesters_refreshOnNoCurrent() { + val semesters = listOf( + getSemesterPojo(1, 1, now().minusMonths(6), now().minusMonths(3)), + getSemesterPojo(1, 2, now().minusMonths(3), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + coEvery { sdk.getSemesters() } returns semesters + coEvery { semesterDb.deleteAll(any()) } just Runs + coEvery { semesterDb.insertSemesters(any()) } returns listOf() + + runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } + + coVerify { semesterDb.deleteAll(emptyList()) } + coVerify { semesterDb.insertSemesters(semesters.mapToEntities(student.studentId)) } + } + + @Test + fun getSemesters_noCurrent_refreshOnNoCurrent() { + val semestersWithNoCurrent = listOf( + getSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), + getSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) + ) + + val newSemesters = listOf( + getSemesterPojo(1, 1, now().minusMonths(12), now().minusMonths(6)), + getSemesterPojo(1, 2, now().minusMonths(6), now().minusMonths(1)), + + getSemesterPojo(2, 1, now().minusMonths(1), now().plusMonths(5)), + getSemesterPojo(2, 2, now().plusMonths(5), now().plusMonths(11)), + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semestersWithNoCurrent + coEvery { sdk.getSemesters() } returns newSemesters + coEvery { semesterDb.deleteAll(any()) } just Runs + coEvery { semesterDb.insertSemesters(any()) } returns listOf() + + val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } + assertEquals(2, items.size) + } + + @Test + fun getSemesters_doubleCurrent_refreshOnNoCurrent() { + val semesters = listOf( + getSemesterEntity(1, 1, now(), now()), + getSemesterEntity(1, 2, now(), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + + val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } + assertEquals(2, items.size) + } + + @Test(expected = IllegalArgumentException::class) + fun getCurrentSemester_doubleCurrent() { + val semesters = listOf( + getSemesterEntity(1, 1, now(), now()), + getSemesterEntity(1, 1, now(), now()) + ) + + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semesters + + runBlocking { semesterRepository.getCurrentSemester(student) } + } + + @Test(expected = RuntimeException::class) + fun getCurrentSemester_emptyList() { + coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() + + runBlocking { semesterRepository.getCurrentSemester(student) } + } +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/student/StudentRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt similarity index 69% rename from app/src/test/java/io/github/wulkanowy/data/repositories/student/StudentRemoteTest.kt rename to app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt index 47a10772e..22669323e 100644 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/student/StudentRemoteTest.kt +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/StudentTest.kt @@ -1,30 +1,43 @@ -package io.github.wulkanowy.data.repositories.student +package io.github.wulkanowy.data.repositories +import io.github.wulkanowy.TestDispatchersProvider +import io.github.wulkanowy.data.db.dao.SemesterDao +import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.pojo.Student import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK +import io.mockk.mockk import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -class StudentRemoteTest { +class StudentTest { @MockK private lateinit var mockSdk: Sdk + @MockK + private lateinit var studentDb: StudentDao + + @MockK + private lateinit var semesterDb: SemesterDao + + private lateinit var studentRepository: StudentRepository + @Before fun initApi() { MockKAnnotations.init(this) + studentRepository = StudentRepository(mockk(), TestDispatchersProvider(), studentDb, semesterDb, mockSdk) } @Test fun testRemoteAll() { coEvery { mockSdk.getStudentsFromScrapper(any(), any(), any(), any()) } returns listOf(getStudent("test")) - val students = runBlocking { StudentRemote(mockSdk).getStudentsScrapper("", "", "http://fakelog.cf", "") } + val students = runBlocking { studentRepository.getStudentsScrapper("", "", "http://fakelog.cf", "") } assertEquals(1, students.size) assertEquals("test Kowalski", students.first().student.studentName) } diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt new file mode 100644 index 000000000..20826f001 --- /dev/null +++ b/app/src/test/java/io/github/wulkanowy/data/repositories/TimetableRepositoryTest.kt @@ -0,0 +1,224 @@ +package io.github.wulkanowy.data.repositories + +import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao +import io.github.wulkanowy.data.db.dao.TimetableDao +import io.github.wulkanowy.data.mappers.mapToEntities +import io.github.wulkanowy.getSemesterEntity +import io.github.wulkanowy.getStudentEntity +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper +import io.github.wulkanowy.utils.toFirstResult +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.SpyK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalDateTime.of +import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable + +class TimetableRepositoryTest { + + @MockK(relaxed = true) + private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper + + @SpyK + private var sdk = Sdk() + + @MockK + private lateinit var timetableDb: TimetableDao + + @MockK + private lateinit var timetableAdditionalDao: TimetableAdditionalDao + + private val student = getStudentEntity() + + private val semester = getSemesterEntity() + + private lateinit var timetableRepository: TimetableRepository + + private val startDate = LocalDate.of(2021, 1, 4) + + private val endDate = LocalDate.of(2021, 1, 10) + + @Before + fun initApi() { + MockKAnnotations.init(this) + timetableRepository = TimetableRepository(timetableDb, timetableAdditionalDao, sdk, timetableNotificationSchedulerHelper) + } + + @Test + fun copyRoomToCompletedFromPrevious() { + // prepare + val remoteList = listOf( + createTimetableRemote(of(2021, 1, 4, 8, 0), 1, "", "Przyroda"), + createTimetableRemote(of(2021, 1, 4, 8, 50), 2, "", "Religia"), + createTimetableRemote(of(2021, 1, 4, 9, 40), 3, "", "W-F"), + createTimetableRemote(of(2021, 1, 4, 10, 30), 4, "", "W-F") + ) + coEvery { sdk.getTimetable(any(), any()) } returns (remoteList to emptyList()) + + val localList = listOf( + createTimetableRemote(of(2021, 1, 4, 8, 0), 1, "123", "Przyroda"), + createTimetableRemote(of(2021, 1, 4, 8, 50), 2, "321", "Religia"), + createTimetableRemote(of(2021, 1, 4, 9, 40), 3, "213", "W-F"), + createTimetableRemote(of(2021, 1, 4, 10, 30), 3, "213", "W-F", "Jan Kowalski") + ) + coEvery { timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) } returns flowOf(localList.mapToEntities(semester)) + coEvery { timetableDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { timetableDb.deleteAll(any()) } just Runs + + coEvery { timetableAdditionalDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf()) + coEvery { timetableAdditionalDao.insertAll(emptyList()) } returns listOf(1, 2, 3) + coEvery { timetableAdditionalDao.deleteAll(emptyList()) } just Runs + + // execute + val res = runBlocking { + timetableRepository.getTimetable(student, semester, startDate, endDate, true).toFirstResult() + } + + // verify + assertEquals(4, res.data?.first.orEmpty().size) + coVerify { + timetableDb.insertAll(withArg { + assertEquals(4, it.size) + assertEquals("123", it[0].room) + assertEquals("321", it[1].room) + assertEquals("213", it[2].room) + }) + } + coVerify { timetableDb.deleteAll(match { it.size == 4 }) } + } + + @Test + fun copyTeacherToCompletedFromPrevious() { + // prepare + val remoteList = listOf( + createTimetableRemote(of(2021, 1, 4, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), // skip + createTimetableRemote(of(2021, 1, 4, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true), + createTimetableRemote(of(2021, 1, 4, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false), + createTimetableRemote(of(2021, 1, 4, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), // skip + + createTimetableRemote(of(2021, 1, 5, 8, 0), 1, "123", "Język polski", "", false), + createTimetableRemote(of(2021, 1, 5, 8, 50), 2, "124", "Język polski", "", true), + createTimetableRemote(of(2021, 1, 5, 9, 40), 3, "125", "Język polski", "", false), + createTimetableRemote(of(2021, 1, 5, 10, 40), 4, "126", "Język polski", "", true), + + createTimetableRemote(of(2021, 1, 6, 8, 0), 1, "123", "Matematyka", "Paweł Środowski", false), + createTimetableRemote(of(2021, 1, 6, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true), + createTimetableRemote(of(2021, 1, 6, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false), + createTimetableRemote(of(2021, 1, 6, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true) + ) + coEvery { sdk.getTimetable(startDate, endDate) } returns (remoteList to emptyList()) + + val localList = listOf( + createTimetableRemote(of(2021, 1, 4, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false), + createTimetableRemote(of(2021, 1, 4, 8, 50), 2, "124", "Matematyka", "Paweł Poniedziałkowski", false), + createTimetableRemote(of(2021, 1, 4, 9, 40), 3, "125", "Język polski", "Joanna Wtorkowska", true), + createTimetableRemote(of(2021, 1, 4, 10, 40), 4, "126", "Język polski", "Joanna Wtorkowska", true), + + createTimetableRemote(of(2021, 1, 5, 8, 0), 1, "123", "Język polski", "Joanna Wtorkowska", false), + createTimetableRemote(of(2021, 1, 5, 8, 50), 2, "124", "Język polski", "Joanna Wtorkowska", false), + createTimetableRemote(of(2021, 1, 5, 9, 40), 3, "125", "Język polski", "Joanna Środowska", true), + createTimetableRemote(of(2021, 1, 5, 10, 40), 4, "126", "Język polski", "Joanna Środowska", true), + + createTimetableRemote(of(2021, 1, 6, 8, 0), 1, "123", "Matematyka", "", false), + createTimetableRemote(of(2021, 1, 6, 8, 50), 2, "124", "Matematyka", "", false), + createTimetableRemote(of(2021, 1, 6, 9, 40), 3, "125", "Matematyka", "", true), + createTimetableRemote(of(2021, 1, 6, 10, 40), 4, "126", "Matematyka", "", true) + ) + coEvery { timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) } returns flowOf(localList.mapToEntities(semester)) + coEvery { timetableDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { timetableDb.deleteAll(any()) } just Runs + + coEvery { timetableAdditionalDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf()) + coEvery { timetableAdditionalDao.insertAll(emptyList()) } returns listOf(1, 2, 3) + coEvery { timetableAdditionalDao.deleteAll(emptyList()) } just Runs + + // execute + val res = runBlocking { timetableRepository.getTimetable(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(12, res.data!!.first.size) + + coVerify { + timetableDb.insertAll(withArg { + assertEquals(10, it.size) +// assertEquals("Paweł Poniedziałkowski", it[0].teacher) // skip + assertEquals("Jakub Wtorkowski", it[0].teacher) + assertEquals("Joanna Poniedziałkowska", it[1].teacher) +// assertEquals("Joanna Wtorkowska", it[3].teacher) // skip + + assertEquals("Joanna Wtorkowska", it[2].teacher) + assertEquals("", it[3].teacher) + assertEquals("", it[4].teacher) + assertEquals("", it[5].teacher) + + assertEquals("Paweł Środowski", it[6].teacher) + assertEquals("Paweł Czwartkowski", it[7].teacher) + assertEquals("Paweł Środowski", it[8].teacher) + assertEquals("Paweł Czwartkowski", it[9].teacher) + }) + } + coVerify { timetableDb.deleteAll(match { it.size == 10 }) } + } + + @Test + fun `force refresh without difference`() { + val remoteList = listOf( + createTimetableRemote(of(2021, 1, 4, 8, 0), 1, "123", "Język polski", "", false), + createTimetableRemote(of(2021, 1, 4, 8, 50), 2, "124", "Język polski", "", true) + ) + + // prepare + coEvery { sdk.getTimetable(startDate, endDate) } returns (remoteList to emptyList()) + coEvery { timetableDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( + flowOf(remoteList.mapToEntities(semester)), + flowOf(remoteList.mapToEntities(semester)) + ) + coEvery { timetableDb.insertAll(any()) } returns listOf(1, 2, 3) + coEvery { timetableDb.deleteAll(any()) } just Runs + + coEvery { timetableAdditionalDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf()) + coEvery { timetableAdditionalDao.deleteAll(emptyList()) } just Runs + coEvery { timetableAdditionalDao.insertAll(emptyList()) } returns listOf(1, 2, 3) + + // execute + val res = runBlocking { timetableRepository.getTimetable(student, semester, startDate, endDate, true).toFirstResult() } + + // verify + assertEquals(null, res.error) + assertEquals(2, res.data?.first?.size) + coVerify { sdk.getTimetable(startDate, endDate) } + coVerify { timetableDb.loadAll(1, 1, startDate, endDate) } + coVerify { timetableDb.insertAll(match { it.isEmpty() }) } + coVerify { timetableDb.deleteAll(match { it.isEmpty() }) } + } + + private fun createTimetableRemote(start: LocalDateTime, number: Int = 1, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false) = SdkTimetable( + number = number, + start = start, + end = start.plusMinutes(45), + date = start.toLocalDate(), + subject = subject, + group = "", + room = room, + teacher = teacher, + info = "", + changes = changes, + canceled = false, + roomOld = "", + subjectOld = "", + teacherOld = "", + studentPlan = true + ) +} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemoteTest.kt deleted file mode 100644 index 2400c6c46..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/attendance/AttendanceRemoteTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package io.github.wulkanowy.data.repositories.attendance - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.getStudentEntity -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Attendance -import io.github.wulkanowy.utils.init -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import java.time.LocalDate -import java.time.LocalDate.of - -class AttendanceRemoteTest { - - @SpyK - private var mockSdk = Sdk() - - @MockK - private lateinit var semesterMock: Semester - - private var student = getStudentEntity() - - @Before - fun initApi() { - MockKAnnotations.init(this) - } - - @Test - fun getAttendanceTest() { - every { mockSdk.init(student) } returns mockSdk - coEvery { - mockSdk.getAttendance( - of(2018, 9, 10), - of(2018, 9, 15), - 1 - ) - } returns listOf( - getAttendance(of(2018, 9, 10)), - getAttendance(of(2018, 9, 17)) - ) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - - val attendance = runBlocking { - AttendanceRemote(mockSdk).getAttendance(student, semesterMock, - of(2018, 9, 10), - of(2018, 9, 15) - ) - } - assertEquals(2, attendance.size) - } - - private fun getAttendance(date: LocalDate): Attendance { - return Attendance( - subject = "Fizyka", - name = "Obecność", - date = date, - timeId = 0, - number = 0, - deleted = false, - excusable = false, - excused = false, - exemption = false, - lateness = false, - presence = false, - categoryId = 1, - absence = false, - excuseStatus = null - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemoteTest.kt deleted file mode 100644 index 7be3f84fd..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/completedlessons/CompletedLessonsRemoteTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.wulkanowy.data.repositories.completedlessons - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.getStudentEntity -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.CompletedLesson -import io.github.wulkanowy.utils.init -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK -import kotlinx.coroutines.runBlocking -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import java.time.LocalDate -import java.time.LocalDate.of - -class CompletedLessonsRemoteTest { - - @SpyK - private var mockSdk = Sdk() - - @MockK - private lateinit var semesterMock: Semester - - private val student = getStudentEntity() - - @Before - fun initApi() { - MockKAnnotations.init(this) - } - - @Test - fun getCompletedLessonsTest() { - every { mockSdk.init(student) } returns mockSdk - coEvery { - mockSdk.getCompletedLessons( - of(2018, 9, 10), - of(2018, 9, 15) - ) - } returns listOf( - getCompletedLesson(of(2018, 9, 10)), - getCompletedLesson(of(2018, 9, 17)) - ) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - - val completed = runBlocking { - CompletedLessonsRemote(mockSdk).getCompletedLessons(student, semesterMock, - of(2018, 9, 10), - of(2018, 9, 15) - ) - } - Assert.assertEquals(2, completed.size) - } - - private fun getCompletedLesson(date: LocalDate): CompletedLesson { - return CompletedLesson( - date = date, - subject = "", - absence = "", - resources = "", - substitution = "", - teacherSymbol = "", - teacher = "", - topic = "", - number = 1 - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/exam/ExamRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/exam/ExamRemoteTest.kt deleted file mode 100644 index 23bf0297e..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/exam/ExamRemoteTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.wulkanowy.data.repositories.exam - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.getStudentEntity -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Exam -import io.github.wulkanowy.utils.init -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import java.time.LocalDate -import java.time.LocalDate.of - -class ExamRemoteTest { - - @SpyK - private var mockSdk = Sdk() - - @MockK - private lateinit var semesterMock: Semester - - private val student = getStudentEntity() - - @Before - fun setUp() { - MockKAnnotations.init(this) - } - - @Test - fun getExamsTest() { - every { mockSdk.init(student) } returns mockSdk - every { mockSdk.switchDiary(1, 2019) } returns mockSdk - - coEvery { - mockSdk.getExams( - of(2018, 9, 10), - of(2018, 9, 15), - 1 - ) - } returns listOf( - getExam(of(2018, 9, 10)), - getExam(of(2018, 9, 17)) - ) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - - val exams = runBlocking { - ExamRemote(mockSdk).getExams(student, semesterMock, - of(2018, 9, 10), - of(2018, 9, 15) - ) - } - assertEquals(2, exams.size) - } - - private fun getExam(date: LocalDate): Exam { - return Exam( - subject = "", - group = "", - type = "", - description = "", - teacher = "", - teacherSymbol = "", - date = date, - entryDate = date - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt deleted file mode 100644 index 6d3d63a7a..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/gradestatistics/GradeStatisticsRemoteTest.kt +++ /dev/null @@ -1,93 +0,0 @@ -package io.github.wulkanowy.data.repositories.gradestatistics - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.getStudentEntity -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.GradePointsStatistics -import io.github.wulkanowy.sdk.pojo.GradeStatisticsItem -import io.github.wulkanowy.sdk.pojo.GradeStatisticsSubject -import io.github.wulkanowy.utils.init -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test - -class GradeStatisticsRemoteTest { - - @SpyK - private var mockSdk = Sdk() - - @MockK - private lateinit var semesterMock: Semester - - private val student = getStudentEntity() - - @Before - fun initApi() { - MockKAnnotations.init(this) - every { mockSdk.init(student) } returns mockSdk - } - - @Test - fun getGradeStatisticsTest() { - coEvery { mockSdk.getGradesPartialStatistics(1) } returns listOf( - getGradeStatisticsPartialSubject("Fizyka"), - getGradeStatisticsPartialSubject("Matematyka") - ) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - - val stats = runBlocking { GradeStatisticsRemote(mockSdk).getGradePartialStatistics(student, semesterMock) } - assertEquals(2, stats.size) - } - - @Test - fun getGradePointsStatisticsTest() { - coEvery { mockSdk.getGradesPointsStatistics(1) } returns listOf( - getGradePointsStatistics("Fizyka"), - getGradePointsStatistics("Matematyka") - ) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - - val stats = runBlocking { GradeStatisticsRemote(mockSdk).getGradePointsStatistics(student, semesterMock) } - assertEquals(2, stats.size) - } - - private fun getGradeStatisticsPartialSubject(subjectName: String): GradeStatisticsSubject { - return GradeStatisticsSubject( - subject = subjectName, - studentAverage = "", - classAverage = "", - classItems = listOf( - GradeStatisticsItem( - subject = subjectName, - grade = 0, - amount = 0 - ) - ), - studentItems = listOf() - ) - } - - private fun getGradePointsStatistics(subjectName: String): GradePointsStatistics { - return GradePointsStatistics( - subject = subjectName, - student = 0.80, - others = 0.40 - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemoteTest.kt deleted file mode 100644 index 1a8157f66..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/luckynumber/LuckyNumberRemoteTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.wulkanowy.data.repositories.luckynumber - -import io.github.wulkanowy.getStudentEntity -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.utils.init -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.SpyK -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import java.time.LocalDate - -class LuckyNumberRemoteTest { - - @SpyK - private var mockSdk = Sdk() - - private val student = getStudentEntity(Sdk.Mode.SCRAPPER) - - @Before - fun setUp() { - MockKAnnotations.init(this) - } - - @Test - fun getLuckyNumberTest() { - every { mockSdk.init(student) } returns mockSdk - coEvery { mockSdk.getLuckyNumber("test") } returns 14 - - every { mockSdk.diaryId } returns 1 - - val luckyNumber = runBlocking { - LuckyNumberRemote(mockSdk) - .getLuckyNumber(student) - } - - assertEquals(14, luckyNumber?.luckyNumber) - assertEquals(LocalDate.now(), luckyNumber?.date) - assertEquals(student.studentId, luckyNumber?.studentId) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt deleted file mode 100644 index d07a0f393..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/message/MessageRepositoryTest.kt +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.wulkanowy.data.repositories.message - -import io.github.wulkanowy.data.Status -import io.github.wulkanowy.data.db.entities.MessageWithAttachment -import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.getMessageEntity -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.just -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import java.net.UnknownHostException - -class MessageRepositoryTest { - - @MockK - lateinit var local: MessageLocal - - @MockK - lateinit var remote: MessageRemote - - @MockK - lateinit var student: Student - - private lateinit var repo: MessageRepository - - @Before - fun setUp() { - MockKAnnotations.init(this) - - every { student.userName } returns "Jan" - repo = MessageRepository(local, remote) - } - - @Test - fun `throw error when message is not in the db`() { - val testMessage = getMessageEntity(1, "", false) - coEvery { local.getMessageWithAttachment(student, testMessage) } throws NullPointerException("No message in database") - - val message = runCatching { runBlocking { repo.getMessage(student, testMessage).toList()[1] } } - assertEquals(NullPointerException::class.java, message.exceptionOrNull()?.javaClass) - } - - @Test - fun `get message when content already in db`() { - val testMessage = getMessageEntity(123, "Test", false) - val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) - - coEvery { local.getMessageWithAttachment(student, testMessage) } returns flowOf(messageWithAttachment) - - val message = runBlocking { repo.getMessage(student, testMessage).toList() } - - assertEquals(Status.SUCCESS, message[1].status) - assertEquals("Test", message[1].data!!.message.content) - } - - @Test - fun `get message when content in db is empty`() { - val testMessage = getMessageEntity(123, "", true) - val testMessageWithContent = testMessage.copy().apply { content = "Test" } - - val mWa = MessageWithAttachment(testMessage, emptyList()) - val mWaWithContent = MessageWithAttachment(testMessageWithContent, emptyList()) - - coEvery { local.getMessageWithAttachment(student, testMessage) } returnsMany listOf(flowOf(mWa), flowOf(mWaWithContent)) - coEvery { remote.getMessagesContentDetails(student, any(), any()) } returns ("Test" to emptyList()) - coEvery { local.updateMessages(any()) } just Runs - coEvery { local.saveMessageAttachments(any()) } just Runs - - val message = runBlocking { repo.getMessage(student, testMessage).toList() } - - assertEquals(Status.SUCCESS, message[2].status) - assertEquals("Test", message[2].data!!.message.content) - coVerify { local.updateMessages(listOf(testMessageWithContent)) } - } - - @Test - fun `get message when content in db is empty and there is no internet connection`() { - val testMessage = getMessageEntity(123, "", false) - val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) - - coEvery { local.getMessageWithAttachment(student, testMessage) } throws UnknownHostException() - - val message = runCatching { runBlocking { repo.getMessage(student, testMessage).toList()[1] } } - assertEquals(UnknownHostException::class.java, message.exceptionOrNull()?.javaClass) - } - - @Test - fun `get message when content in db is empty, unread and there is no internet connection`() { - val testMessage = getMessageEntity(123, "", true) - val messageWithAttachment = MessageWithAttachment(testMessage, emptyList()) - - coEvery { local.getMessageWithAttachment(student, testMessage) } throws UnknownHostException() - - val message = runCatching { runBlocking { repo.getMessage(student, testMessage).toList()[1] } } - assertEquals(UnknownHostException::class.java, message.exceptionOrNull()?.javaClass) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt deleted file mode 100644 index 286cf5e91..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/mobiledevice/MobileDeviceRepositoryTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.wulkanowy.data.repositories.mobiledevice - -import io.github.wulkanowy.data.db.entities.MobileDevice -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.getStudentEntity -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.impl.annotations.MockK -import io.mockk.just -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import java.time.LocalDateTime.of - -class MobileDeviceRepositoryTest { - - @MockK - private lateinit var semester: Semester - - @MockK - private lateinit var mobileDeviceRemote: MobileDeviceRemote - - @MockK - private lateinit var mobileDeviceLocal: MobileDeviceLocal - - private val student = getStudentEntity() - - private lateinit var mobileDeviceRepository: MobileDeviceRepository - - @Before - fun initTest() { - MockKAnnotations.init(this) - mobileDeviceRepository = MobileDeviceRepository(mobileDeviceLocal, mobileDeviceRemote) - } - - @Test - fun getDevices() { - val devices = listOf( - getDeviceEntity(1), - getDeviceEntity(2) - ) - - coEvery { mobileDeviceLocal.getDevices(semester) } returns flowOf(emptyList()) - coEvery { mobileDeviceLocal.deleteDevices(emptyList()) } just Runs - coEvery { mobileDeviceLocal.saveDevices(devices) } just Runs - coEvery { mobileDeviceRemote.getDevices(student, semester) } returns devices - - runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toList() } - - coVerify { mobileDeviceLocal.deleteDevices(emptyList()) } - coVerify { mobileDeviceLocal.saveDevices(devices) } - } - - private fun getDeviceEntity(day: Int): MobileDevice { - return MobileDevice( - studentId = 1, - deviceId = 1, - name = "", - date = of(2019, 5, day, 0, 0, 0) - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt deleted file mode 100644 index 5a2388ff7..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/semester/SemesterRepositoryTest.kt +++ /dev/null @@ -1,216 +0,0 @@ -package io.github.wulkanowy.data.repositories.semester - -import io.github.wulkanowy.TestDispatchersProvider -import io.github.wulkanowy.createSemesterEntity -import io.github.wulkanowy.data.db.entities.Student -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.just -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotEquals -import org.junit.Before -import org.junit.Test -import java.time.LocalDate.now - -class SemesterRepositoryTest { - - @MockK - private lateinit var semesterRemote: SemesterRemote - - @MockK - private lateinit var semesterLocal: SemesterLocal - - @MockK - private lateinit var student: Student - - private lateinit var semesterRepository: SemesterRepository - - @Before - fun initTest() { - MockKAnnotations.init(this) - semesterRepository = SemesterRepository(semesterRemote, semesterLocal, TestDispatchersProvider()) - every { student.loginMode } returns "SCRAPPER" - } - - @Test - fun getSemesters_noSemesters() { - val semesters = listOf( - createSemesterEntity(1, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(1, 2, now().minusMonths(3), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns emptyList() - coEvery { semesterRemote.getSemesters(student) } returns semesters - coEvery { semesterLocal.deleteSemesters(any()) } just Runs - coEvery { semesterLocal.saveSemesters(any()) } just Runs - - runBlocking { semesterRepository.getSemesters(student) } - - coVerify { semesterLocal.saveSemesters(semesters) } - coVerify { semesterLocal.deleteSemesters(emptyList()) } - } - - @Test - fun getSemesters_invalidDiary_api() { - every { student.loginMode } returns "API" - val badSemesters = listOf( - createSemesterEntity(0, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(0, 2, now().minusMonths(3), now()) - ) - - val goodSemesters = listOf( - createSemesterEntity(122, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(123, 2, now().minusMonths(3), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns badSemesters - coEvery { semesterRemote.getSemesters(student) } returns goodSemesters - coEvery { semesterLocal.deleteSemesters(any()) } just Runs - coEvery { semesterLocal.saveSemesters(any()) } just Runs - - val items = runBlocking { semesterRepository.getSemesters(student) } - assertEquals(2, items.size) - assertEquals(0, items[0].diaryId) - } - - @Test - fun getSemesters_invalidDiary_scrapper() { - every { student.loginMode } returns "SCRAPPER" - val badSemesters = listOf( - createSemesterEntity(0, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(0, 2, now().minusMonths(3), now()) - ) - - val goodSemesters = listOf( - createSemesterEntity(1, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(1, 2, now().minusMonths(3), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returnsMany listOf(badSemesters, badSemesters, goodSemesters) - coEvery { semesterRemote.getSemesters(student) } returns goodSemesters - coEvery { semesterLocal.deleteSemesters(any()) } just Runs - coEvery { semesterLocal.saveSemesters(any()) } just Runs - - val items = runBlocking { semesterRepository.getSemesters(student) } - assertEquals(2, items.size) - assertNotEquals(0, items[0].diaryId) - } - - @Test - fun getSemesters_noCurrent() { - val semesters = listOf( - createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), - createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) - ) - - coEvery { semesterLocal.getSemesters(student) } returns semesters - - val items = runBlocking { semesterRepository.getSemesters(student) } - assertEquals(2, items.size) - } - - @Test - fun getSemesters_oneCurrent() { - val semesters = listOf( - createSemesterEntity(1, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(1, 2, now().minusMonths(3), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns semesters - - val items = runBlocking { semesterRepository.getSemesters(student) } - assertEquals(2, items.size) - } - - @Test - fun getSemesters_doubleCurrent() { - val semesters = listOf( - createSemesterEntity(1, 1, now(), now()), - createSemesterEntity(1, 2, now(), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns semesters - - val items = runBlocking { semesterRepository.getSemesters(student) } - assertEquals(2, items.size) - } - - @Test - fun getSemesters_noSemesters_refreshOnNoCurrent() { - val semesters = listOf( - createSemesterEntity(1, 1, now().minusMonths(6), now().minusMonths(3)), - createSemesterEntity(1, 2, now().minusMonths(3), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns emptyList() - coEvery { semesterRemote.getSemesters(student) } returns semesters - coEvery { semesterLocal.deleteSemesters(any()) } just Runs - coEvery { semesterLocal.saveSemesters(any()) } just Runs - - runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } - - coVerify { semesterLocal.deleteSemesters(emptyList()) } - coVerify { semesterLocal.saveSemesters(semesters) } - } - - @Test - fun getSemesters_noCurrent_refreshOnNoCurrent() { - val semestersWithNoCurrent = listOf( - createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), - createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)) - ) - - val newSemesters = listOf( - createSemesterEntity(1, 1, now().minusMonths(12), now().minusMonths(6)), - createSemesterEntity(1, 2, now().minusMonths(6), now().minusMonths(1)), - - createSemesterEntity(2, 1, now().minusMonths(1), now().plusMonths(5)), - createSemesterEntity(2, 2, now().plusMonths(5), now().plusMonths(11)), - ) - - coEvery { semesterLocal.getSemesters(student) } returns semestersWithNoCurrent - coEvery { semesterRemote.getSemesters(student) } returns newSemesters - coEvery { semesterLocal.deleteSemesters(any()) } just Runs - coEvery { semesterLocal.saveSemesters(any()) } just Runs - - val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } - assertEquals(2, items.size) - } - - @Test - fun getSemesters_doubleCurrent_refreshOnNoCurrent() { - val semesters = listOf( - createSemesterEntity(1, 1, now(), now()), - createSemesterEntity(1, 2, now(), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns semesters - - val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } - assertEquals(2, items.size) - } - - @Test(expected = IllegalArgumentException::class) - fun getCurrentSemester_doubleCurrent() { - val semesters = listOf( - createSemesterEntity(1, 1, now(), now()), - createSemesterEntity(1, 1, now(), now()) - ) - - coEvery { semesterLocal.getSemesters(student) } returns semesters - - runBlocking { semesterRepository.getCurrentSemester(student) } - } - - @Test(expected = RuntimeException::class) - fun getCurrentSemester_emptyList() { - coEvery { semesterLocal.getSemesters(student) } returns emptyList() - - runBlocking { semesterRepository.getCurrentSemester(student) } - } -} diff --git a/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt b/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt deleted file mode 100644 index 88ecf711d..000000000 --- a/app/src/test/java/io/github/wulkanowy/data/repositories/timetable/TimetableRemoteTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.wulkanowy.data.repositories.timetable - -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.getStudentEntity -import io.github.wulkanowy.sdk.Sdk -import io.github.wulkanowy.sdk.pojo.Timetable -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.impl.annotations.SpyK -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import java.time.LocalDate -import java.time.LocalDate.of -import java.time.LocalDateTime.now - -class TimetableRemoteTest { - - @SpyK - private var mockSdk = Sdk() - - @MockK - private lateinit var semesterMock: Semester - - private val student = getStudentEntity() - - @Before - fun initApi() { - MockKAnnotations.init(this) - } - - @Test - fun getTimetableTest() { - coEvery { - mockSdk.getTimetable( - of(2018, 9, 10), - of(2018, 9, 15) - ) - } returns (listOf( - getTimetable(of(2018, 9, 10)), - getTimetable(of(2018, 9, 17)) - ) to emptyList()) - - every { semesterMock.studentId } returns 1 - every { semesterMock.diaryId } returns 1 - every { semesterMock.schoolYear } returns 2019 - every { semesterMock.semesterId } returns 1 - every { mockSdk.switchDiary(any(), any()) } returns mockSdk - - val timetable = runBlocking { - TimetableRemote(mockSdk).getTimetable(student, semesterMock, - of(2018, 9, 10), - of(2018, 9, 15) - ) - } - assertEquals(2, timetable.first.size) - } - - private fun getTimetable(date: LocalDate): Timetable { - return Timetable( - date = date, - number = 0, - teacherOld = "", - subjectOld = "", - roomOld = "", - subject = "", - teacher = "", - group = "", - canceled = false, - changes = false, - info = "", - room = "", - end = now(), - start = now(), - studentPlan = true - ) - } -} diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt index 390098f0d..0d523c01e 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt @@ -1,13 +1,13 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.createSemesterEntity import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.repositories.grade.GradeRepository -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.semester.SemesterRepository +import io.github.wulkanowy.data.repositories.GradeRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.getSemesterEntity import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.flowWithResource import io.mockk.MockKAnnotations @@ -42,9 +42,9 @@ class GradeAverageProviderTest { private val student = Student("", "", "", "SCRAPPER", "", "", false, "", "", "", 101, 0, "", "", "", "", "", "", 1, true, LocalDateTime.now()) private val semesters = mutableListOf( - createSemesterEntity(10, 21, of(2019, 1, 31), of(2019, 6, 23)), - createSemesterEntity(11, 22, of(2019, 9, 1), of(2020, 1, 31)), - createSemesterEntity(11, 23, of(2020, 2, 1), now(), semesterName = 2) + getSemesterEntity(10, 21, of(2019, 1, 31), of(2019, 6, 23)), + getSemesterEntity(11, 22, of(2019, 9, 1), of(2020, 1, 31)), + getSemesterEntity(11, 23, of(2020, 2, 1), now(), semesterName = 2) ) private val firstGrades = listOf( diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/LoginPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/LoginPresenterTest.kt index 4f32cf7eb..35cda35bd 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/LoginPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/LoginPresenterTest.kt @@ -1,6 +1,6 @@ package io.github.wulkanowy.ui.modules.login -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.mockk.MockKAnnotations import io.mockk.clearMocks import io.mockk.every diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt index bda0639c6..b475584b5 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.login.form import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.mockk.MockKAnnotations @@ -16,8 +16,8 @@ import io.mockk.verify import org.junit.Before import org.junit.Rule import org.junit.Test -import java.time.LocalDateTime.now import java.io.IOException +import java.time.LocalDateTime.now class LoginFormPresenterTest { diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt index 6b0baf9bc..4b9776825 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectPresenterTest.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.login.studentselect import io.github.wulkanowy.MainCoroutineRule import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.modules.login.LoginErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper import io.mockk.MockKAnnotations diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index d3c9fa138..2c44e9fad 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.utils.AnalyticsHelper diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt index a81f1abd9..7cced3c9e 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/splash/SplashPresenterTest.kt @@ -1,7 +1,7 @@ package io.github.wulkanowy.ui.modules.splash import io.github.wulkanowy.MainCoroutineRule -import io.github.wulkanowy.data.repositories.student.StudentRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.ErrorHandler import io.mockk.MockKAnnotations import io.mockk.coEvery diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt index eac84759d..84b61a904 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimetableExtensionTest.kt @@ -1,11 +1,13 @@ package io.github.wulkanowy.utils -import io.github.wulkanowy.getTimetableEntity +import io.github.wulkanowy.data.db.entities.Timetable import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Test +import java.time.LocalDate +import java.time.LocalDateTime import java.time.LocalDateTime.now class TimetableExtensionTest { @@ -40,4 +42,29 @@ class TimetableExtensionTest { assertTrue(getTimetableEntity(end = now().minusSeconds(1)).isJustFinished) assertFalse(getTimetableEntity(end = now().plusSeconds(1)).isJustFinished) } + + private fun getTimetableEntity( + isStudentPlan: Boolean = false, + canceled: Boolean = false, + start: LocalDateTime = now(), + end: LocalDateTime = now() + ) = Timetable( + studentId = 0, + subject = "", + number = 0, + diaryId = 0, + canceled = canceled, + changes = false, + date = LocalDate.now(), + end = end, + group = "", + info = "", + isStudentPlan = isStudentPlan, + room = "", + roomOld = "", + start = start, + subjectOld = "", + teacher = "", + teacherOld = "" + ) } From 8b2dc514f4fc87d9c55cc606120cf72298261ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 4 Jan 2021 00:21:25 +0100 Subject: [PATCH 058/110] Use Firebase Android BoM (#1064) * Use Firebase Android BoM * Use androidx-bom to resolve androidx versions * Revert "Use androidx-bom to resolve androidx versions" This reverts commit 55a79a61cfa55090e157c66d4aed94322d44ac69. --- app/build.gradle | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 84ca502be..e79ebcc1f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,11 +198,12 @@ dependencies { implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation 'me.xdrop:fuzzywuzzy:1.3.1' - playImplementation 'com.google.firebase:firebase-analytics:18.0.0' - playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.2' - playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.2" - playImplementation 'com.google.firebase:firebase-messaging:21.0.0' - playImplementation 'com.google.firebase:firebase-crashlytics:17.3.0' + playImplementation platform('com.google.firebase:firebase-bom:26.2.0') + playImplementation 'com.google.firebase:firebase-analytics-ktx' + playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx' + playImplementation "com.google.firebase:firebase-inappmessaging-ktx" + playImplementation 'com.google.firebase:firebase-messaging:' + playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' From 344d404238f0cfca068c3f27f0d046d8d51b15fa Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 5 Jan 2021 04:37:48 +0000 Subject: [PATCH 059/110] Bump hianalytics from 5.0.5.301 to 5.1.0.300 (#1060) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e79ebcc1f..1ba866ca8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -207,7 +207,7 @@ dependencies { playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' - hmsImplementation 'com.huawei.hms:hianalytics:5.0.5.301' + hmsImplementation 'com.huawei.hms:hianalytics:5.1.0.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.4.2.301' releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" From 205bcf9c222ddfe0fd5eff9947b2d67fe9ce6cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 11 Jan 2021 09:52:44 +0100 Subject: [PATCH 060/110] Use work-gcm dependency in play flavor only (#1069) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1ba866ca8..7af696cae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -169,7 +169,7 @@ dependencies { implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" implementation "androidx.work:work-runtime-ktx:$work_manager" - implementation "androidx.work:work-gcm:$work_manager" + playImplementation "androidx.work:work-gcm:$work_manager" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" From d332369872a3fe090e09f81e8a61911e55b342cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Mon, 11 Jan 2021 09:53:19 +0100 Subject: [PATCH 061/110] Disable force dark theme on Android >9 (#1071) --- app/src/main/res/values-night/styles.xml | 1 + app/src/main/res/values/styles.xml | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index a06a46f00..32f7cdf13 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,6 +1,7 @@ - From 3e3a080b701a93e19886d32c202a6c53d0753007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 1 Feb 2021 23:58:44 +0100 Subject: [PATCH 107/110] Add nick for student (#1119) --- .../31.json | 2 +- .../32.json | 2142 +++++++++++++++++ .../github/wulkanowy/data/db/AppDatabase.kt | 6 +- .../wulkanowy/data/db/dao/StudentDao.kt | 5 + .../wulkanowy/data/db/entities/Student.kt | 2 + .../wulkanowy/data/db/entities/StudentNick.kt | 16 + .../data/db/migrations/Migration32.kt | 12 + .../data/repositories/StudentRepository.kt | 76 +- .../TimetableNotificationSchedulerHelper.kt | 67 +- .../ui/base/WidgetConfigureAdapter.kt | 3 +- .../ui/modules/account/AccountAdapter.kt | 3 +- .../ui/modules/account/AccountPresenter.kt | 10 +- .../accountdetails/AccountDetailsFragment.kt | 49 +- .../accountdetails/AccountDetailsPresenter.kt | 82 +- .../accountdetails/AccountDetailsView.kt | 15 +- .../AccountEditDetailsDialog.kt | 38 - .../account/accountedit/AccountEditDialog.kt | 72 + .../accountedit/AccountEditPresenter.kt | 54 + .../account/accountedit/AccountEditView.kt | 14 + .../accountquick/AccountQuickPresenter.kt | 6 +- .../studentinfo/StudentInfoFragment.kt | 1 + .../TimetableWidgetProvider.kt | 11 +- .../wulkanowy/utils/StudentExtension.kt | 5 + ...it_details.xml => dialog_account_edit.xml} | 13 +- app/src/main/res/layout/fragment_account.xml | 2 +- .../res/layout/fragment_account_details.xml | 455 ++-- .../main/res/layout/fragment_login_form.xml | 2 - app/src/main/res/values/strings.xml | 7 + 28 files changed, 2857 insertions(+), 313 deletions(-) create mode 100644 app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt delete mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountEditDetailsDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt rename app/src/main/res/layout/{dialog_account_edit_details.xml => dialog_account_edit.xml} (90%) diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json index 55a51e042..4935a9018 100644 --- a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/31.json @@ -2133,4 +2133,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd642512ffa5fe81ae9308c9c55612539')" ] } -} \ No newline at end of file +} diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json new file mode 100644 index 000000000..3621be48a --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json @@ -0,0 +1,2142 @@ +{ + "formatVersion": 1, + "database": { + "version": 32, + "identityHash": "9531cdc8b3f0e62db5ab6ebe66837a28", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `first_guardian_full_name` TEXT NOT NULL, `first_guardian_kinship` TEXT NOT NULL, `first_guardian_address` TEXT NOT NULL, `first_guardian_phones` TEXT NOT NULL, `first_guardian_email` TEXT NOT NULL, `second_guardian_full_name` TEXT NOT NULL, `second_guardian_kinship` TEXT NOT NULL, `second_guardian_address` TEXT NOT NULL, `second_guardian_phones` TEXT NOT NULL, `second_guardian_email` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9531cdc8b3f0e62db5ab6ebe66837a28')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index d1f99e3e8..806f0c9d1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -83,6 +83,7 @@ import io.github.wulkanowy.data.db.migrations.Migration29 import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration30 import io.github.wulkanowy.data.db.migrations.Migration31 +import io.github.wulkanowy.data.db.migrations.Migration32 import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration6 @@ -128,7 +129,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 31 + const val VERSION_SCHEMA = 32 fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array { return arrayOf( @@ -161,7 +162,8 @@ abstract class AppDatabase : RoomDatabase() { Migration28(), Migration29(), Migration30(), - Migration31() + Migration31(), + Migration32() ) } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index d7237ea8a..e9c5f157e 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -6,7 +6,9 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy.ABORT import androidx.room.Query import androidx.room.Transaction +import androidx.room.Update import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentNick import io.github.wulkanowy.data.db.entities.StudentWithSemesters import javax.inject.Singleton @@ -20,6 +22,9 @@ interface StudentDao { @Delete suspend fun delete(student: Student) + @Update(entity = Student::class) + suspend fun update(studentNick: StudentNick) + @Query("SELECT * FROM Students WHERE is_current = 1") suspend fun loadCurrent(): Student? diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt index 0b37b1e98..6b60c8146 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt @@ -79,4 +79,6 @@ data class Student( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + var nick = "" } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt new file mode 100644 index 000000000..71f48f7a3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/StudentNick.kt @@ -0,0 +1,16 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity +data class StudentNick( + + val nick: String + +) : Serializable { + + @PrimaryKey + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt new file mode 100644 index 000000000..508485e08 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration32.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration32 : Migration(31, 32) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"") + } +} + diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt index 5b80035ba..558214799 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -5,6 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentNick import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.mappers.mapToEntities @@ -25,49 +26,70 @@ class StudentRepository @Inject constructor( private val sdk: Sdk ) { - suspend fun isStudentSaved(): Boolean = getSavedStudents(false).isNotEmpty() + suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty() - suspend fun isCurrentStudentSet(): Boolean = studentDb.loadCurrent()?.isCurrent ?: false + suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false - suspend fun getStudentsApi(pin: String, symbol: String, token: String): List { - return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities() - } + suspend fun getStudentsApi( + pin: String, + symbol: String, + token: String + ): List = + sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities() - suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List { - return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password) - } + suspend fun getStudentsScrapper( + email: String, + password: String, + scrapperBaseUrl: String, + symbol: String + ): List = + sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol) + .mapToEntities(password) - suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List { - return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password) - } + suspend fun getStudentsHybrid( + email: String, + password: String, + scrapperBaseUrl: String, + symbol: String + ): List = + sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password) - suspend fun getSavedStudents(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) { - studentDb.loadStudentsWithSemesters().map { - it.apply { - if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password) + suspend fun getSavedStudents(decryptPass: Boolean = true) = + withContext(dispatchers.backgroundThread) { + studentDb.loadStudentsWithSemesters().map { + it.apply { + if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { + student.password = decrypt(student.password) + } + } } } - } suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) { studentDb.loadById(id)?.apply { - if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) + if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) { + password = decrypt(password) + } } } ?: throw NoCurrentStudentException() - suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) { - studentDb.loadCurrent()?.apply { - if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) - } - } ?: throw NoCurrentStudentException() + suspend fun getCurrentStudent(decryptPass: Boolean = true) = + withContext(dispatchers.backgroundThread) { + studentDb.loadCurrent()?.apply { + if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) { + password = decrypt(password) + } + } + } ?: throw NoCurrentStudentException() suspend fun saveStudents(studentsWithSemesters: List): List { semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters }) return withContext(dispatchers.backgroundThread) { studentDb.insertAll(studentsWithSemesters.map { it.student }.map { - if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context)) - else it + if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) { + it.copy(password = encrypt(it.password, context)) + } else it }) } } @@ -79,7 +101,7 @@ class StudentRepository @Inject constructor( } } - suspend fun logoutStudent(student: Student) { - studentDb.delete(student) - } + suspend fun logoutStudent(student: Student) = studentDb.delete(student) + + suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick) } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index c0eba2f92..c5a5590b9 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -27,6 +27,7 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.DispatchersProvider +import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.toTimestamp import kotlinx.coroutines.withContext import timber.log.Timber @@ -41,17 +42,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor( private val dispatchersProvider: DispatchersProvider, ) { - private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt() + private fun getRequestCode(time: LocalDateTime, studentId: Int) = + (time.toTimestamp() * studentId).toInt() - private fun getUpcomingLessonTime(index: Int, day: List, lesson: Timetable): LocalDateTime { - return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) - } + private fun getUpcomingLessonTime( + index: Int, + day: List, + lesson: Timetable + ) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) suspend fun cancelScheduled(lessons: List, studentId: Int = 1) { withContext(dispatchersProvider.backgroundThread) { lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) - cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId)) + cancelScheduledTo( + upcomingTime..lesson.start, + getRequestCode(upcomingTime, studentId) + ) cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId)) Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") @@ -61,13 +68,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor( private fun cancelScheduledTo(range: ClosedRange, requestCode: Int) { if (now() in range) cancelNotification() - alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)) + alarmManager.cancel( + PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT) + ) } - fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) + fun cancelNotification() = + NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) suspend fun scheduleNotifications(lessons: List, student: Student) { - if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId) + if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) { + return cancelScheduled(lessons, student.studentId) + } withContext(dispatchersProvider.backgroundThread) { lessons.groupBy { it.date } @@ -82,13 +94,28 @@ class TimetableNotificationSchedulerHelper @Inject constructor( val intent = createIntent(student, lesson, active.getOrNull(index + 1)) if (lesson.start > now()) { - scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson)) + scheduleBroadcast( + intent, + student.studentId, + NOTIFICATION_TYPE_UPCOMING, + getUpcomingLessonTime(index, active, lesson) + ) } if (lesson.end > now()) { - scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start) + scheduleBroadcast( + intent, + student.studentId, + NOTIFICATION_TYPE_CURRENT, + lesson.start + ) if (active.lastIndex == index) { - scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end) + scheduleBroadcast( + intent, + student.studentId, + NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, + lesson.end + ) } } } @@ -99,7 +126,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor( private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { return Intent(context, TimetableNotificationReceiver::class.java).apply { putExtra(STUDENT_ID, student.studentId) - putExtra(STUDENT_NAME, student.studentName) + putExtra(STUDENT_NAME, student.nickOrName) putExtra(LESSON_ROOM, lesson.room) putExtra(LESSON_START, lesson.start.toTimestamp()) putExtra(LESSON_END, lesson.end.toTimestamp()) @@ -109,13 +136,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor( } } - private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) { - AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(), + private fun scheduleBroadcast( + intent: Intent, + studentId: Int, + notificationType: Int, + time: LocalDateTime + ) { + AlarmManagerCompat.setExactAndAllowWhileIdle( + alarmManager, RTC_WAKEUP, time.toTimestamp(), PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) it.putExtra(LESSON_TYPE, notificationType) }, FLAG_UPDATE_CURRENT) ) - Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") + Timber.d( + "TimetableNotification scheduled: type: $notificationType, subject: ${ + intent.getStringExtra(LESSON_TITLE) + }, start: $time, student: $studentId" + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt index cefe6ed75..8e6130fbf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/WidgetConfigureAdapter.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.databinding.ItemAccountBinding import io.github.wulkanowy.utils.getThemeAttrColor +import io.github.wulkanowy.utils.nickOrName import javax.inject.Inject class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter() { @@ -28,7 +29,7 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter() { @@ -84,7 +85,7 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter 1 && isAccountQuickDialogMode with(binding) { - accountItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}" + accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}" accountItemSchool.text = studentWithSemesters.student.schoolName accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student) accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 349561f82..366793b6d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -62,10 +62,16 @@ class AccountPresenter @Inject constructor( } private fun loadData() { - flowWithResource { studentRepository.getSavedStudents() } + flowWithResource { studentRepository.getSavedStudents(false) } .onEach { when (it.status) { - Status.LOADING -> Timber.i("Loading account data started") + Status.LOADING -> { + Timber.i("Loading account data started") + view?.run { + showProgress(true) + showContent(false) + } + } Status.SUCCESS -> { Timber.i("Loading account result: Success") view?.updateData(createAccountItems(it.data!!)) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt index bdd4946ae..f4b2c833d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt @@ -9,13 +9,16 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.get import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.FragmentAccountDetailsBinding import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.account.accountedit.AccountEditDialog import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView +import io.github.wulkanowy.utils.nickOrName import javax.inject.Inject @AndroidEntryPoint @@ -43,22 +46,19 @@ class AccountDetailsFragment : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - arguments?.let { - presenter.studentWithSemesters = - it.getSerializable(ARGUMENT_KEY) as StudentWithSemesters - } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentAccountDetailsBinding.bind(view) - presenter.onAttachView(this) + presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters) } override fun initView() { + binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() } + binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() } binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() } binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() } - binding.accountDetailsSelect.isEnabled = !presenter.studentWithSemesters.student.isCurrent binding.accountDetailsPersonalData.setOnClickListener { presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL) @@ -76,24 +76,31 @@ class AccountDetailsFragment : override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu[0].isVisible = false + inflater.inflate(R.menu.action_menu_account_details, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return if (item.itemId == R.id.accountDetailsMenuEdit) { - showAccountEditDetailsDialog() - return true + presenter.onAccountEditSelected() + true } else false } - override fun showAccountData(studentWithSemesters: StudentWithSemesters) { + override fun showAccountData(student: Student) { with(binding) { - accountDetailsName.text = studentWithSemesters.student.studentName - accountDetailsSchool.text = studentWithSemesters.student.schoolName + accountDetailsName.text = student.nickOrName + accountDetailsSchool.text = student.schoolName } } - override fun showAccountEditDetailsDialog() { - (requireActivity() as MainActivity).showDialogFragment(AccountEditDetailsDialog.newInstance()) + override fun enableSelectStudentButton(enable: Boolean) { + binding.accountDetailsSelect.isEnabled = enable + } + + override fun showAccountEditDetailsDialog(student: Student) { + (requireActivity() as MainActivity).showDialogFragment( + AccountEditDialog.newInstance(student) + ) } override fun showLogoutConfirmDialog() { @@ -127,6 +134,22 @@ class AccountDetailsFragment : ) } + override fun showErrorView(show: Boolean) { + binding.accountDetailsError.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun setErrorDetails(message: String) { + binding.accountDetailsErrorMessage.text = message + } + + override fun showProgress(show: Boolean) { + binding.accountDetailsProgress.visibility = if (show) View.VISIBLE else View.GONE + } + + override fun showContent(show: Boolean) { + binding.accountDetailsContent.visibility = if (show) View.VISIBLE else View.GONE + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt index da437429e..7b93d3d87 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsPresenter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.account.accountdetails +import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.repositories.StudentRepository @@ -9,6 +10,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -19,14 +21,74 @@ class AccountDetailsPresenter @Inject constructor( private val syncManager: SyncManager ) : BasePresenter(errorHandler, studentRepository) { - lateinit var studentWithSemesters: StudentWithSemesters + private lateinit var studentWithSemesters: StudentWithSemesters - override fun onAttachView(view: AccountDetailsView) { + private lateinit var lastError: Throwable + + private var studentId: Long? = null + + fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) { super.onAttachView(view) - view.initView() - Timber.i("Account details view was initialized") + studentId = studentWithSemesters.student.id - view.showAccountData(studentWithSemesters) + view.initView() + errorHandler.showErrorMessage = ::showErrorViewOnError + Timber.i("Account details view was initialized") + loadData() + } + + fun onRetry() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData() + } + + fun onDetailsClick() { + view?.showErrorDetailsDialog(lastError) + } + + private fun loadData() { + flowWithResource { studentRepository.getSavedStudents() } + .map { studentWithSemesters -> + Resource( + data = studentWithSemesters.data?.single { it.student.id == studentId }, + status = studentWithSemesters.status, + error = studentWithSemesters.error + ) + } + .onEach { + when (it.status) { + Status.LOADING -> { + view?.run { + showProgress(true) + showContent(false) + } + Timber.i("Loading account details view started") + } + Status.SUCCESS -> { + Timber.i("Loading account details view result: Success") + studentWithSemesters = it.data!! + view?.run { + showAccountData(studentWithSemesters.student) + enableSelectStudentButton(!studentWithSemesters.student.isCurrent) + showContent(true) + showErrorView(false) + } + } + Status.ERROR -> { + Timber.i("Loading account details view result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + } + .afterLoading { view?.showProgress(false) } + .launch() + } + + fun onAccountEditSelected() { + view?.showAccountEditDetailsDialog(studentWithSemesters.student) } fun onStudentInfoSelected(infoType: StudentInfoView.Type) { @@ -97,4 +159,14 @@ class AccountDetailsPresenter @Inject constructor( view?.popView() }.launch("logout") } + + private fun showErrorViewOnError(message: String, error: Throwable) { + view?.run { + lastError = error + setErrorDetails(message) + showErrorView(true) + showContent(false) + showProgress(false) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt index f1001fd78..652f0c1aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsView.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.account.accountdetails +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView @@ -8,9 +9,9 @@ interface AccountDetailsView : BaseView { fun initView() - fun showAccountData(studentWithSemesters: StudentWithSemesters) + fun showAccountData(student: Student) - fun showAccountEditDetailsDialog() + fun showAccountEditDetailsDialog(student: Student) fun showLogoutConfirmDialog() @@ -18,8 +19,18 @@ interface AccountDetailsView : BaseView { fun recreateMainView() + fun enableSelectStudentButton(enable: Boolean) + fun openStudentInfoView( infoType: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters ) + + fun showErrorView(show: Boolean) + + fun setErrorDetails(message: String) + + fun showProgress(show: Boolean) + + fun showContent(show: Boolean) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountEditDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountEditDetailsDialog.kt deleted file mode 100644 index be0af7df1..000000000 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountEditDetailsDialog.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.ui.modules.account.accountdetails - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import io.github.wulkanowy.databinding.DialogAccountEditDetailsBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable - -class AccountEditDetailsDialog : DialogFragment() { - - private var binding: DialogAccountEditDetailsBinding by lifecycleAwareVariable() - - companion object { - - fun newInstance() = AccountEditDetailsDialog() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return DialogAccountEditDetailsBinding.inflate(inflater).apply { binding = this }.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.accountEditDetailsCancel.setOnClickListener { dismiss() } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt new file mode 100644 index 000000000..89f23e29f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt @@ -0,0 +1,72 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.databinding.DialogAccountEditBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment +import javax.inject.Inject + +@AndroidEntryPoint +class AccountEditDialog : BaseDialogFragment(), AccountEditView { + + @Inject + lateinit var presenter: AccountEditPresenter + + companion object { + + private const val ARGUMENT_KEY = "student_with_semesters" + + fun newInstance(student: Student) = + AccountEditDialog().apply { + arguments = Bundle().apply { + putSerializable(ARGUMENT_KEY, student) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, 0) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student) + } + + override fun initView() { + with(binding) { + accountEditDetailsCancel.setOnClickListener { dismiss() } + accountEditDetailsSave.setOnClickListener { + presenter.changeStudentNick(binding.accountEditDetailsNickText.text.toString()) + } + } + } + + override fun showCurrentNick(nick: String) { + binding.accountEditDetailsNickText.setText(nick) + } + + override fun popView() { + dismiss() + } + + override fun recreateMainView() { + activity?.recreate() + } + + override fun onDestroyView() { + super.onDestroyView() + presenter.onDetachView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt new file mode 100644 index 000000000..7830605c6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditPresenter.kt @@ -0,0 +1,54 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import io.github.wulkanowy.data.Status +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.StudentNick +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.utils.afterLoading +import io.github.wulkanowy.utils.flowWithResource +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject + +class AccountEditPresenter @Inject constructor( + errorHandler: ErrorHandler, + studentRepository: StudentRepository +) : BasePresenter(errorHandler, studentRepository) { + + lateinit var student: Student + + fun onAttachView(view: AccountEditView, student: Student) { + super.onAttachView(view) + this.student = student + + with(view) { + initView() + showCurrentNick(student.nick.trim()) + } + Timber.i("Account edit dialog view was initialized") + } + + fun changeStudentNick(nick: String) { + flowWithResource { + val studentNick = + StudentNick(nick = nick.trim()).apply { id = student.id } + studentRepository.updateStudentNick(studentNick) + }.onEach { + when (it.status) { + Status.LOADING -> Timber.i("Attempt to change a student nick") + Status.SUCCESS -> { + Timber.i("Change a student nick result: Success") + view?.recreateMainView() + } + Status.ERROR -> { + Timber.i("Change a student result: An exception occurred") + errorHandler.dispatch(it.error!!) + } + } + } + .afterLoading { view?.popView() } + .launch() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt new file mode 100644 index 000000000..b25eec6c8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditView.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.ui.modules.account.accountedit + +import io.github.wulkanowy.ui.base.BaseView + +interface AccountEditView : BaseView { + + fun initView() + + fun popView() + + fun recreateMainView() + + fun showCurrentNick(nick: String) +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt index 87b14c53f..43cc8bc77 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickPresenter.kt @@ -52,9 +52,9 @@ class AccountQuickPresenter @Inject constructor( errorHandler.dispatch(it.error!!) } } - }.afterLoading { - view?.popView() - }.launch("switch") + } + .afterLoading { view?.popView() } + .launch("switch") } private fun loadData() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt index fb7f8d285..767d31a45 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/studentinfo/StudentInfoFragment.kt @@ -109,6 +109,7 @@ class StudentInfoFragment : listOf( getString(R.string.student_info_first_name) to studentInfo.firstName, getString(R.string.student_info_second_name) to studentInfo.secondName, + getString(R.string.student_info_last_name) to studentInfo.surname, getString(R.string.student_info_gender) to getString(if (studentInfo.gender == Gender.MALE) R.string.student_info_male else R.string.student_info_female), getString(R.string.student_info_polish_citizenship) to getString(if (studentInfo.hasPolishCitizenship) R.string.all_yes else R.string.all_no), getString(R.string.student_info_family_name) to studentInfo.familyName, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 938be98da..1d63f0943 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextSchoolDay +import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.GlobalScope @@ -151,8 +152,14 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { val remoteView = RemoteViews(context.packageName, layoutId).apply { setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) - setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize()) - setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data)) + setTextViewText( + R.id.timetableWidgetDate, + date.toFormattedString("EEEE, dd.MM").capitalize() + ) + setTextViewText( + R.id.timetableWidgetName, + student?.nickOrName ?: context.getString(R.string.all_no_data) + ) setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) diff --git a/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt new file mode 100644 index 000000000..fdd0610a0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/StudentExtension.kt @@ -0,0 +1,5 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.db.entities.Student + +inline val Student.nickOrName get() = if (nick.isBlank()) studentName else nick diff --git a/app/src/main/res/layout/dialog_account_edit_details.xml b/app/src/main/res/layout/dialog_account_edit.xml similarity index 90% rename from app/src/main/res/layout/dialog_account_edit_details.xml rename to app/src/main/res/layout/dialog_account_edit.xml index 0b13e0427..b65f85ac3 100644 --- a/app/src/main/res/layout/dialog_account_edit_details.xml +++ b/app/src/main/res/layout/dialog_account_edit.xml @@ -18,7 +18,7 @@ android:layout_marginStart="24dp" android:layout_marginTop="24dp" android:layout_marginEnd="24dp" - android:text="Modify data" + android:text="@string/account_edit_header" android:textSize="21sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" @@ -34,16 +34,21 @@ android:layout_marginStart="24dp" android:layout_marginTop="28dp" android:layout_marginEnd="24dp" - android:hint="Nick" + android:hint="@string/account_edit_nick_hint" + app:endIconMode="clear_text" app:errorEnabled="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader"> + + + android:inputType="textPersonName" + android:maxLength="20" /> diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index 306dc2190..5d3d81e2e 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -45,7 +45,7 @@ android:orientation="vertical" android:visibility="invisible" tools:ignore="UseCompoundDrawables" - tools:visibility="visible"> + tools:visibility="gone"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + android:visibility="invisible" + tools:ignore="UseCompoundDrawables" + tools:visibility="visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_login_form.xml b/app/src/main/res/layout/fragment_login_form.xml index 412502b52..fb0e138e9 100644 --- a/app/src/main/res/layout/fragment_login_form.xml +++ b/app/src/main/res/layout/fragment_login_form.xml @@ -237,9 +237,7 @@ android:layout_gravity="center_vertical" android:layout_marginTop="48dp" android:layout_marginEnd="24dp" - android:layout_marginBottom="16dp" android:text="@string/login_sign_in" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f48179890..e41f2f28c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -412,6 +412,12 @@ Phones Male Female + Last name + + + + Nick + Add nick @@ -444,6 +450,7 @@ Search… Yes No + Save From 42f95942101617fae8610f903c5f4c3d3e13ae0d Mon Sep 17 00:00:00 2001 From: Mateusz Idziejczak Date: Tue, 2 Feb 2021 00:11:34 +0100 Subject: [PATCH 108/110] Fix empty message bug (#1122) --- .../ui/modules/message/preview/MessagePreviewPresenter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt index 55b9631af..702e54676 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/preview/MessagePreviewPresenter.kt @@ -70,6 +70,7 @@ class MessagePreviewPresenter @Inject constructor( this@MessagePreviewPresenter.attachments = it.data.attachments view?.apply { setMessageWithAttachment(it.data) + showContent(true) initOptions() } analytics.logEvent( From aff40df707f06fab9f35fd4164d508dbddfe508d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Tue, 2 Feb 2021 00:25:27 +0100 Subject: [PATCH 109/110] New Crowdin updates (#1095) --- app/src/main/res/values-cs-rCZ/strings.xml | 71 ++++++++++++---- app/src/main/res/values-de/strings.xml | 53 ++++++++++-- app/src/main/res/values-pl/strings.xml | 45 +++++++++- app/src/main/res/values-ru/strings.xml | 95 ++++++++++++++++------ app/src/main/res/values-sk-rSK/strings.xml | 91 +++++++++++++++------ app/src/main/res/values-uk/strings.xml | 67 ++++++++++++--- 6 files changed, 334 insertions(+), 88 deletions(-) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index e2e7bdac0..fc01584b5 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -17,7 +17,10 @@ Nová zpráva Poznámky a úspěchy Domácí úkoly - Vyberte účet + Manažer účtů + Vyberte účet + Manažer účtů + Informace o žáku Semestr %1$d, %2$d/%3$d @@ -44,11 +47,11 @@ Neplatný e-mail Místo e-mailu použijte přiřazené přihlašovací údaje Neplatný symbol - Student nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ + Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+ Toto pole je povinné - Vybraný student je již přihlášen + Vybraný žák je již přihlášen Symbol najdete na stránce deníku v  Uczeń →  Dostęp Mobilny →  Zarejestruj urządzenie mobilne.\n\nUjistěte se, že jste na předchozí obrazovce nastavili správnou variantu deníku do pole Variace deníku UONET+. Wulkanowy v tuto chvíli nezjistí předškolní studenty - Vyberte studenty, kteří se mají do aplikace přihlásit + Vyberte žáky, kteří se mají do aplikace přihlásit Jiné možnosti V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, absolvované lekce, informace o škole a prohlížení seznamu registrovaných zařízení Tento režim zobrazuje stejná data, která se zobrazují na webových stránkách deníka @@ -63,7 +66,7 @@ Zapomněl jsem své heslo Obnovte svůj účet Obnovit - Student je již přihlášen + Žák je již přihlášen Manažer účtů Přihlásit se @@ -84,17 +87,21 @@ Bez průměru Předpovězeno: %1$s Konečná: %1$s - Celkem bodů + Součet bodů Konečná známka Předpokládaná známka Vypočítaný průměr Konečný průměr - Souhrn + Shrnutí Třída Označit jako přečtené Částečně Semestr Body + Vysvětlivky + Průměr: %1$s + Třída + Žák %d známka %d známky @@ -139,7 +146,7 @@ Lekce - Pokoj + Učebna Skupina Hodiny Změny @@ -164,10 +171,10 @@ Zobrazit další lekce Žádné informace o dalších lekcích - Souhrn docházky + Shrnutí docházky Nepřítomen ze školních důvodů Omluvená nepřítomnost - Neomluvená absence + Neomluvená nepřítomnost Osvobození Omluvené zpoždění Neomluvené zpoždění @@ -189,7 +196,7 @@ Ospravedlnit Účast - Celkový + Společně Tento týden žádné testy Typ @@ -215,7 +222,7 @@ Téma Obsah Zpráva úspěšně odeslána - Message does not exist + Zpráva neexistuje Musíte vybrat alespoň 1 příjemce Obsah zprávy musí mít alespoň 3 znaky @@ -337,12 +344,19 @@ Přidat účet Odhlásit se - Chcete se odhlásit z aktivního studenta? - Odhlášení studentů - Studentský účet + Chcete se odhlásit z aktivního žáka? + Odhlášení žáků + Žákův účet Rodičovský účet Režim Mobilního API Hybridní režim + Upravit data + Manažer účtů + Vyberte žáka + Rodina + Kontakt + Údaje o adresách + Osobní údaje Verze aplikace Tvůrci @@ -366,6 +380,30 @@ Avatar Zobrazit více na GitHub + + Žádné informace o žácích + Jméno + Druhé jméno + Pohlaví + Polské občanství + Rodinné jméno + Jména matky a otce + Telefon + Mobilní telefon + E-mail + Adresa bydliště + Registrovaná adresa + Korespondenční adresa + Příjmení a jméno + Stupeň příbuznosti + Adresa + Telefony + Muž + Žena + Příjmení + + Přezdívka + Přidat přezdívku Sdílejte protokoly Obnovit @@ -390,6 +428,9 @@ Další Hledat Hledat… + Ano + Ne + Uložit Žádné lekce Vybrat motiv diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7ba35734c..8d0bf3e98 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -17,7 +17,10 @@ neue Nachricht Eintragen und Erfolgen Hausaufgaben - Wählen Sie ein Konto + Konten-Manager + Konto auswählen + Kontodetails + Schülerinfo Semester %1$d, %2$d/%3$d @@ -95,6 +98,10 @@ Partiell Semester Punkte + Legende + Durchschnitt: %1$s + Klasse + Schüler %d Note %d Noten @@ -146,9 +153,9 @@ Abwesenheit Ressourcen - Additional lessons - Show additional lessons - No info about additional lessons + Zusätzliche Lektionen + Zusätzliche Lektionen anzeigen + Keine Infos zu zusätzlichen Lektionen Übersicht über die Schulbesuch Aus schulischen Gründen abwesend @@ -199,7 +206,7 @@ Thema Inhalt Nachricht erfolgreich gesendet - Message does not exist + Nachricht existiert nicht Sie müssen mindestens 1 Empfänger auswählen. Der Inhalt der Nachricht muss mindestens 3 Zeichen lang sein. @@ -297,12 +304,19 @@ Konto hinzufügen Abmelden - Wollen Sie sich von einem aktiven Studenten abmelden? + Wollen Sie diesen Schüler abmelden? Abmeldung von Student Studentenkonto Elternkonto Mobiler API Modus Hybrid Modus + Daten bearbeiten + Konten-Manager + Schüler auswählen + Familie + Kontakt + Wohnungsdetails + Persönliche Information Version der App Mitarbeiter @@ -326,6 +340,30 @@ Benutzerbild Sehen Sie mehr auf GitHub + + Keine Informationen über Schüler + Name + Zweite Name + Geschlecht + Polnische Staatsbürgerschaft + Familienname + Namen von Mutter und Vater + Telefonnummer + Mobiltelefonnummer + E-Mail + Wohnadresse + Adresse der Registrierung + Korrespondenzadresse + Nachname und Vorname + Verwandtschaftsgrad + Adresse + Telefonnummern + Mann + Frau + Nachname + + Nick + Nick hinzufügen Logs teilen Aktualisieren @@ -350,6 +388,9 @@ Nächste Suchen Suchen… + Ja + Nein + Speichern Keine Lektionen Thema wählen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 19e312bab..2f849bc1b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -17,7 +17,10 @@ Nowa wiadomość Uwagi i osiągnięcia Zadania domowe - Wybierz konto + Menadżer kont + Wybierz konto + Szczegóły konta + Informacje o uczniu Semestr %1$d, %2$d/%3$d @@ -95,6 +98,10 @@ Cząstkowe Semestralne Punkty + Legenda + Średnia: %1$s + Klasa + Uczeń %d ocena %d oceny @@ -337,12 +344,19 @@ Dodaj konto Wyloguj - Czy chcesz wylogować aktualnego ucznia? + Czy chcesz wylogować tego ucznia? Wylogowanie ucznia Konto ucznia Konto rodzica Tryb API mobilne Tryb hybrydowy + Edytuj dane + Menadżer kont + Wybierz ucznia + Rodzina + Kontakt + Dane adresowe + Dane osobowe Wersja aplikacji Twórcy @@ -366,6 +380,30 @@ Awatar Zobacz więcej na GitHub + + Brak informacji o uczniu + Imię + Drugie imię + Płeć + Obywatelstwo polskie + Nazwisko rodowe + Imiona matki i ojca + Telefon + Telefon komórkowy + E-mail + Adres zamieszkania + Adres zameldowania + Adres korespondencyjny + Nazwisko i imię + Stopień pokrewieństwa + Adres + Telefony + Mężczyzna + Kobieta + Nazwisko + + Pseudonim + Dodaj pseudonim Udostępnij logi Odśwież @@ -390,6 +428,9 @@ Następny Szukaj Szukaj… + Tak + Nie + Zapisz Brak lekcji Wybierz motyw diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9c4efa450..158c91bc0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -8,7 +8,7 @@ Тесты Расписание Настройки - Другое + Ещё О приложении Просмотр журнала Разработчики @@ -17,7 +17,10 @@ Новое сообщение Предупреждения и свершения Домашние задания - Выберите аккаунт + Менеджер аккаунтов + Выбор учетной записи + Данные аккаунта + Информация о студенте %1$d семестр, %2$d/%3$d @@ -27,10 +30,10 @@ Электронная почта Логин, PESEL или электронная почта Пароль - Разновидностью бревна UONET+ - Mobile API + UONET + вариант регистрации + Мобильный API Scraper - Hybrid + Гибрид Token PIN Ключ API @@ -39,15 +42,15 @@ Слишком короткий пароль Данные для входа неверны. Убедитесь, что в поле ниже выбран правильный вариант регистра UONET+ Неправильный PIN - Неправильный token - Токен просрочен + Неверный token + Token просрочен Неверный адрес электронной почты Используйте назначенный логин вместо электронной почты - Неправильный symbol + Неправильный символ Студент не найден. Подтвердите символ и выбранный вариант регистра UONET+ - Обязательное поле + Это обязательное поле Данный ученик уже авторизован - Этот символ можно найти на странице регистрации в  Uczeń →  Dostęp Mobilny →  Zarejestruj urządzenie mobilne.\n\nУбедитесь, что вы установили соответствующий вариант регистра в поле Разновидностью бревна UONET+ на предыдущем экране. Wulkanowy на данный момент не обнаруживает дошкольников + Этот символ можно найти на странице регистрации в  Ученик →  Телефонный доступ →   Зарегистрируйте мобильное устройство.\n\nУбедитесь, что вы установили соответствующий вариант регистра в поле Разновидностью бревна UONET+ на предыдущем экране. Вулкановый на данный момент не обнаруживает дошкольников Выберите учеников для авторизации в приложении Другие варианты В этом режиме не работают: счастливый номер, статистика класса по оценкам, статистика посещаемости и уроков, информация о школе и список зарегистрированных устройств @@ -60,7 +63,7 @@ Отправить письмо Опишите детали проблемы: Убедитесь, что вы выбрали правильный вариант регистра UONET+! - Забыли пароль? + Я забыл свой пароль Восстановите свой аккаунт Восстановить Студент уже вошел в систему @@ -68,7 +71,7 @@ Менеджер аккаунтов Войти Сеанс истёк - Сеанс истёк, пожалуйста, авторизируйтесь ещё раз + Сеанс истёк, пожалуйста, войдите ещё раз Оценка Семестр %d @@ -95,6 +98,10 @@ Частичные За семестр Баллы + Легенда + Средняя: %1$s + Класс + Студент %d оценка %d оценки @@ -132,7 +139,7 @@ Вы получили %1$d ожидаемых оценок - Вы получили %1$d финальную оценку + Вы получили %1$d итоговою оценку Вы получили %1$d итоговых оценки Вы получили %1$d итоговых оценок Вы получили %1$d финальные оценки @@ -143,7 +150,7 @@ Группа Часы Изменения - Нет уроков в данный день + Нету уроков в данный день %s мин %s сек %1$s осталось @@ -160,9 +167,9 @@ Отсутствие Ресурсы - Additional lessons - Show additional lessons - No info about additional lessons + Дополнительные уроки + Показать дополнительные уроки + Нет информации о дополнительных уроках Итоговая посещаемость Отсутствие по школьным причинам @@ -215,7 +222,7 @@ Тема Текст Сообщение успешно отправлено - Message does not exist + Сообщения не существует Вы должны выбрать как минимум одного получателя Текст сообщения должен содержать как минимум 3 знака @@ -237,7 +244,7 @@ Вы получили %1$d новых сообщений - Нет данных о предупреждениях + Нет информации о заметках Баллы %d предупреждение @@ -297,19 +304,19 @@ Нет домашних заданий - сделанный - Не сделано + Отметить как выполненное + Отметить как невыполненное Вложения Счастливый номер - Сегодняшний счастливый номер + Сегодняшний счастливый номер это Нет данных о счастливом номере Сегодняшний счастливый номер - Сегодняшний счастливый номер: %d + Сегодняшний счастливый номер это: %d Мобильные устройства Нет устройств - Удалить + Отменить регистрацию Устройство удалено QR-код Token @@ -323,8 +330,8 @@ Название школы Адрес школы Телефон - Директор - Педагог + Имя директора + Имя педагога Показать на карте Позвонить @@ -343,10 +350,17 @@ Профиль родителя Режим Mobile API Гибридный режим + Изменить данные + Менеджер аккаунтов + Выберите Студента + Семья + Контакт + Детали проживания + Персональные данные Версия приложения Разработчики - Список разработчиков \"Wulkanowy\" + Список разработчиков \"Вулкановый\" Возникла ошибка? Сообщить о ошибке FAQ @@ -366,6 +380,30 @@ Aватар Страница проекта на GitHub + + Нет информации о студенте + Имя + Фамилия + Пол + Польское гражданство + Фамилия + Имена матери и отца + Телефон + Сотовый телефон + Эл. почта + Адрес проживания + Адрес регистрации + Адрес переписки + Фамилия и имя + Степень родства + Адрес + Телефоны + Муж + Женская + Фамилия + + Ник + Добавить ник Поделиться логами Обновить @@ -390,6 +428,9 @@ Следующий Поиск Поиск… + Да + Нет + Сохранить Нет уроков Выбрать тему diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 54012b1ff..b5c0116ec 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -17,7 +17,10 @@ Nová správa Poznámky a úspechy Domáce úlohy - Vyberte účet + Accounts manager + Select account + Account details + Student info Semester %1$d, %2$d/%3$d @@ -44,11 +47,11 @@ Neplatný e-mail Namiesto e-mailu použite priradené prihlasovacie údaje Neplatný symbol - Študent nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ + Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+ Toto pole je povinné - Vybraný študent je už prihlásený + Vybraný žiak už je prihlásený The symbol can be found on the register page in Uczeń → Dostęp Mobilny → Zarejestruj urządzenie mobilne.\n\nMake sure that you have set the appropriate register variant in the UONET+ register variant field on the previous screen. Wulkanowy does not detect pre-school students at the moment - Vyberte studenty, kteří se mají do aplikace přihlásit + Vyberte žiakov, ktorí sa majú do aplikácie prihlásiť Iné možnosti In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices This mode displays the same data as it appears on the register website @@ -84,17 +87,21 @@ Bez priemeru Predpovedané: %1$s Konečná: %1$s - Celkom bodov + Súčet bodov Konečná známka Predpokladaná známka Vypočítaný priemer Konečný priemer - Súhrn + Zhrnutie Trieda Označiť ako prečítané - Čiastočne + Čiastočné Semester Body + Legenda + Average: %1$s + Trieda + Žiák %d známka %d známky @@ -109,27 +116,27 @@ Nová predpokladaná známka - Nové predpovedanej známky - Nové predpovedanej známky - Nové predpovedanej známky + Nové predpokladané známky + Nové predpokladané známky + Nové predpokladané známky Nová konečná známka - Nové konečnej známky - Nové konečnej známky - Nové konečnej známky + Nové konečné známky + Nové konečné známky + Nové konečné známky Máte %1$d novú známku Máte %1$d nové známky - Máte %1$d nové známky - Máte %1$d nové známky + Máte %1$d nových známok + Máte %1$d nových známok Máte %1$d novú predpokladanú známku Máte %1$d nové predpokladané známky - Máte %1$d nové predpokladané známky - Máte %1$d nové predpokladané známky + Máte %1$d nových predpokladaných známok + Máte %1$d nových predpokladaných známok Máte %1$d novú konečnú známku @@ -139,7 +146,7 @@ Lekcia - Room + Učebňa Skupina Hodiny Zmeny @@ -158,9 +165,9 @@ No info about completed lessons Téma Neprítomnosť - Resources + Zdroje - Additional lessons + Ďalší lekcie Show additional lessons No info about additional lessons @@ -171,11 +178,11 @@ Exemption Excused lateness Unexcused lateness - Present + Prítomnosť Deleted - Unknown + Neznámy Number of lesson - No entries + Žiadne položky %1$d absence %1$d absences @@ -183,7 +190,7 @@ %1$d absences Absence reason (optional) - Send + Poslať Absence excused successfully! You must select at least one absence! Excuse @@ -337,12 +344,19 @@ Add account Logout - Do you want to log out of an active student? + Do you want to log out this student? Student logout Student account Parent account Mobile API mode Hybrid mode + Edit data + Accounts manager + Select student + Family + Contact + Residence details + Personal information App version Contributors @@ -366,6 +380,30 @@ Avatar See more on GitHub + + No info about student + Name + Second name + Gender + Polish citizenship + Family name + Mother\'s and father\'s names + Phone + Cellphone + E-mail + Address of residence + Address of registration + Correspondence address + Surname and first name + Degree of kinship + Address + Phones + Male + Female + Last name + + Nick + Add nick Share logs Refresh @@ -390,6 +428,9 @@ Next Search Search… + Yes + No + Save No lessons Choose theme diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a1511d9fe..3c4b44360 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -17,11 +17,14 @@ Нове повідомлення Нотатки та досягнення Домашні завдання - Оберіть аккаунт + Менеджер аккаунтів + Виберіть обліковий запис + Деталі облікового запису + Інформація про учня %1$d семестр, %2$d/%3$d - Авторизуйтеся за допомогою аккаунта учня або батька + Авторизуйтеся за допомогою аккаунта учня або батьків Введіть символ зі сторінки реєстру Ім\'я користувача Електронна пошта @@ -43,11 +46,11 @@ Минув термін дії токену Недійсна адреса електронної пошти Використовуйте призначений логін замість електронної пошти - Неправильний symbol + Неправильний симбвол Студента не знайдено Перевірте символ та обраний варіант реєстру UONET+ Обов\'язкове поле Даного учня вже авторизовано - Символ можна знайти на сторінці реєстру в   Uczeń →   Dostęp Mobilny →   Zarejestruj urządzenie mobilne .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів + Символ можна знайти на сторінці реєстру в   Учень →   Мобільний доступ →   Додайте мобільне приладдя .\n\nПереконайтесь, що ви встановили відповідний варіант реєстру в полі UONET + варіант реєстрації на попередньому екрані. На даний момент Wulkanowy не виявляє учнів дошкільних закладів Виберіть учнів для авторизації в додатку Інші варіанти У цьому режимі не працюють: щасливий номер, статистика класу по оцінкам, статистика відвідуваності і уроків, інформація про школу і список зареєстрованних пристроїв @@ -61,7 +64,7 @@ Опишіть деталі помилки: Переконайтеся, що ви вибрали правильний варіант реєстрації UONET+! Забули пароль? - Відновіть свій аккаунт + Відновіть свій обліковий запис Відновити Учень вже увійшов до системи @@ -73,15 +76,15 @@ Оцінка %d семестр Змінити семестр - Брак оцінок + Оцінки відсутні Вартість Вартість: %s Коментар - Брак нових оцінок + Нові оцінки відсутні Кількість нових оцінок: %1$d Середня оцінка: %1$.2f - точок: %s - Брак середньої оцінки + Балів: %s + Відсутність середньої оцінки Очікувана оцінка: %1$s Підсумкова оцінка: %1$s Сума балів @@ -95,6 +98,10 @@ Поточні Семестрові Бали + Умовні позначення + Середня оцінка: %1$s + Клас + Учень %d оцінка %d оцінки @@ -160,9 +167,9 @@ Відсутність Ресурси - Additional lessons - Show additional lessons - No info about additional lessons + Додаткові уроки + Показати додаткові уроки + Немає інформації про додаткових уроків Підсумок відвідуваності Відсутність зі шкільних причин @@ -215,7 +222,7 @@ Тема Зміст Повідомлення було успішно відправлено - Message does not exist + Такого повідомлення не існує Необхідно обрати принаймні 1 адресата Зміст повідомлення мусить складатися принаймні з 3 знаків @@ -343,6 +350,13 @@ Головний рахунок Режим мобільного API Гібридний режим + Редагувати дані + Менеджер аккаунтів + Виберіть учня + Сім\'я + Контакт + Деталі проживання + Особиста інформація Версія додатка Розробники @@ -366,6 +380,30 @@ Аватар Сторінка проекту на GitHub + + Брак інформації про учня + Ім\'я + Друге ім\'я + Стать + Польське громадянство + Прізвище + Імена батька і матері + Номер телефону + Мобільний телефон + Електронна пошта + Місця проживання + Адреса реєстрації + Адреса для кореспонденції + Прізвище та ім\'я + Ступінь спорідненості + Адреса + Телефони + Чоловіча + Жінка + Прізвище + + Псевдонім + Додати псевдонім Поділитися логами Оновити @@ -390,6 +428,9 @@ Наступний Пошук Пошук… + Так + Ні + Зберегти Брак уроків Увібрати тему From dfa10883d32958f4b6b7f9db2f3de210e67bd1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Tue, 2 Feb 2021 00:47:30 +0100 Subject: [PATCH 110/110] Version 0.25.0 --- app/build.gradle | 8 ++++---- app/src/main/play/release-notes/pl-PL/default.txt | 12 +++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0d42ad711..f2efb4218 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 17 targetSdkVersion 30 - versionCode 81 - versionName "0.24.3" + versionCode 82 + versionName "0.25.0" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -129,7 +129,7 @@ play { serviceAccountCredentials = file('key.p12') defaultToAppBundles = false track = 'alpha' - updatePriority = 5 + updatePriority = 3 } ext { @@ -142,7 +142,7 @@ ext { } dependencies { - implementation "io.github.wulkanowy:sdk:b7576e86" + implementation "io.github.wulkanowy:sdk:0.25.0" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 1af17e0e5..e33059d83 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,9 +1,7 @@ -Wersja 0.24.3 -- naprawiliśmy odczytywanie wiadomości -- naprawiliśmy niekończące się ładowanie w ocenach na drugim semestrze -- naprawiliśmy ciemny motyw na MIUI 12 -- dodaliśmy automatyczne odświeżanie danych w aplikacji -- naprawiliśmy wysyłanie wiadomości kiedy uczeń zalogowany był/jest przez konto ucznia i rodzica -- dodaliśmy zakładkę lekcji dodatkowych (na górnym pasku w planie lekcji) +Wersja 0.25.0 +- naprawiliśmy przełączanie semestrów przy przełączaniu uczniów +- naprawiliśmy błąd przy odświeżaniu ocen gdy włączony był inny niż domyślny tryb liczenia średniej +- dodaliśmy menadżer kont, gdzie można podejrzeć informacje o uczniu oraz zmienić pseudonim +- zmieniliśmy ikonę planu lekcji oraz kolor animacji odświeżania w ciemnym motywie Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases