From e86c1bad1d6f0e1afb58eb321381c79cb11e65cf Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Mon, 13 Dec 2021 13:39:06 +0100 Subject: [PATCH] = 0.8.0-dev = - code cleanup - caching - black-/whitelist - sz/news fixes - added settings options ... --- android/app/build.gradle | 2 +- android/app/proguard-rules.pro | 23 ++ android/app/src/main/AndroidManifest.xml | 8 +- .../src/main/res/drawable-hdpi/app_icon.png | Bin 0 -> 3498 bytes .../src/main/res/drawable-mdpi/app_icon.png | Bin 0 -> 2354 bytes .../src/main/res/drawable-xhdpi/app_icon.png | Bin 0 -> 4798 bytes .../src/main/res/drawable-xxhdpi/app_icon.png | Bin 0 -> 7449 bytes .../main/res/drawable-xxxhdpi/app_icon.png | Bin 0 -> 10019 bytes android/app/src/main/res/raw/keep.xml | 3 + ios/Runner/AppDelegate.swift | 4 + lib/Settings/Pages/dev_settings.dart | 7 +- lib/Settings/Pages/info_settings.dart | 32 +- lib/Settings/Pages/plan_settings.dart | 239 ++++++++++++--- lib/Settings/Pages/user_settings.dart | 89 +++++- lib/Settings/dashboard.dart | 4 +- lib/cache_manager.dart | 20 ++ lib/dashboard.dart | 115 ++++++-- lib/generated_plugin_registrant.dart | 4 +- lib/login.dart | 7 +- lib/main.dart | 21 +- lib/networking.dart | 120 ++++---- lib/news.dart | 224 ++++++++++++++ lib/notifications.dart | 1 + lib/presets/colors.dart | 2 +- lib/schuelerzeitung.dart | 279 +++++++++++++----- lib/timetable.dart | 74 +++-- pubspec.yaml | 14 +- web/manifest.json | 4 +- 28 files changed, 1017 insertions(+), 279 deletions(-) create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/res/drawable-hdpi/app_icon.png create mode 100644 android/app/src/main/res/drawable-mdpi/app_icon.png create mode 100644 android/app/src/main/res/drawable-xhdpi/app_icon.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/app_icon.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/app_icon.png create mode 100644 android/app/src/main/res/raw/keep.xml create mode 100644 lib/cache_manager.dart create mode 100644 lib/news.dart create mode 100644 lib/notifications.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 26c5fa5..cfa383a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "de.cantorgymnasium.meincantor" - minSdkVersion 18 + minSdkVersion 20 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..2d6b4a9 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,23 @@ +## Gson rules +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-dontwarn sun.misc.** +#-keep class com.google.gson.stream.** { *; } + +# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, +# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Prevent R8 from leaving Data object members always null +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 336c0b8..729401f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,8 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + android:showWhenLocked="true" + android:turnScreenOn="true" - + />--> diff --git a/android/app/src/main/res/drawable-hdpi/app_icon.png b/android/app/src/main/res/drawable-hdpi/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..975f3679e21f610346c382d208618dd4d8ccbf1e GIT binary patch literal 3498 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 Xqbf1Ic!Su#4n}8YYz2L9cn9+zBX)W( literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/app_icon.png b/android/app/src/main/res/drawable-mdpi/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c3206495d789b4d2d777c653d02eb5f2cfcabb6e GIT binary patch literal 2354 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�wu)+0{MUg;oi>iek$G6AYG9sB;p6+?b5G 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/app_icon.png b/android/app/src/main/res/drawable-xxxhdpi/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..31cfe902752c45a3905d6983eb80c9d1d1b86567 GIT binary patch literal 10019 zcmeHtXH-)`*KUF+ND~C4gQyfiiU=wtfRrGhgeoXPK$=o5^w6b92Wbit3`KeukRU}t z=_R3r7LX1hfIvbB^&a1EeQVuc_uu{Z{>WJ?=ggkjvuDrjXFq$Q4Gnad8F(2$AP}?e zeJx`U2we5=M}H1@(`6tl0Ro8=*wpa~+IY&A$i^e8+Z!qS64ovHe0FbKp!2R;XSdGWuG|JvaH@&k^0c!Ke8 zB(lEjs+&=7Y_OFK+Sz>9v^dQ4#jRgzk8b1~V<^q1r?pFYTXHf7OC_V2y0Pclq`c-J zYvF!NS9zamd7oqq@hp}oRN%3~D+SN~1Hbf=7JTj~CO%t$SGT`m_bZGYrtH4CH{7`##ker&5k$BpTfEI-sGxhah{zV;eih4t}krGwPc&mi-L+ZLQt2}2JPQE zcS4c%#ak~j%4;DvrkYBU{`y30>Bkd%F(P%7YZ)PyXgP*Dq5QQsaV;ORnZLfcReO() z;|dMa*>;vg0nE?gWT}7+9&%NWvNy%NJe7IUsy;!(trdRKv{4>^)MbrQJ<@y9e7o^B zjYD^}hpo-80Di zA`YSDHZGW4#-1qqIggE-G&y1tr+jxzxS#MDzqkeKY^cxp`Z^xdv4&O7;cY%Gz=+NA zlrz3}7fw`U*9>DBeT_6m@veY}2TCK1>~X&hY(J__Yli&bIh0SUP`sW8 zsTcYB`bP^BakL|`f>;U9<|h0u)ad_^tE$yQ?)sA{rw|wXh<_hyI!@pi_Gc0ixAsr3~e`n~Pd8h(; zr3)3KFv|P9!j|95WU~?H^p2QY>O551HB?KBx<5a-G1N6Yo^@+mPur3zD9%jF{o1_~ zifwjsnvddUFdx-J9(WBFkq1!HJp9fRCI5yHZc8#j+(|}$%l{eA&@tI&@(#W=(%pgNPf-pfU3z*ubN$}aITo2(yMEu-R8%c#yLfI>R&&^ zN`>{(%W`B{m2DrHee|sn9Ouy4T5*YrL?$#}9~zW=@6P;GI|OARVV1N>(Dp^`6MrTI z;RDG;AyGWX-55~2Xf2mBL*584wQVf!ut{GdNb8@oR@0t83l8 zay|RH4YV&Xwm$Hy6}uzV9-kE%Q-IEalcvkrENzoG$8O!#s5DxWg@mnfz55j{E<@*o?g~@$>j z+bqz_9u=3Q6^TF-`7R&Lui=RtLcnXU%h}TyXpE0`z^^~N5vdWn_m}M<`jrUlG$u5 z20upmhI8!YmsxDyXy|$rjtYa^pkdnn zV$|fv@iMgBCb%aQnwa3AS~kRHb9VFFF+^ZC(@*BxQV)AT^X=RkXx{?WVhEt?iKOwBWNF|D|94z_B8?_c@fbt{9syl=p`rruy>0 zBT6Ob*@;UE1ybw&lP&kWBk*HnBJ{PeEf5w8QhgY$cXS~mIEpgC}?)V;@mk=~Wd1wRrKwGMKlO5{bubt?l{ ze?z3FuJ(xCHWdqYsWdcaPRsOIDPFKfCOn@jcK7;8#7V2o;B=2yBRDV8l!Z)hQ{o7? zMU>No_9dTIj^}Nq-Q=&gM`K2@O*tR;(FCJnt6MV2(~0Ey-s<}Pfb5~)tI@A+tcr60 zQ&@fVfs1Qh%5y7_&AID&UbRVFdc^~@!Y?O;Xl+Wfk~ zwt|}@s}XUo0#R3dl@zdvUvFsXnRh2l3JLh|0yEyBtuLffE zWD=!4V6^Dd$zbrnl{|zs3whjvy>ed5_Hvu?W==5KtQC47WYc>4j{gU%y_ZFI7``dB zALZmL!)1X;@%aarqcNhVKG4YTZ=E4djqQ?)rs6a-q+ooEoXrnyIg!; zr?Ah7tq;*6?p4jht$3c*=w7)=2T7mJ5PRCC57E0XB`S&`JCtn{B0^B|e3ItmDre-umWN+lgt;{lxo-P>K&tlwHtYF=xAxVPB_5na`iS_Jah$j zz7ubX;aC^}2sOts!|%In#UAmPHN!^FzbV2hY5Rl=h4k;#g2=GTsDe+J05P5Vzs7;w z^Ja;WynCXUG<}(18~IoMpObM8rZOm2OEh+Mp053y6Eg=V(AX^t7(g&M_OFDL_p9(g zMQ&gB2^O()sOVBB5<+?;x4_|`Aen~NB1XPQM4(Dv=?rngr?zy+(BBnCJPHjx@S^Ak z+ICL1JztEPIDTs4m`cddhyhj-TbfrH!-?gxF>&N33vx*0@0t;ifw&@DsXYBendZZ; zOVbc1_0Ned(seSSH1|RZ!pl{HJWELtxbMH9PSb3%D0t+#1f~V{9G?lL2Fh^EDAY$q z$a!delmAN05#VAS*Ehb8NM$#0{ysgZyv$IYZ)t4d`t6gi%jbIlq=B(Wc;9&9O!|2U zrRe;4?sAK!Q6r>4YtUd#auR{BGMw#)p*bU7%o(rc2ZhdjSImnPDazhooI*(l-_7rg zFa{KA6P(hs9Md)5>J?13<>I`oSWira{5qHmw!d8*(~KV^Pv#o}hllYzV?hi^Ka{1Z zu#`gX=`aiwvioJLTIE#2><=0F7o{P{0g*6vzRl@`NDvt3tP5G8d%HEpm zONp%3)Wt7_KYfF3Q_k^iMn+(C#0K8n4X@+IDxzNf+sM962>`Z*pHtvlbTZQA=CWd}91AY+sm>I5+hk&}yhvzU+ z8Np&dJZdsT!w-@jc0npM*MS14Ms|QGFXw7syMFL5Mdcm5i9GLk{z~x6}8YjC#wxTWf*fE@qm( zn-BTW$^p+GQWHRDlK5yp*wvS0{cBMBShoSB{sfp(@xuPO<&TW3q5sofK~fAKHgCzr z-T%c`CWIfbp^4jWzNKNn>;Su%%$Y78pigNTRF2ZyIZaR_;2mGW+QEBNV!kEl@>+FYG%LTrxXXd ziPM&w+d2KI29~iuHkvvnlJN7+kblpp#k?yoorcuGz`4 z+)KZ60Whx}pdrH&zpE$94Qo*{4?m$wR9ZL=bq$T_?uF{o*gTm(H(ghbaCYb?fsUDAe$5D1V&cssG*%VMj@n_a|(q<9v3# zNI{#-l8zK%x$t*K>LI8;)5;rC7VkYJ-&2NPuYEt%U>0;3Y0aqoO~&iqO#G`MDBSjR z!I|rZaqBtoo=ZyWO{K*Wbs$oX005QQW#yD^{YfI*8Wb3Hi8AZ2kXtzWAmj90V?#h1 z>5t2V?XOZ8X_O3tx3q4nWpL0iQNuxR;vl1UO;VXP{fex~z|L<4NC)6hBw(tcK4elI z{t}hSgLuZmM12c1Eq9zFpwb`m?fjkkkl3oAL?ozNP5CM{W>;p{_f=l*MMi=a=>X%p z2uOE+)oefV=Uyr0EG>wFeq3b$RD_u$lc-ip3HenFZ2uWL@EZ``JtbqD3d`TSha!kI zhipRS!+tvZMwNYuy+$I@g4{dj0q^~#UvRGOy}S1INaa7w^yrFv$pmA*z956w>^no; zr%y&v_>}c-Il9OIjSxl0?ij!?iXnd6!kf-y!LSXQ6&)K*LDEdh7&E1EybU1u)@7c zGX|J7F8t3jl|NT?2{l^qcNeu-ib1kWwhv3P`>`%(3OxFLD3Yhsc@9nWghy?q96D3_ zLWOf!ZCDN#Eq%`P5yoZ`Y5L{_Cj16k7D+^*k{9iUT%LPgBQ1@La?Zk6{?qcWBs(Pa zpMmRUCWZlk^ik~^{=H|`Nweobh~d8+v&9mMxO3g_APN3&l5dGBA9{(F-#0j?$OGT|lt<+^ODuli3#0LIh|~Lrl>2Crh#xHg zKz~}qHN)t&ncR}#X7=6Aa=LjT2?}&QR@VE-VSHk4GR!;elYm~pf?We5rGn~T;W>9~ zOWI*2{C4N58@FY@8cnMSYzKaYT{`x&AEsa_XS{{m-tSD@SX<>g1E8J4nNIr{75P=d z9&TWNDk#cYc8=*TBq%PJ#M5=y+qSz>tW!f)>bwkbu8Xi)R*k> z`{6C6`APL-w9|;{4^8<{X21-4M+lU^S6^UrQ_Wa_B}}J|MEXhVZ%h5nBI(@(8FG&= z*i||#)6lX8+TM2ig2&m7fzYwI8;1Euje6 zkhdD1F-ihZ%Lh_?G)>>J;D@@EJtUgAzDA|2MJ&ZGPwA})CQtl1b%(1YxG!~f);%TJ zeBRErrQjB0uO&~3QlLkvq0N)N}pO=8u5^7aY#592K(50`~jpI$T)i7xx3 zN*yRV)ki@CI#!~v+5iCia2@J`<4KdrNU;959V4F-OE(HaTi_o&T^Obm#12c$^HwQo z8}S>}<%M)IUC*&NDA%-*tXib8CLoPX!aJP>w#7A%f25BZpdkM12vpSkHGF~5G(!}+9cuj%jr_W~<&A=$=Z zBWwUEtyn?75Xo0fbwnC$*vP zmKXjQ^%bV3El=q-CrbJG9zVj}4hlFo9S@7VfxTzI3P9zpiOWbWn2AgudT%iggg`aWofzz*eQ(yB+jGU&Ihs;zq zRc<0iOQxyk#E?V8qyF1^}Zr5X?hunT(?@7HHD!5{i_{`-goY&VqC>&R57Oy%BUa|K) zxCaG5`ER-gn&5DdzOdyVO8IKm$>(J%*`}vpXNe<+W4g+_XbNe^GxbF&KnQlaHdl&u zr1~X}3;zxDA>~T4&-Mbz7HNs1+rU5tG%rpTngHpeISYDK7f&$100tUMJu`9H(lmQ- zZyuQ;xts5n<;Ux#sfgcsP=v6ab2!@yq|St27viLe&qj(8uEL5h-p$qaCO_T!5!&)G z%hP2C;4iTeI#fbX6(Tl3j%O{$Do`Leq_A)XNWQb6`sreLiEHt|$V;UhIfL@6VbkWO zT{_-y%!jO=H6r3nvs|)b%4~_5nSU7!+N9upqcNx*sT!n>iyKhsC>s1@n5Q;ZD{T+t zY!kK}#kb-z6;FmFBoK=_r?mlYMv@GhxOk?)`f=g@+KA+J+=IbDn^S2`>46SOBbgxC zp|Z2O4fMLe6eJ-tn$+Lcm3^h<3!X@2Ax%*xji5b;xknkZ+=U7?!&Lmrtc7HNf%9uUP-Y`cAWkxI~p#5!vfj-{jXGeZBz@c#d7%>}M^o zIhxFPuTt_ zUw6nj@i2i;dbz-wJQjYL&7Uuix2&FAB`Ir2y$Mid&q@GEs^gd#7xZQ$j8S>tT&v_O zwtnG+5hpvdZ`)Q&8;w5q-#(CLsEyE%zjTzp&-&ZUCC}yI9?BWURnJ%2OBGd}{OvAU zX?m~3%FNlY*W~TwM3wW{UBtY6%t|r_n$n7*n7yN~qEUFMRdnLSx$m6ttjAtuWBfiw zd-Ym>%Z+gce(?vu-2CSP*~t5tpely%O^^7G$BFfcWqy` zhlz+aaQ=%}T&Bxwi zzsmZUmiyUW|6Wa$rkmNI$&}T{3z{d5D-L;>99HZ7v*1Yb^uE^TF`Pr{_W7HOiIpqd z_&Z^KGG&RNc7N4u5w9kxk+$6cvvRF>!hZEfj}x2Tnr6>MPG4^OtL*Xc7U9sMC^)CP zN9)>pY0=6kdHJ$*@7beE;7B#n;@v#-s0LXv-6SYEj;h8(Ccu1c+X0T>@H9g+@YdjP z+nbF~fEV2proL!2T3M$#rgY|8h6O-F=3w#rL(l)N^S6f8cVi*OtU_A4{TVBNJ>JRcka(T|Z=~tEdhsgd#$3ch%@Es^@g;I8PS1&-SX*^QvD!5aBL$1{UuFKu zm=y`sPpan}H2d78n<=h2B75R&O_P5=@X-WP1F=$}u|Q>}SCV~0Fx${)k7QPGML6;y zWmYH;u`2kJ5vVhw#-RcB^35Kf=gjR*n|OsIa}S@@8)X!&)Oi9#Vkl79N$tAxreK#Y zlJ|8r3~a;Eom&8f9I#u9E7CBD>@Az-C74H{cWB{mcVo`-v4&&q>@)$gbtDnsYHd0)Awki{yX8`il@y4%w<9# zn{}5I4U?insrRQh9^B@&t*ETZje48=)k)2}AB}SEg*r+BK>z+#8m5A0)AD)JI6D9R z+_=3-B&(#nD8cm~|KybzwLQ&c$P1{Ssok4z=fUJ@E(6cDl*_lcUlDv|q>b$wmmU{5 z(zxz+(trS~^N4+3?FCBRa60KXRw8Z(;CxVz+%bR|!aT_Qd)1h0k zgm6Y$=<&wESV4OEBS;vd!@t5~bz;3_RHVOOE@q9+kMQ-**-)zn*8FFm^fAVYo66X z6ed=aF=et^HT1IAoB`5qF(`C$Yjdrm{$Gx^@Gf1Y&NBityu}NVm9vL)?a+WDJ0hU| z3wG@A{)V*8)np@}Zfv1iO_r7k{=nSbB}2=-)3fMNu$Mtcn8pr|icw48Z!ha(Nn7#K zf#O4S!zu3P{{o8cBy1!@PFpbJ#i0o?VZ=Yn$Vs9zEZ6|;5MevY3nY;r_lK}JF}`xE z4A0mQHE4*ihty+fW*)nFmqIB`a=gENxzFAb=DH;3J)3#1sGz}!%zi^*e)W(a=W_~3y*A;K$!Gso;*{Hc+@sF#Y_%FsA zp0Q?LkYWk}y!ft%@rdO5gqrCwukH7ovSWATp8I^Upd>eEq^Mv}-7l|BgPZ;?C76;r z0RI3a7E|%fQ?a$}h6e$U59P@t8|pARXy~I-=eh8YkXRSOgST5vJw3BxB$z zfXlomJ3J`Ws@!MWbNU3Sqy_oPYlh`=y!U(_x%%w|%KD`3`2m2f=*0tp)`OdpwPIRR zy_+H~lZKN)jIbv6jTePd1?2?)$r7X~>J#4&N6L#pW!Qm#1?DRc7RcvK^41HNbQ_q& zEor$KdeG_zuIbHj)}Q|N5}Ncl^OvV440~B1w_|^n+X~2RHj8veZV}Hyg@QQAqItdb z_;Z)CVu2e7@5-NEjX#~%%J4kxa;=&$bg|HRrC18BD`*`dnbyXb7NSiX(1qyH+#DU8 zBW4RIrb}zMB7ARO+fYdr7(0a5c_aShH4{(k9o_a;_P!DvBny^GCDUy@0=bn>6u zBu*&#EirZv@p?%*@R1<+oNEYkUFFL7dN4#`Pah1h6K6noqyPUt)=no&&s+p + \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..44f2c4f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -7,6 +7,10 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/lib/Settings/Pages/dev_settings.dart b/lib/Settings/Pages/dev_settings.dart index b243c35..4e40844 100644 --- a/lib/Settings/Pages/dev_settings.dart +++ b/lib/Settings/Pages/dev_settings.dart @@ -34,9 +34,7 @@ class DevSettings extends StatelessWidget { content: Text('Neuer API-Schlüssel gesetzt: $apiKey')); ScaffoldMessenger.of(context).showSnackBar(snackBar); - } - ) - ), + })), const Divider(), Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), @@ -51,7 +49,6 @@ class DevSettings extends StatelessWidget { ) ) ], - ) - ); + )); } } diff --git a/lib/Settings/Pages/info_settings.dart b/lib/Settings/Pages/info_settings.dart index 80715a7..f82c1f8 100644 --- a/lib/Settings/Pages/info_settings.dart +++ b/lib/Settings/Pages/info_settings.dart @@ -1,6 +1,11 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:MeinCantor/const.dart'; +import 'package:flutter_linkify/flutter_linkify.dart'; +import 'package:meincantor/const.dart'; +import 'package:meincantor/main.dart'; +import 'package:url_launcher/url_launcher.dart'; class InfoSettings extends StatelessWidget { const InfoSettings({Key? key}) : super(key: key); @@ -19,9 +24,31 @@ class InfoSettings extends StatelessWidget { leading: Icon(Icons.info_outlined), title: Text("Version"), subtitle: Text(version)), + ListTile( + leading: Icon(Icons.person_outlined), + title: Text("Autor"), + subtitle: Text(author), + onTap: () => launch("https://git.cantorgymnasium.de/denyskon"), + ), + ListTile( + leading: const Icon(Icons.source_outlined), + title: const Text("Quellcode"), + subtitle: Linkify( + onOpen: (link) async { + if (await canLaunch(link.url)) { + await launch(link.url); + } else { + throw 'Could not launch $link'; + } + }, + text: "https://git.cantorgymnasium.de/cantortechnik/meincantor-app", + linkStyle: const TextStyle(color: Palette.accent), + ), + ), ListTile( leading: const Icon(Icons.settings_backup_restore_outlined), title: const Text("Änderungsverlauf"), + subtitle: const Text("Was ist neu?"), onTap: () { showModalBottomSheet( isScrollControlled: true, @@ -30,14 +57,13 @@ class InfoSettings extends StatelessWidget { return SizedBox( height: 400, child: Column( - mainAxisSize: MainAxisSize.min, children: [ AppBar( title: const Text("Änderungsverlauf"), ), const Padding( padding: EdgeInsets.all(10), - child: Text(""), + child: Text("1.0 --\nErste Release-Version!"), ), ], ), diff --git a/lib/Settings/Pages/plan_settings.dart b/lib/Settings/Pages/plan_settings.dart index 54feb7a..0e34b7b 100644 --- a/lib/Settings/Pages/plan_settings.dart +++ b/lib/Settings/Pages/plan_settings.dart @@ -1,51 +1,20 @@ import 'dart:convert'; -import 'package:MeinCantor/main.dart'; +import 'package:meincantor/main.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cyclop/cyclop.dart'; -import 'package:MeinCantor/networking.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:MeinCantor/presets/colors.dart'; -import 'package:MeinCantor/presets/subjects.dart'; +import 'package:meincantor/presets/colors.dart'; +import 'package:meincantor/presets/subjects.dart'; -import 'package:MeinCantor/presets/teachers.dart'; +import 'package:meincantor/presets/teachers.dart'; -class PlanSettings extends StatefulWidget { +class PlanSettings extends StatelessWidget { const PlanSettings({Key? key}) : super(key: key); - @override - State createState() => _PlanSettingsState(); -} - -Future buildPlanColors(lesson) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - if (!prefs.containsKey("color$lesson")) { - prefs.setInt("color$lesson", colors[lesson].value ?? Colors.grey.value); - } - await fetchLessonList(); - Color colorDeu = Color(prefs.getInt("color$lesson")!); - return colorDeu; -} - -Future> buildLessonsList() async { - await fetchLessonList(); - SharedPreferences prefs = await SharedPreferences.getInstance(); - String lessonsJson = prefs.getString("lessons")!; - List lessons = jsonDecode(lessonsJson); - return lessons; -} - -class _PlanSettingsState extends State { - Set swatches = { - ...Colors.primaries, - ...Colors.accents, - Palette.accent, - Palette.primary - }; - @override Widget build(BuildContext context) { return Scaffold( @@ -56,10 +25,202 @@ class _PlanSettingsState extends State { body: ListView( padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), children: [ - const ListTile( - title: Text("Kurse/Fächer"), - leading: Icon(Icons.list_alt_outlined), + ListTile( + leading: const Icon(Icons.list_alt_outlined, color: Colors.red), + trailing: const Icon(Icons.arrow_forward_ios, size: 16), + title: const Text("Kurse"), + subtitle: const Text("Konfiguration der Kurse (Whitelist)"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const WhitelistSettings()), + ); + }, ), + ListTile( + leading: + const Icon(Icons.color_lens_outlined, color: Colors.teal), + trailing: const Icon(Icons.arrow_forward_ios, size: 16), + title: const Text("Farben"), + subtitle: + const Text("Konfiguration der Farben für die Plankacheln"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const PlanColorSettings()), + ); + }, + ), + ], + )); + } +} + +class WhitelistSettings extends StatefulWidget { + const WhitelistSettings({Key? key}) : super(key: key); + + @override + State createState() => _WhitelistSettingsState(); +} + +class _WhitelistSettingsState extends State { + Set swatches = { + ...Colors.primaries, + ...Colors.accents, + Palette.accent, + Palette.primary + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Farben"), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), + children: [ + FutureBuilder( + future: buildLessonsList(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List children = []; + for (var element in (snapshot.data as List)) { + String subject = element['subject']; + String teacher = element['teacher']; + int id = element['id']; + children.add( + FutureBuilder( + future: buildPlanColors(subject), + builder: (context, snapshot) { + if (snapshot.hasData) { + Color color = snapshot.data as Color; + return FutureBuilder( + future: buildBlacklist(), + builder: (context, snapshot) { + if (snapshot.hasData) { + final _blacklist = + snapshot.data! as List; + final _blacklisted = + _blacklist.contains(id); + return ListTile( + leading: Checkbox( + value: + _blacklisted ? false : true, + onChanged: (state) async { + SharedPreferences prefs = + await SharedPreferences + .getInstance(); + setState(() { + _blacklisted + ? _blacklist.remove(id) + : _blacklist.add(id); + }); + prefs.setString("blacklist", + jsonEncode(_blacklist)); + }, + activeColor: color), + title: Text(subjects[subject] ?? ""), + subtitle: + Text(teachers[teacher] ?? ""), + onTap: () async { + SharedPreferences prefs = + await SharedPreferences + .getInstance(); + setState(() { + _blacklisted + ? _blacklist.remove(id) + : _blacklist.add(id); + }); + prefs.setString("blacklist", + jsonEncode(_blacklist)); + }, + ); + } else { + return const LinearProgressIndicator(); + } + }); + } else { + return (const LinearProgressIndicator()); + } + }), + ); + } + return Column( + children: children, + ); + } else { + return (const Center(child: CircularProgressIndicator())); + } + }), + ], + )); + } +} + +class PlanColorSettings extends StatefulWidget { + const PlanColorSettings({Key? key}) : super(key: key); + + @override + State createState() => _PlanColorSettingsState(); +} + +Future buildPlanColors(String lesson) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String key = "color$lesson"; + if (prefs.containsKey(key) == false) { + int colorValue; + if (colors.containsKey(lesson)) { + colorValue = colors[lesson].value; + } else { + colorValue = Colors.grey.value; + } + prefs.setInt(key, colorValue); + } + //await fetchLessonList(); + Color color = Color(prefs.getInt(key)!); + return color; +} + +Future> buildLessonsList() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String lessonsJson = prefs.getString("lessons")!; + List lessons = jsonDecode(lessonsJson); + return lessons; +} + +Future buildBlacklist() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (!prefs.containsKey("blacklist") || + jsonDecode(prefs.getString("blacklist")!).isEmpty) { + return []; + } + String blacklistJson = prefs.getString("blacklist")!; + List blacklist = jsonDecode(blacklistJson); + return blacklist; +} + +class _PlanColorSettingsState extends State { + Set swatches = { + ...Colors.primaries, + ...Colors.accents, + Palette.accent, + Palette.primary + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Farben"), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), + children: [ FutureBuilder( future: buildLessonsList(), builder: (context, snapshot) { diff --git a/lib/Settings/Pages/user_settings.dart b/lib/Settings/Pages/user_settings.dart index 63465dc..3407fbf 100644 --- a/lib/Settings/Pages/user_settings.dart +++ b/lib/Settings/Pages/user_settings.dart @@ -1,7 +1,14 @@ +import 'package:cyclop/cyclop.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:MeinCantor/networking.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:meincantor/Settings/Pages/plan_settings.dart'; +import 'package:meincantor/networking.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'dart:io' show Platform; + +import '../../const.dart'; Future getSettingsString(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); @@ -28,17 +35,39 @@ class UserSettings extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), - child: Container( - width: 128.0, - height: 128.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - fit: BoxFit.scaleDown, - image: - AssetImage("assets/images/meincantor_r.png") - ) - ) + child: FutureBuilder( + future: Future.sync(() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? user = prefs.getString("user"); + if (user == null || user.isEmpty) { + user = ""; + } + String? name = prefs.getString("name"); + if (name == null || name.isEmpty) { + name = ""; + } + Map data = {"user": user, "name": name }; + return data; + }), + builder: (context, snapshot) { + if (snapshot.hasData) { + // .svg?text=${(snapshot.data! as Map)['name'][0]} + String url = "$avatarUrl/${(snapshot.data! as Map)['user']}"; + return Container( + width: 120.0, + height: 120.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.scaleDown, + image: NetworkImage(url) + ) + ) + ); + } else { + return const CircularProgressIndicator(); + } + }, ), ), FutureBuilder( @@ -61,8 +90,44 @@ class UserSettings extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(5, 20, 5, 5), child: buildClassesChooser()), + ListTile( + leading: const Icon(MdiIcons.accountSettingsOutline), + trailing: const Icon(Icons.link, size: 16), + title: const Text("Account-Konsole"), + subtitle: const Text("Konto-Einstellungen öffnen"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AccountConsole()), + ); + }, + ), ], ) ); } } + + +class AccountConsole extends StatefulWidget { + @override + AccountConsoleState createState() => AccountConsoleState(); +} + +class AccountConsoleState extends State { + @override + void initState() { + super.initState(); + // Enable virtual display. + if (Platform.isAndroid) WebView.platform = AndroidWebView(); + } + + @override + Widget build(BuildContext context) { + return const WebView( + initialUrl: 'https://mein.cantorgymnasium.de/auth/realms/GCG.MeinCantor/account/', + ); + } +} + diff --git a/lib/Settings/dashboard.dart b/lib/Settings/dashboard.dart index fa176df..09672ac 100644 --- a/lib/Settings/dashboard.dart +++ b/lib/Settings/dashboard.dart @@ -1,5 +1,5 @@ -import 'package:MeinCantor/Settings/Pages/appearance_settings.dart'; -import 'package:MeinCantor/Settings/Pages/service_settings.dart'; +import 'package:meincantor/Settings/Pages/appearance_settings.dart'; +import 'package:meincantor/Settings/Pages/service_settings.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; diff --git a/lib/cache_manager.dart b/lib/cache_manager.dart new file mode 100644 index 0000000..4f384b9 --- /dev/null +++ b/lib/cache_manager.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future getCachedTimetable(String ext) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String classNum; + if (prefs.getString('class_num') != null) { + classNum = prefs.getString('class_num')!.replaceAll("/", "_"); + } else { + classNum = '05_1'; + } + var apiKey = prefs.getString('api_key'); + var headers = {"x-api-key": "$apiKey"}; + var file = await DefaultCacheManager().getSingleFile( + "https://mein.cantorgymnasium.de/api/timetable/$ext/$classNum", + headers: headers); + return (utf8.decode(await file.readAsBytes())); +} diff --git a/lib/dashboard.dart b/lib/dashboard.dart index 7d5dddb..486d737 100644 --- a/lib/dashboard.dart +++ b/lib/dashboard.dart @@ -1,19 +1,24 @@ -import 'package:MeinCantor/raumuebersicht.dart'; -import 'package:MeinCantor/schulbibliothek.dart'; -import 'package:MeinCantor/schulcomputer.dart'; -import 'package:MeinCantor/schuelerzeitung.dart'; -import 'package:MeinCantor/Settings/dashboard.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:meincantor/const.dart'; +import 'package:meincantor/raumuebersicht.dart'; +import 'package:meincantor/schulbibliothek.dart'; +import 'package:meincantor/schulcomputer.dart'; +import 'package:meincantor/schuelerzeitung.dart'; +import 'package:meincantor/Settings/dashboard.dart'; -import 'package:MeinCantor/main.dart'; -import 'package:MeinCantor/networking.dart'; -import 'package:MeinCantor/login.dart'; +import 'package:meincantor/main.dart'; +import 'package:meincantor/networking.dart'; +import 'package:meincantor/login.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'news.dart'; + class Dashboard extends StatefulWidget { const Dashboard({Key? key, this.restorationId}) : super(key: key); @@ -53,8 +58,39 @@ class _DashboardState extends State with RestorationMixin { UserAccountsDrawerHeader( accountName: buildSettingsString('name', const TextStyle()), accountEmail: buildSettingsString('user', const TextStyle()), - currentAccountPicture: - Image.asset("assets/images/meincantor_r.png")), + currentAccountPicture: FutureBuilder( + future: Future.sync(() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? user = prefs.getString("user"); + if (user == null || user.isEmpty) { + user = ""; + } + String? name = prefs.getString("name"); + if (name == null || name.isEmpty) { + name = ""; + } + Map data = {"user": user, "name": name }; + return data; + }), + builder: (context, snapshot) { + if (snapshot.hasData) { + // .svg?text=${(snapshot.data! as Map)['name'][0]} + String url = "$avatarUrl/${(snapshot.data! as Map)['user']}"; + return Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.scaleDown, + image: NetworkImage(url) + ) + ) + ); + } else { + return const CircularProgressIndicator(); + } + }, + ) + ), ListTile( title: const Text("Einstellungen"), onTap: () { @@ -175,7 +211,7 @@ class _DashboardBottomNavView extends StatelessWidget { _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10))) { lessonCount = 9; } else if (_timeOfDayToDouble(TimeOfDay.now()) > - _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10)) && + _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10)) && _timeOfDayToDouble(TimeOfDay.now()) <= _timeOfDayToDouble(const TimeOfDay(hour: 16, minute: 00))) { lessonCount = 10; @@ -271,10 +307,10 @@ class _DashboardBottomNavView extends StatelessWidget { ), )), ), - width: 175, + width: 170, ), SizedBox( - width: 175, + width: 170, child: GestureDetector( onTap: () async { Navigator.push( @@ -310,7 +346,7 @@ class _DashboardBottomNavView extends StatelessWidget { ), ), SizedBox( - width: 175, + width: 170, child: GestureDetector( onTap: () async { Navigator.push( @@ -346,7 +382,7 @@ class _DashboardBottomNavView extends StatelessWidget { ), ), SizedBox( - width: 175, + width: 170, child: GestureDetector( onTap: () async { Navigator.push( @@ -381,6 +417,42 @@ class _DashboardBottomNavView extends StatelessWidget { ), )), ), + ), + SizedBox( + width: 170, + child: GestureDetector( + onTap: () async { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const News()), + ); + }, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: const Padding( + padding: EdgeInsets.all(10), + child: ListTile( + title: Padding( + padding: EdgeInsets.fromLTRB(0, 0, 0, 10), + child: Icon( + MdiIcons.newspaperVariantOutline, + color: Palette.accent, + size: 48, + ), + ), + subtitle: Center( + child: Padding( + padding: EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Text( + 'Aktuelles', + ), + ), + ), + ), + )), + ), ) ], ), @@ -439,9 +511,16 @@ class _DashboardBottomNavView extends StatelessWidget { ), body: TabBarView( children: [ - buildTodayClassTimetable(), - buildTomorrowClassTimetable(), - buildClassTimetable(), + buildTimetable( + fetchClassTimetable( + "/${DateFormat("yyyyMMdd").format(DateTime.now())}"), + "Vertretungsplan für heute"), + buildTimetable( + fetchClassTimetable( + "/${DateFormat("yyyyMMdd").format(DateTime.now().add(const Duration(days: 1)))}"), + "Vertretungsplan für morgen"), + buildTimetable(fetchClassTimetable("/latest"), + "aktueller Vertretungsplan") ], ), ), diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart index 7933e85..849a492 100644 --- a/lib/generated_plugin_registrant.dart +++ b/lib/generated_plugin_registrant.dart @@ -5,14 +5,14 @@ // ignore_for_file: directives_ordering // ignore_for_file: lines_longer_than_80_chars -import 'package:fluttertoast/fluttertoast_web.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; +import 'package:url_launcher_web/url_launcher_web.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { - FluttertoastWebPlugin.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar); + UrlLauncherPlugin.registerWith(registrar); registrar.registerMessageHandler(); } diff --git a/lib/login.dart b/lib/login.dart index 00701d7..434e9f7 100644 --- a/lib/login.dart +++ b/lib/login.dart @@ -14,6 +14,7 @@ Future checkKey() async { } class Login extends StatelessWidget { + final userController = TextEditingController(); final passwordController = TextEditingController(); final otpController = TextEditingController(); @@ -148,11 +149,7 @@ class Login extends StatelessWidget { child: const Text("Anmelden")) ], ), - ) - ) - ) - ) - ); + ))))); }, ); } diff --git a/lib/main.dart b/lib/main.dart index b99e9d9..261669d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,28 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dashboard.dart'; import 'login.dart'; import 'dart:math'; -void main() => runApp(const App()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + // initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('app_icon'); + final IOSInitializationSettings initializationSettingsIOS = + IOSInitializationSettings(); + final MacOSInitializationSettings initializationSettingsMacOS = + MacOSInitializationSettings(); + final InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + macOS: initializationSettingsMacOS); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + runApp(const App()); +} class App extends StatelessWidget { const App({Key? key}) : super(key: key); diff --git a/lib/networking.dart b/lib/networking.dart index 60c9a4a..fff30a9 100644 --- a/lib/networking.dart +++ b/lib/networking.dart @@ -1,19 +1,30 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:meincantor/cache_manager.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/painting.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:MeinCantor/const.dart'; -import 'package:MeinCantor/timetable.dart'; -import 'package:MeinCantor/login.dart'; -import 'package:MeinCantor/main.dart'; +import 'package:meincantor/const.dart'; +import 'package:meincantor/timetable.dart'; +import 'package:meincantor/login.dart'; +import 'package:meincantor/main.dart'; Future getArticles() async { - var uri = Uri.https(szUrl["url"]!, "/articles"); - final response = await http.get(uri); - return (response); + var uri = Uri.https(szUrl["url"]!, "/articles"); + final response = await http.get(uri); + return (response); +} + +Future getNews() async { + var uri = Uri.https(szUrl["url"]!, "/aktuelles"); + final response = await http.get(uri); + return (response); } Future getToken( @@ -40,7 +51,28 @@ Future getUserInfo( } Future fetchClassTimetable(String ext) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails('de.cantorgymnasium.meincantor', 'GCG.MeinCantor', + channelDescription: '', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); + const NotificationDetails platformChannelSpecifics = + NotificationDetails(android: androidPlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show( + 0, 'Neuer Vertretungsplan geladen!', 'Du hast folgende Vertretungen:\nSt. 8 Deutsch Frau Rinke, Raum 203\nSt. 4 Biologie Frau Borchert, Raum 107', platformChannelSpecifics, + payload: 'item x'); + try { + return (http.Response(await getCachedTimetable(ext), 200)); + } on HttpExceptionWithStatus catch (e) { + return http.Response(e.message, e.statusCode); + } on HttpException catch (e) { + return http.Response(e.message, 500); + } on SocketException catch (e) { + return http.Response(e.message, 404); + } + /*SharedPreferences prefs = await SharedPreferences.getInstance(); String classNum; if (prefs.getString('class_num') != null) { classNum = prefs.getString('class_num')!.replaceAll("/", "_"); @@ -53,45 +85,9 @@ Future fetchClassTimetable(String ext) async { final response = http.get(uri, headers: headers).onError((error, stackTrace) { return (http.Response("", 404)); }); - return response; + return response;*/ } -/*Future fetchTodayClassTimetable() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - String classNum; - if (prefs.getString('class_num') != null) { - classNum = prefs.getString('class_num')!.replaceAll("/", "_"); - } else { - classNum = '05_1'; - } - var apiKey = prefs.getString('api_key'); - var uri = - Uri.https("mein.cantorgymnasium.de", "/api/timetable/$classNum/today"); - var headers = {"x-api-key": "$apiKey"}; - final response = http.get(uri, headers: headers).onError((error, stackTrace) { - return (http.Response("", 404)); - }); - return response; -} - -Future fetchTomorrowClassTimetable() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - String classNum; - if (prefs.getString('class_num') != null) { - classNum = prefs.getString('class_num')!.replaceAll("/", "_"); - } else { - classNum = '05_1'; - } - var apiKey = prefs.getString('api_key'); - var uri = - Uri.https("mein.cantorgymnasium.de", "/api/timetable/$classNum/tomorrow"); - var headers = {"x-api-key": "$apiKey"}; - final response = http.get(uri, headers: headers).onError((error, stackTrace) { - return (http.Response("", 404)); - }); - return response; -}*/ - fetchLessonList() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String classNum; @@ -114,18 +110,6 @@ fetchLessonList() async { } } -Widget buildClassTimetable() { - return buildTimetable(fetchClassTimetable(""), "aktueller Vertretungsplan"); -} - -Widget buildTodayClassTimetable() { - return buildTimetable(fetchClassTimetable("/today"), "Vertretungsplan für heute"); -} - -Widget buildTomorrowClassTimetable() { - return buildTimetable(fetchClassTimetable("/tomorrow"), "Vertretungsplan für morgen"); -} - Widget buildTimetable(Future future, String info) { return FutureBuilder( future: future, @@ -134,7 +118,7 @@ Widget buildTimetable(Future future, String info) { int statusCode = snapshot.data!.statusCode; if (statusCode == 200) { Widget timetableView = ClassTimetableBuilder.buildView( - jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), context) + jsonDecode(snapshot.data!.body), context) .view .child; return timetableView; @@ -142,8 +126,7 @@ Widget buildTimetable(Future future, String info) { Navigator.push( context, MaterialPageRoute(builder: (context) => Login())); } else if (statusCode == 500) { - var chars = Runes( - 'Es konnte kein $info gefunden werden. \u{1F937}'); + var chars = Runes('Es konnte kein $info gefunden werden. \u{1F937}'); List cardChildren = []; cardChildren.add(ListTile( title: Text(String.fromCharCodes(chars), @@ -182,8 +165,7 @@ Widget buildTimetable(Future future, String info) { padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), child: ListView( children: [card], - ) - ); + )); } return Center(child: Text('Error $statusCode')); } else if (snapshot.hasError) { @@ -197,13 +179,14 @@ Widget buildTimetable(Future future, String info) { Widget buildTodayClassTimetableLesson(int count) { return FutureBuilder( - future: fetchClassTimetable("/today"), + future: fetchClassTimetable( + "/${DateFormat("yyyyMMdd").format(DateTime.now())}"), builder: (context, snapshot) { if (snapshot.hasData) { int statusCode = snapshot.data!.statusCode; if (statusCode == 200) { List lessons = LessonsListBuilder.buildList( - jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), + jsonDecode(snapshot.data!.body), count: count) .lessons; if (lessons.isNotEmpty) { @@ -223,7 +206,7 @@ Widget buildTodayClassTimetableLesson(int count) { child: Column( children: cardChildren, )); - return card; + return Column(children: [card]); } } else if (statusCode == 400) { Future.delayed(Duration.zero, () { @@ -264,11 +247,8 @@ Widget buildTodayClassTimetableLesson(int count) { child: Column( children: cardChildren, )); - return Padding( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), - child: ListView( - children: [card], - ) + return Column( + children: [card], ); } return Center(child: Text('Error $statusCode')); diff --git a/lib/news.dart b/lib/news.dart new file mode 100644 index 0000000..0e3d141 --- /dev/null +++ b/lib/news.dart @@ -0,0 +1,224 @@ +import 'dart:convert'; +import 'package:meincantor/networking.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:http/http.dart' as http; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future getNewsRead() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? newsReadString = prefs.getString("newsRead"); + List newsRead; + if (newsReadString == null || + (jsonDecode(newsReadString) as List).isEmpty) { + newsRead = []; + } else { + newsRead = jsonDecode(newsReadString) as List; + } + return newsRead; +} + +class News extends StatefulWidget { + const News({Key? key}) : super(key: key); + + @override + State createState() => _NewsState(); +} + +class _NewsState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Aktuelles"), + centerTitle: true, + ), + body: FutureBuilder( + future: getNews(), + builder: (context, snapshot) { + if (snapshot.hasData) { + int statusCode = snapshot.data!.statusCode; + if (statusCode == 200) { + String data = utf8.decode(snapshot.data!.bodyBytes); + List articles = jsonDecode(data); + List articleTiles = []; + for (var element in articles) { + Color color = Colors.white70; + Widget card = FutureBuilder( + future: getNewsRead(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List readList = snapshot.data! as List; + if (!readList.contains(element["id"])) { + return GestureDetector( + onTap: () async { + SharedPreferences prefs = + await SharedPreferences.getInstance(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + readList.add(element["id"]); + prefs.setString("newsRead", jsonEncode(readList)); + setState(() { + color = Colors.transparent; + }); + }, + child: Card( + color: color, + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } else { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + }, + child: Card( + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold) + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } + } else { + return const LinearProgressIndicator(); + } + }, + ); + articleTiles.add(card); + } + return ListView( + children: articleTiles.reversed.toList(), + ); + } else { + return (const Center( + child: Text("Uups... Irgendwas ist schief gelaufen"))); + } + } else { + return (const Center(child: CircularProgressIndicator())); + } + }, + ), + ); + } +} + +class Article { + Widget widget; + //const Article({Key? key}) : super(key: key); + Article({required this.widget}); + factory Article.fromData( + String title, String content, String author, String publishDate) { + return Article( + widget: Scaffold( + appBar: AppBar( + title: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: Text(title)), + ), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(15, 15, 15, 15), + children: [ + ListTile( + leading: const Icon(MdiIcons.accountOutline), + title: Text(author), + ), + ListTile( + leading: const Icon(MdiIcons.calendarOutline), + title: Text( + "${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), + ), + MarkdownBody(data: content) + ], + ))); + } +} diff --git a/lib/notifications.dart b/lib/notifications.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/notifications.dart @@ -0,0 +1 @@ + diff --git a/lib/presets/colors.dart b/lib/presets/colors.dart index eaba1c2..8704493 100644 --- a/lib/presets/colors.dart +++ b/lib/presets/colors.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -dynamic colors = { +Map colors = { 'Bio': Colors.green, 'Mat': Colors.indigo, 'matL1': Colors.indigo, diff --git a/lib/schuelerzeitung.dart b/lib/schuelerzeitung.dart index 102fe25..e211464 100644 --- a/lib/schuelerzeitung.dart +++ b/lib/schuelerzeitung.dart @@ -1,72 +1,190 @@ import 'dart:convert'; - -import 'package:MeinCantor/networking.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:meincantor/networking.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; - import 'package:http/http.dart' as http; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -class SZ extends StatelessWidget { +Future getSZread() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? szReadString = prefs.getString("SZread"); + List szRead; + if (szReadString == null || + (jsonDecode(szReadString) as List).isEmpty) { + szRead = []; + } else { + szRead = jsonDecode(szReadString) as List; + } + return szRead; +} + +class SZ extends StatefulWidget { const SZ({Key? key}) : super(key: key); + @override + State createState() => _SZState(); +} + +class _SZState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text("Schülerzeitung"), - centerTitle: true, - ), - body: FutureBuilder( - future: getArticles(), - builder: (context, snapshot) { - if (snapshot.hasData) { - int statusCode = snapshot.data!.statusCode; - if (statusCode == 200) { - String data = utf8.decode(snapshot.data!.bodyBytes); - List articles = jsonDecode(data); - List articleTiles = []; - for (var element in articles) { - Card card = Card( - child: Column(children: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), - child: ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => Article.fromData(element["title"], element["content"], element["author"], element["published_at"]).widget), - ); - }, - title: Text(element["title"], - style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text( - element["summary"])), - ) - ]), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - ); - articleTiles.add(card); - } - return ListView( - children: articleTiles.reversed.toList(), + appBar: AppBar( + title: const Text("Schülerzeitung"), + centerTitle: true, + ), + body: FutureBuilder( + future: getArticles(), + builder: (context, snapshot) { + if (snapshot.hasData) { + int statusCode = snapshot.data!.statusCode; + if (statusCode == 200) { + String data = utf8.decode(snapshot.data!.bodyBytes); + List articles = jsonDecode(data); + List articleTiles = []; + for (var element in articles) { + Color color = Colors.white70; + Widget card = FutureBuilder( + future: getSZread(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List readList = snapshot.data! as List; + if (!readList.contains(element["id"])) { + return GestureDetector( + onTap: () async { + SharedPreferences prefs = + await SharedPreferences.getInstance(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + readList.add(element["id"]); + prefs.setString("SZread", jsonEncode(readList)); + setState(() { + color = Colors.transparent; + }); + }, + child: Card( + color: color, + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } else { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + }, + child: Card( + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } + } else { + return const LinearProgressIndicator(); + } + }, ); - } else { - return(const Center( - child: Text("Uups... Irgendwas ist schief gelaufen") - )); + articleTiles.add(card); } + return ListView( + children: articleTiles.reversed.toList(), + ); } else { - return(const Center( - child: CircularProgressIndicator() - )); + return (const Center( + child: Text("Uups... Irgendwas ist schief gelaufen"))); } - }, - ), - ); + } else { + return (const Center(child: CircularProgressIndicator())); + } + }, + ), + ); } } @@ -74,33 +192,32 @@ class Article { Widget widget; //const Article({Key? key}) : super(key: key); Article({required this.widget}); - factory Article.fromData(String title, String content, String author, String publishDate, ) { - return Article(widget: Scaffold( - appBar: AppBar( - title: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text(title) + factory Article.fromData( + String title, String content, String author, String publishDate) { + return Article( + widget: Scaffold( + appBar: AppBar( + title: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: Text(title)), + ), + centerTitle: true, ), - ), - centerTitle: true, - ), - body: ListView( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), - children: [ - ListTile( - leading: const Icon(MdiIcons.accountOutline), - title: Text(author), - ), - ListTile( - leading: const Icon(MdiIcons.calendarOutline), - title: Text("${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), - ), - MarkdownBody(data: content) - ], - ) - ) - ); + body: ListView( + padding: const EdgeInsets.fromLTRB(15, 15, 15, 15), + children: [ + ListTile( + leading: const Icon(MdiIcons.accountOutline), + title: Text(author), + ), + ListTile( + leading: const Icon(MdiIcons.calendarOutline), + title: Text( + "${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), + ), + MarkdownBody(data: content) + ], + ))); } } diff --git a/lib/timetable.dart b/lib/timetable.dart index bbfcc5e..681c00b 100644 --- a/lib/timetable.dart +++ b/lib/timetable.dart @@ -1,12 +1,14 @@ -import 'package:MeinCantor/presets/teachers.dart'; -import 'package:MeinCantor/presets/subjects.dart'; -import 'package:MeinCantor/presets/colors.dart'; +import 'package:meincantor/presets/teachers.dart'; +import 'package:meincantor/presets/subjects.dart'; +import 'package:meincantor/presets/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'Settings/Pages/plan_settings.dart'; + class ClassTimetableBuilder { final RefreshIndicator view; ClassTimetableBuilder({required this.view}); @@ -54,15 +56,14 @@ class ClassTimetableBuilder { } return ClassTimetableBuilder( view: RefreshIndicator( - onRefresh: () { - return Future.delayed(const Duration(seconds: 1)); - }, - child: ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: list, - ), - ) - ); + onRefresh: () { + return Future.delayed(const Duration(seconds: 1)); + }, + child: ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: list, + ), + )); } } @@ -104,22 +105,35 @@ class LessonsListBuilder { style: TextStyle(color: element.fontColor)))); } Widget card = FutureBuilder( - future: element.color, - builder: (context, snapshot) { - if (snapshot.hasData) { - return Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ), - color: snapshot.data as Color, - child: Column( - children: cardChildren, - )); - } else { - return (const Center(child: CircularProgressIndicator())); - } - }, - ); + future: buildBlacklist(), + builder: (context, snapshot) { + if (snapshot.hasData) { + if (!((snapshot.data as List).contains(element.id))) { + return FutureBuilder( + future: element.color, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + ), + color: snapshot.data as Color, + child: Column( + children: cardChildren, + )); + } else { + return (const Center( + child: CircularProgressIndicator())); + } + }, + ); + } else { + return const SizedBox.shrink(); + } + } else { + return const LinearProgressIndicator(); + } + }); children.add(card); } } @@ -187,6 +201,7 @@ class ClassTimetable { lessons.add(TimetableLesson( value['St'], + value["Nr"], subjects[subject] ?? subject.toString(), teachers[teacher] ?? teacher.toString(), room.toString(), @@ -202,6 +217,7 @@ class ClassTimetable { class TimetableLesson { final int count; + final int id; final String name; final String teacher; final String room; @@ -209,6 +225,6 @@ class TimetableLesson { final Future color; final Color fontColor; final String info; - const TimetableLesson(this.count, this.name, this.teacher, this.room, + const TimetableLesson(this.count, this.id, this.name, this.teacher, this.room, this.comment, this.color, this.fontColor, this.info); } diff --git a/pubspec.yaml b/pubspec.yaml index 6ffed1f..4bd95bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: MeinCantor +name: meincantor description: Die Schulplatform für Cantorianer. # The following line prevents the package from being accidentally published to @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.7.5-beta1.nightly2021-11-16 +version: 0.8.0-dev environment: sdk: ">=2.12.0 <3.0.0" @@ -37,11 +37,17 @@ dependencies: google_fonts: ^2.1.0 time: ^2.0.0 flutter_launcher_icons: ^0.9.1 - fluttertoast: ^8.0.8 - flutter_colorpicker: ^0.6.0 material_design_icons_flutter: ^5.0.5955-rc.1 cyclop: ^0.5.2 flutter_markdown: ^0.6.8 + flutter_cache_manager: ^3.2.0 + intl: ^0.17.0 + url_launcher: ^6.0.17 + flutter_linkify: ^5.0.2 + flutter_svg: ^1.0.0 + webview_flutter: ^3.0.0 + flutter_local_notifications: ^10.0.0-dev.1 + background_fetch: ^1.0.3 flutter_icons: # image_path: "assets/images/icon-128x128.png" diff --git a/web/manifest.json b/web/manifest.json index 0426ae5..8feaa1a 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -3,8 +3,8 @@ "short_name": "MeinCantor", "start_url": ".", "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", + "background_color": "#1a1a37", + "theme_color": "#ffbc3b", "description": "Die Schulplatform für Cantorianer.", "orientation": "portrait-primary", "prefer_related_applications": false,