From 3300d82c8d3d655a3dc1ac38995641641f0514f4 Mon Sep 17 00:00:00 2001 From: MohammadFerryAlfansyah Date: Wed, 29 Jul 2026 23:50:22 +0700 Subject: [PATCH] first commit --- .vscode/settings.json | 3 +- android/app/src/main/AndroidManifest.xml | 10 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1650 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 2414 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 3366 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1326 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 1576 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2116 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 2274 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 3486 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 4948 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 3256 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 4952 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 7714 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 4258 bytes .../ic_launcher_foreground.webp | Bin 0 -> 7666 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 9580 bytes .../reports/problems/problems-report.html | 2 +- lib/app/models/chart_data_point.dart | 13 +- lib/app/models/realtime_monitoring_model.dart | 10 +- .../controllers/device_detail_controller.dart | 21 +- .../views/device_detail_view.dart | 121 +++++++- .../controllers/home_patient_controller.dart | 112 +++++-- .../home-patient/views/home_patient_view.dart | 166 +++++++---- .../home/controllers/home_controller.dart | 194 ++++++++++-- lib/app/modules/home/views/home_view.dart | 279 ++++++++++++++---- .../login/controllers/login_controller.dart | 22 +- .../navbar/bindings/navbar_binding.dart | 4 +- lib/app/modules/navbar/views/navbar_view.dart | 8 +- .../controllers/notification_controller.dart | 82 ++--- .../notification/views/notification_view.dart | 18 -- .../controllers/profile_controller.dart | 2 +- .../controllers/schedule_controller.dart | 123 ++++---- .../controllers/splash_screen_controller.dart | 44 ++- .../views/splash_screen_view.dart | 2 +- lib/app/services/notification_service.dart | 261 ++++++++++++++++ .../services/realtime_database_service.dart | 70 +++-- lib/app/widgets/history_grafik_card.dart | 31 +- lib/app/widgets/modal_schedule_add_edit.dart | 116 ++++++-- lib/app/widgets/monitoring_card.dart | 14 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 8 + pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 52 files changed, 1334 insertions(+), 414 deletions(-) delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/.vscode/settings.json b/.vscode/settings.json index e0f15db..be4fbc2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "java.configuration.updateBuildConfiguration": "automatic" + "java.configuration.updateBuildConfiguration": "automatic", + "cmake.sourceDirectory": "E:/JOKI/smartinfuse/linux" } \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ddad42f..c88f80a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,14 @@ + + + + + + + + P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..5dc9323fc0ff210faca1933352af09d5a3d3e16a GIT binary patch literal 1650 zcmV-&295brNk&F$1^@t8MM6+kP&iCp1^@srN5Byf2?cE%IZE$$pFiOZA)@~i5bTqT zG1eEcPRRt|MR?~;VIK=cjU+|&-d#=w_*aJr@Svz|8^}A~rVYO8+MS4nP2Yz$4S4b`@|4zFj)74ILEtP7Ou*2o2pLVF3VvhG%!sd0a}_5*z@~ zv29zmt&Fv=;O>^WTj*8LjIw;mY2;7<$lTo}`b1>z?tb6dd$#uPxqGfFNJz=HZDV^j z&wXCRw$p&5WPj;O#@Cq?)fekW)hycP`|-J3ZnkaDf1KAVn}E&)d4c~JCeS;SIWAxU zYKH|>{sz!HR5`Ud{=P?UZEMRG?dQYYX~GE|ViuA00jm* zKxb@5lrpH=*iZBX=Lvn?($~SgH9IOx1dOo>6{`d^1OUPkDgg>r1j!fz5IS}xg~ws@ z>ju;ZsroIT28*@Xg+;QZRnc@Q8KK*j}_3w?# zSz2f;4G!)fPzAM2^mut`zX4o)_2mjIVoTJx;%Nnn3l5LVj&A~Hto~T!^%wc|Z-82i z0w6LOwdU_W|8N!b*Tn%v8~@F_93X&cY$?e0}9Xu zqWRyAVMhu~ieu&gN8p5g@d^ML5~2&jESuW@g&?>fK{QIkuaDUbuLOXA!4|GlRnf~? zmi)ry^2;D)b8#=p+yl3V?=n6##5%nmjLr?%93W{h%<;WQU&#@=C z^e)4Kx}LTf`cMEA(Graq5(8x*hJd=3KY!)BkNNAjNnpA14%gqK%*v=5?W&;g%(ip+ zG6Vn!7ywyyq>(>==g@um>8o9%<*c(zw?>iC0PA0Vrph$O9LW{Sd3Jegv`iXcOV_c- zbL2t5j$EafG9wR8^B zztbE=1?D=z5f{n*HOY|6i`tObv$i@l-kJoqZyvoBczm&Pzu%)AvH)O*0P{r z-3h(*C8W1VN~OGI6on@$w(kw(ES;%P5k=yhlU1)^Ew$V$QSp=^!iH)H?f@4xXn0~E zp@fQbE5fP)3=k~=PnpE4loqA0Fh+-n1TkdvCjvMniX#ZC1V9TF&tyhM$pFO)N3;DR zA@tQ~R1(cH=fFsm@pu3E*YW+@+h+`#%gz3*Mh9#HP|82oJqzaNJ0I(>Edtm&zdSsiOt+tp#k zvKRpvpaEbUv3)sziIYz+lNk+4+Q_i6RD@+RFq^tS%zy5r^_WH~H-`+omukU%<Q;Yw??dQcUVVJs*zeDSsXK3I9`eAOP6>2mF7!0KqySZ2$lO literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..5f0a2a10912a4e7f77605f40d44e8ee5757d4a4d GIT binary patch literal 2414 zcmV-!36b_vNk&Fy2><|BMM6+kP&iCl2><{up+G1A3B_&OMv&9Koc$N>eGCxM{|R7c z<(HX2EZLw}a)5y{Fw@%Of%dd+yd|6Y6R{+L*SdAjlMA%WyNqwdFN z0K>Lz9LIS5?yFrzlBC$tlyDO2^D)D5|0e*)gkvI{6XCc=iJC_k#1558gksnw@BqLF zh!qj$Brqi+OnU&Z!$_DGj0AQ7uRW9z6!W8pc^R=?EShnTaS{6-EsADjq!b z8{fv$z4x5uplu^Ta^{Xd!iVy@h6G8HB&o?+Af}a+YKN}sIglQ3=gJ>Cw+NWEk#*&g zoSr4*LfFjw$dYoNoeC=#i4jF|q>v*kf)zs1lpS&-Vx%=ONQz|RAC$67T~dx$%8vi zd)hmoT$Vj9nhxA;y`#G~f`&is2oMH&a`XV|1=Fz=qg{1}NtR`5E=PyOzeV=cfrO?# zV?>`2;BT{UyF9Eub< zNqHBl9|Q=h)X1p6eS;hE8mdm_XWe$q&Eez$sD%Eh#H|ASvpUlE4T!IA z+B-GaxG-@O)LL{cnN7blqMl?GXcgr9y;YAE#%P@UdXC>8IMaT>p%E@#=E@BYkGq&{ zRjU~y!3c{mm5yy$plYy6to|V|JlsB8wCFrCTMTAP!uI|_kiB1sYLD9Q)nB3_i}ur} znX4CdWN54pW0qxqY^s|!ixG0z4g~DHE}z+TOPoKk0G*B$az@(-bnPaFGPa-&^u4uG z5yvBVdRIF%wv)4E|xd$SY{4&CMjSldGMA{-A{=5JiM$*KbL~(Ti9I zS;`?GOC~b7l_T$ZP$5U#Wd%x%E#TNxfO$Z?5dc9Em7`j>MW=mNE=|xEq@L&SWCBJ) ztQ-X+j1$`dPbJ`gl%s$4rsE_M>v`lz6}A%z@&xP?l%s&E03|MO2SY`DTT)qasK`%D z0xbRXAi9DSF;9;TW;Scc%26A|G#&D&zA5aKqqXusCd?y{o()dVcgNoC8&om+{3j)X zh0txgcg0$zeDg-xJCDYQ?Eoy9034a%=Iy&+v8HcDXj1FN6MO$T0D}er7!7dQDp#!U z)I0}A7y(I-9l&5wGguVc(mI3H`fEi4K}lR%SY#GqhmxJ`Xo!hziliqBMy&Rz0J)f& zWWH_p={K)m_s)a01wKh87 z^9H(c;uC6PUPhGKMyqR9>a>S=Z`Gq z5LoAkD!I^t(#EwIUlUUxLrw%tuxNZu(9GBh>l#)Jiplz>EH%TY85|4agE0KOW^sI9pIO$`(A)u2+T)@HrSpH^1~=umJtbch-iBrr-Q@L9!&C7;)4cYQiNk&E<4FCXFMM6+kP&iBy4FCWyN5Byf6^DVeZCd}bxBV6(Vgfj;2LU^8 z4V8N6l&)<%BuU@cwr$(CZF6m_ZFiUJWTYYykx|vmYIm>3wrv}gh0~E4k%&L=j%~Ny zF}AU7_jYXC%Is1082hdo+ct9goY`4*PK>>)7~3Q5wk<=F?)-B*Uc`%tVwUZywffU| z+O};QQ?BlmVB5CZHb*i99G1civaK)u|K2A-c@h~YNJL!UL z+g2AoW82Bu`%DLJBPmict*IS4hBv^iZCj1ge;W=(Ga@rHcfbue6WEHp-v7F;;de4? zDKzxJu7a(|HE_gGgG2RT_~BIG5*XEh+t{{MrT-AW_VCo*T}mLO0OIH3$hO_Il~r3R z40}2am`ReM)PF~A+ei}KGb8kNpD%#5hQ7iMZ;a2A0@(J2a? zz&m*`s<(|>n%CRllpp#S|8ITLt9Pg<2LI2zGqy%_0)XG=Hi(kahWcg~`hB(gDB&LH zNSbv*KI(-0B8B|>c^92<9}DY*JkSZ*rz5w3`YLVC0syCIOPur*m+GSpql6nr33rwf z?wyVt`AKrHN=PM)Bz;Gco*|SUr_xgxfRODC&Nf@Y1NaGBL49*ODd9fn_hU;MUrV}Z ztNQp}^odpiVy);Cd)3EV4T$XQfdSE-%@Et#jOndr$~T&|*hr@QR?;Sn_}ktRN+6^V zMv{IeNp}*$R{-#$ZAJk|(t1kBHA*=0>&TKWY-E<@OIh-9t;=^x49^8YFJO2nIO&zZ z=aRr)iJ@0;S&$1sDA;MIz;4NW$0uYiG^%AUgj69&h#*KIC|oNHV1q%P2ST_6O1NWm zBBcn$*3K|r6ZBFL3@~f33k#ea<7X@pT2iY~L9|jiS}Q#S`D4X^=dHxci7o&-akj?^l#L6PRL_bo>`TQ%AWKATk z11$_1vEH>eJ7iiZ=IF~bDqOe-9a}oxL7kt|7E8%%A;0MHA>CT zYEDm;D&|A^YR8P={A zH796<%RZS|4qA)&0Mc_Zv`kbhkBXzHRY0M`-^2;cw_uV7r5$}q(xqc8!tP(R^M)J*z1ovO|(RvN|E1VHu&*(H64Nv9U2#uAE8*RLUD%1w4X!(j~Hv;p}TkQV0^u zy5eZd=~!F-nR55gs%5k?<4KU(cVZjjK`Y%=LMmG2g(aD4BKrnr^hM7Y!8yr2?vC7a z@hoGwlG0+1O_?JMOq9R^bh=|}dBp%%(W+De^915#npzN<*J>r(!+tVJtePYTkglDH zEu15}@2=EYlpIMKm3`o3j7jWQ2to4Ep_N|aYA3gt1*Gd}Vr!a`vwrRE_n(IBZ=!)W z?PXTRhH1#Fxytk0RxBYsmy=)2lq#vr1mdK+X{Md%QY&}*M~w=?K0@YpTa+)5!S}{J z!+J!?iDY4W5dlXL00f9qGJ%AVH~!w9)RA7(MOt1RMYhoWr3Gw)Bt?*)pCU?PB464} z&65xn1sVFj><$%4bQoH5s?Di3=8Do>E8i+C4X4i2kWo2d;y_Qjvs7YyK*lAlg7eNUo9f5a}*e~1upTGr(r=ghHtfm^bFgn+9c2+-+1 z?#sJ0mJ9;QE#dPVp5v3~n(Mr-d_dq~?8Up#AeVO~gMo6(+jH(-;uF*Ny-!?41J6U^ ziM#k9mv5Xi^IDB>P`?lU?;*`d!pS<-fZ}(_U|_lB`so*M@eYLF1OI)>H4=BEPBXa0 zZ_b%^?hda&{5{~m$2=oZrfw1g-Qqjv7gNvP;}Hx$`Wkx41YUrl*XoGn-0~gwxH;*4 zr|Wm|AP7+BH9pI@MI+}2O+9x{(y+GF{74{;fPmK$F84Wq|4%)4M}oM0f`G3ehJeDRi%s{4(nIRQijoYRADmvCD_fKb3h z%Qp=E`+49mD+37j{z2Tb;5D}!!=KK&dh@oG-P2UN001i^;5Q#Q$L++Y8!P7TuOi@2 z_W(`=JjVqyxjiZb=wjWHQ74`jALZrX0stT&;L^j>I%ARg^1b1?An2vg;z2J3x86$s z*=Kmtt;H?;&Mkhc%!CLdgaMY8K7`1ml-V-vg)V%xA??KT#}-b`^B)($Xp|DJ2n4$4 z+I<0;J(;RI$y)s)OSK_$WlN^=mW;(6=?i-TaxMByi$2|wE^W9eUB;l#8q(+Y>~m@V z^Vool|DFxATstcIf+0=1BW2t|MFm|%`QQUG>N!CdSNxKWlxa)qOi!9zPnv8;%9MqO zM!}uPXjOBpR9VLVfujcd^V(gZBRD5;6)ofE3s(^(Crot)jQ#X~j*7~kPG$y-%JhIy zsUEGIN8AI6UMBAPBjC9*nA+Xemm31%$;0->?n<4-gnTR(J6%FLPnMivOGin~2xe#b zIZCd(hd{(`z+hzrdA^_s$SBqR;!d9d3w@Qoox(a? zxvci~h3`tvD0on|M62<7fHZ9q{|*|=OnPggX!zdBLER>-)Bb0|^PfZgX22&lEb&Px*2=6p(yh9xODv-+9u5z@Sgv=~Uu zV5DXuMn+p|X{?HFXk#Y2fH`}b!4rY7j@GWbxjFv3k_Y5w6lPjot_M_k2OiduwQS>d z&gMx{Gm{|Zf3&lG)9df|!MyEkUHKvq-S^nTSdT!^Ly9p{#fl5q|HsyS;C>{YAF_So zNYtLDA15IY<8WDktJVDre+1$O7sh$jG zX5z=<_nxNjC*^G6h(H7!!7}a5o}2|CkkG+))iYkwwO}?Q=TT*PR65!1Mt4X^=he}w zAThIIdRAZ0M2xg+X>YUYb7*e5ENv|uodqHgSABO`GG@tK_3}p)JfPrYWO}ePgn+OC_TRA%JG)i@U z-pvVMc0{g6q;?knv{TENaQuY|r7-hDu9BFE3zm0*mHyJ~2|) zR?!9?PlJ8|>vff{yS@p_i9l`_^VRU-`r4x@irw12Qvr;2Bhizis9InU? zuFMEtE(}--5C|ywG{Z~oTN#;KV}23rvnXNWdbK;VB0V+ zi}4NhmNmbFSa$iZAuMaE0>yilzxw@$>aK6X8h$tr_|y)z*WSVkuFc%kK5H~_L1+y)4o4v3 wp`r-HeO>gSb9dQqjgwcf182JIo%ZA0#tbe^ZZ~d??!UQjx!03h+g)It02&OKo&W#< literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@U#70yX+z##xc7U0gsR6~z z4{q?Tj_OmsBEyj*wu>?kN6aVv*PzUx2ACoGKXwMUwr$nQy5~N_iM#=K2^loun!6T2 zOcNA96GUWgk-L1s^}TnG+}gHPEnDl{4{HD-jidkypa`0v7zRC%c?;wWBDcuh?bo;A z*0!x$ndf}}5$;Yy44Poji)%j?lG22`^X{DhfnbWfx<9S`GFvfbKj|(di9jRpKW2Y@ z_L<@{fkOA{Xnb`twp2?h$PYHfI(m9^ApXkvvmT%2b2@K^Rrs|dWV0q6L;(;%DjFmr zqHQ=(ns#$y&*|B(`Lh&k4Bq#|)RqtiIPI>K-HZFp@f$HP4+e#=B2bfHGNp74iVd8NRm;(Zl%e+D#)%+(sSpKJftG| z_(?aB01gZc+}HCS^bSqICdz`nCS#-Mi{^Q=kY=AMoY9LqQImxEbg(Kc z-sjXw?AubN6%xi-D{;nL96uVtm~7HrN_Ao<=zZE$Ie%@^KFw0>fc9<><8jAw?B5Ip z|BnkoD8;)rE+X3aUKBKxabe|8Xlz^{4w^zVK1PZiK2f0YAtHr@m>C}f#fG4iXk5%z zKT^n_p3ijF#wb%wI~ikCexd$@DpD%&dn6o~34rx^EIHBQgHZ7F6K*~|bKCWq0+0te z8nF3}l|Pk|>PKmO_0?aCwtgvu1Tw(>6+;>q znw6J-%-{Y7jset;Qiw_4nPg44rj9!f1~~oaOww`FG$}B^NxCLy+D_jA7&z%{nxq3* zA79}l-3;J1posv$aAroDVBr9c0XRz0uDS!}fDkjAHH8)67=Sw+(`v7vy^N*;3*C6ah-36xt5KC`gKE zwHy80tVwUa*_BZ(p5CBdygAoDBAVV4bzD#+HVXg;lYg^tEjzSS)UjDm2{^#FL0h|Y zzbuaL&ToEN8C=3*3j8K6C3z{vvBk6NYm<-On&kDVBmFI*t3ri?C}95g=j;66_t)La znqJYxi`l!^wZX?LPJ6p(MP>drOa5=J?tIz27HA11Jq5Zier|tW`>=Ghmp?qL`3EXM zd*`6+R*J4I6_WJ7*Xh`%3;tX8^~c)Re<}~OyD30y&+7R3cj52(tia$if!d2xm~RAb kw=mXUpTc~19xI7fpv>!AC1^@t8MM6+kP&iBz1^@srYrq-+2}NxqIr7}A@h9AmK}7#2!2dxx z>GEn!I%VqCsZ%Zx>D=riCT?>z#Mwr#5D89b9nk|d>q{S-7_ z)c9ik>o_U>p8%W*!RKZ$X7_@iDd+`2Ku*mMG({31oy9f5e&}Es-oYQ%22}%u&J2Y^EbYN|=N#G6JPf&g(!no}YX zfJ+3BM!g`3K9s#QpK-8y<9-3G@)+b7U2p}(rS&^q?Jx-9s$fr>#5X25qfgnOk z(t7dmr?LEs%Z#zP!Cj{xBQ!~m^*{J2blWUhh7jYOR` z-_i);E=?ILQ}aaa$Km`m2?VI5nSc@1%)(wb@_{+U$P<#N5pLuYh{!u*ju1sHBZxrK zM&EHnrF|@Co)VAgokajZQ9q^%XesdkayLbiC}JD=O0*PnqL8ad(`qYiq3JA0523kPDH2dMBVp z`}F)~q}MN9+?%h6{`+aKsSE;OPMQ_bL4)&wQwXfP+5!giim2hFj{p^HqwKeEex?8s zqeUvBF_|?Zl1s{s4Z=bZ4TR)UU?~|>5iwg57!V*Z2(uy@ArS=VyC8}P5~umTUqmn} z5rFT4UJF%*f^8mojhw51nLQMd!JJy(xe7F6Q4yIjFHDp6LbFxrwkSw*?Yk-^fO&nI z`RF}ggGCe(;Zfuqa#gfRMRchLQio&{FYMSO%hb@v$CpA7;IzM&_Uzu|%oNaHZ@jVY zQnnLFpSEqU$%AR`PVDBe9kgeTAfu3t!raIR()5fr(?T=fp1n5zsq5{J*KA4+r8fUv z|38MZvC>#Ho%77Kdad4GJ*&$c(x&9%o4;QB;p#5F6ZWm13-wq@Yd+GO>ps6dny7zD zUR(KO2IiS{%#-@{I;sEux%y4iKcz9bA%FmDlcUW)qBb>a5}^}78Y)B$n6|1cB77SJ z5Fk1L7^~E?)7&i7vL_aKfdWSbCQ&T77178?OyvD-ijb+_lRjNhhKd9MAO=xp=F>mwIIsE+C)h1 zw(GjpC8|+QJW_(_Rm>70s##@-=v;5q4+{z8zhmSd&|4&G&&nhsjRLKi4t0BEB7iVK z4V6S)BeNbXgO+YCe-d?(QVEhpTm5v4sGlp=qkiN8j{wq1?_NH19JZ(E;=5N@TuHPr zPjuoDyM;coq~|tw+|$w{%v@A|z1l|y_U#42YTG9;Adh@^N1Z*G+Hp5nEDW_SzPtFk z>wo>P!&b+=9Cg;}GB?)xkH9hXuyBCic_a2Errrjr7bv zU-L7Prd+F7+qQS>HcrMiA=tK+*16xKK$!gKX|EjH*?X6?-N|!`+k40~Um$!`IBTB}#wkzfo%;-Z z85|*;Q-RngJn=_)!t*gp?b{bjMKJ(V0;Smt)p+;4yEi|6_;SdyU;8Zkv(Hbz5BTxd zAwT{+WZCb1R{Y;%b=UCQr>`zpXh#ThKL~Yq3H7tBg!9E6ijPShCOv1SWx^TC@Ra%Z z$psC&OPT>~0z_`(KIH39C&~+kV-5{-$#B#!n~aT|i~}9OO{lkkNX%z|hdp19%}xMiU;5lff{hglHJ zJ(!Eb+^rLva=8f|Uc*y@r`Ga>RM$gc51Y!REeqWVO~c%SnODr6H86A7l|QP~Isx2- zF7cEZ;;0jz61q+p6Z92q%9nO5a4m*mVD9L0vLG%Nq|2_{Wd!)lCZ0u}+9PW?>STzg zL_#z$Qm`posOT0`SC@->oLt1gMJCLKkHs3jGU>_!p|p0*&`^TXKhUr#o8Pg(ndp*g zBG$$oB9Lo%10)&On{e(GTN`Fxs6;4}@eMU>O6Mv(jwLpRcD+*wHWl0prKul`$Y&zd zmL>=zOD9e94K!>@=4$$7IL1G&bPBOriIdK_lFlrRU!yb#JtCDCmP2reN@ z>9mstbutmV2)t0_Rgk3mC5I%BW1LBF+`B|ZqxBCpO3+EtA)<4LNRIW{yDvQyNnH{g zYMhHZ(&ojzGi9d z4c;=mVTg55636k|Bz8ocGLmcL#~sbrF5;sLAAUJp7!G<0^F|BvhJvgmnsr2zmPpbO zi96P;8I5Eeef2v_*fe25@k~u5?FdCZ!B7zl6e9!c!%5FzH1CLJErCGu`Ns?8O3dRm zyCSw?9m)=tCwiLz=~cv*|FuMiL+zys1oK`oG~ zNa0jVZjEZrdUsU|V9D?OCJn$_6$vMb+@gPX#iQ$%01N)9HjTh%71@#NgfG`TdF}|1 z-#%!kCO&^4+r}?fKfde&U}_J|>wtG^lI4W==fAjY18`-8S~WnbBL1=Ag`%%HdURYL z00BuU@IpoWzUJvFmm(k!UO$XzE)1Me5nrfz=(4?t0FyKo#Y}t!tm(yh+0%e)FLyQSr)a5qB$mW{hgd*INlZkFTtY>!47{kaj@6IA;UUKE%i65^fBOoyq zVF2icfRdY<4|~lzVi#GhGK+{!B-X4^7!=Re%+blnSF>2;tXWRxy8Dj@GfBpK{?s}` z1T?<;#@i*cPS{13jbd44CZ#aCWUj{L39>S)cgeAZn;UOeyHBknq(5%dJfjF`b?-HM zEng?>JY!MCr9}OqhE2(0jjJe7v{PQJiHH41+jHy`Fj6ldb)ky&bI4# z=Vjl;K)gkycOOE~zCRZP7z7mUMnKEo_SxBVpC5OFkMPxyqy2Ws{F!d>f$Ql0 u+fD>DKtR#x-LHP5PH3l-9e3B?;=a)|0`d`1nA(^gJ+(c?rlOb*<&6M>PX#al literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d4391482be68e9e4a07fab769b5de337d16eb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 721 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy!iOI#yLg7ec#$`gxH85~pclTsBt za}(23gHjVyDhp4h+5i=O3-AeX1=1l$e`s#|#^}+&7(N@w0CIr{$Oe+Uk^K-ZP~83C zcc@hG6rikF&NPT(23>y!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..ddbb8a6f4d62f34c4952a02cfa9b007fe971ac59 GIT binary patch literal 2274 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 MVRFxGwxr1@5Fna+0RR91 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..64fe98ea007a079692141ed588e6fb3f7a568a1f GIT binary patch literal 4948 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 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..19bd0232ff7b6dcd53508fcf8c0468aa1be0b0b5 GIT binary patch literal 3256 zcmV;p3`g@)Nk&Gn3;+OEMM6+kP&iDa3;+NxkH8}k2?lK=Ia2ofXZaJpeh4D^KLPCN z0oZYXHZlP1bICQq8bw5-b2AG8`(m?bWb2_nKV~i*MY8QG{XZFQ-69G2OP1fiDm=U> zfk7ilit3o2B)orBFX8eEoW{28Dr-Id(;GmLu0&Up-T(;w={|ua z#u)&qgowfuK#-mc!-PLd0%QOfG>I^up@JW9JnVZ7AiM#D`5`*05dZ)EM{_mrBQZl zFI!_s^5Jv(3`fMYX7FkO!-5TvFImhxkgXlCJeEoxTO*5^ zp}D&%t0F!+wr#7nl|J^p|9>2!AK@-J@hb$Bfrv6VRnRK9ySqkIz}=nqxbH4Hwr!iX zw!St#+ls!($dxMh_cOFn1G5bFpV_E{ZQJ{t1yQ!Gs?Da5Exn=FN6@6U9{>dZJFO_< z9&U>#-zQ4X&tH*mM*`{C^TnR}rT%R*Fjrz*1LYOH*UKyW2a{9B7kU~n0Q@5di%zzm z)+g**+D#VCCRk}9Qg6rR^3C_aKL9?G&l)g1`1pM{S75r|KM%EQ-3=E2CqX+huZ7TDn&D^ z5LQ#|ZI1uA7P5~i;*fw#s7&`-{wCz z;)kPv`|)}+p-cezk@KH)Fz=Ft2@X9;&eAfLm(f~6Dzpl9Yoa zlaz&f+L8{~np%2)aqB0xrn0cvuw0Hr`c?n*!3Q3_xAQd!3t#N1uj)5_?CN=!0tDeQ zHpIz|dDy9(-p2CMy`-cSNKgwX2>^w_2#*Pr>GWt<$(dGmW^?Upe)oFLf0As3C1Q9o z!Wy^vz5VI!C$1Ld>X#383t#G+fzoDk60QNmW$cridG58WuK|Ll()*hqy_7S3)EFZM z0Ru*8^x3s{y!YH|Z@X|ye&YUzs%2To8xKFe1hzw1P2KEqxJu-SS7WW@<*)Lsr_%d- zhg6{1NTpe%yu1JM$LDxx&1+Q{7Vkj-<%x0CCTO$a0G7rc&PaC3xZe)UmHh?2^Fq#i z{5KaP4@4{8@W`#7zVXF7_lYWo2lQSqKx8^xAXt#sMrCtq&1mJ&Oe=E#H9)u+87b10 z-69}Pp8KpA(x2aMzZ6A!7`e6-0XrgA%91RO#lRRkr^bwI@?WvM6FxKZNdRTD{!yJy zJ?#t~N;}b7v{>SYCKJh&!vd76-49_&KlO3A80#dc9Y~a<;9>(st1#vtqszIvp#u_0 zQXI^s3_pZS8H0ErfWXnbKS@ymlWWU(pb8qJQ-p^hZ&%dho%=kPX=Cu9%R9C=)|54% zis-?fdXRx-F^8#d=CVU$>CBeQG&l_9I8H1!;w~} zB06M@ic7P{J)aF5Jk7EiQzW>j7p|1#`48SmcmGhl*FP}#?fx11Rg-tC9xfd4m~6# z;eim4VK1Hnjr1{6(&Exst>98jBVuYAr)0g1ObWHfLX+Rh*275FNggf@B9Ub>>wGbp zdw>*4xLGVZf+XwKB0NYyQW)rB(ZK>4&C_$T2oG8*?ZpWmn?E`|OC#2l!GNh-!h_yF zbP61bikxqqCqB<;q@)`~JJF7`gam|%YNqg*24w^_Aw=~c2|^KdEl@X&C`qsqc+g+q z{;P6jmOjba$eaw+i7z1lu145W?c5~lQ+>f>cm=!3K%te_aOiwq^>7N^@PpQq$X$D zc;&|N{Ea+j6{i-_3LNOLJo?t5C$83)7E3{}9Ho;n=XJdBu*E!Q?Re1!9=noGU}tUH zk*7THpKh+JBr8)AaxC+dRXk!j5|}x0jP5E}sj?J2Y6UAPW>&Wi2v)0XBsMZ;I%|%x zVziax1x6zss2Y-3TZ~3HUW!1QaLk}eX2uOL+se2hi)@VlZcvA?gW8ff�fh0= z#FUsTMpSM=>md6uUYfL`B957Xl)-qc~F`CeTW9DgNXITXd$INv_$QTUcrAZlS9p<_-f2?Y3Pq956TDd;4D5==;oxj+c zz|6&;xIM-CcD+*UEb!Nl*q%+o3M;qg`QCeZ>cjep!;?uVs>9Y4zk7B3@vVV|J-4&J z=Btk__d7RvzcUjXJF~n#o#iA2D1?EBLWn^<)Pz|Kk%5Gu!0u;!i$)^TZlv*`Pg0{q z8<~=s3?}NKCKHFsP%&^0I1*@f({2_Mh%ieT{kd#TjO*X$!YAX>UHbDl+!6!R`p>i2r703qG=>jC(zXG$g?&sClK5B$1mHI?(%<^YgjfW%woD-KV1w)1239b~dB6a<*eUxc)s`m3&4Rqq%)hAL7V7 zcINRQZF~K}-mq<$r^oYZ!ox^7=>QKh$j0Qd(%yd*(+&@`jfE+qWX7nD3mHQj)iIJ< zfdk=?vw932yX;10H9q%6lq@0`e=miG&lR3mM2sacQXP-qZ#r5izK8TPmt!tC)TqM4 zV0B)F6@qXu62#?c z2}x3@!YjVTCM^8=OUZuSP0IRj;1b1VBx?_l*i+(UA@%yd7bhqF{`bo>KlkI)Di1U|y#h0T3sHOI5bg{+C~_Sl@A@`!MtK37*Y|a%249-w*%YuDcHj zmG{|!$q0`OJjiO&E-prj${nh=wE5z9*)I>BJ{@1&bHQxL{>hsao*jPmLPkJTOFmN= znmro*h6ibvbP$goQpv#$E$7STRUnVA`lT{I`@jF|rvc3G`~IG1uCVzuLltJ^vJu&< zvSot9w|55ghYWuy=dG$miD->J)l2THPi~k8rnjg58J53x|1F=-k6(Fl{q@XOi@&eA z{Td@@KE#{90v+5t$oH5bz@avhc z{yDYubbeI5_Vei0^IEo9O|+Rnld@)|%o{Pt#nI#@lgkM+>5%hx$;PF1N{s}isAM1Y zZ(e?5cYkiwv!}=z{9}3C2WDVgS@`Mi<7qp6#69w@yuJR9^3^5wb(^UoC6UM-Pm&tx zd&Rjy@t7sCE{3Z=vo%AaBKK!Y!{)FZa-HRZ`uf+N`#;}#_ITb-SLiQbO!qA-00e<$ z8#=k_CP)2LSoZ3{$qoQ} z*3-F5&W^tM8|eGiul+f&2w1XIEm;kVZu>;pPXFs9g{3dzlNDIo_1P=CMZmjp*+2aY zG@t@`ko1;E0+`BJq&b+UBxs-9eW@eN2HE3L3N-`{Z}Jz z+j_}tF>|l~aRJ4VBuSFNK2oKG^ZyqP9KMrVm9-~i*tU%$Ucc|&f7plHwvi-B=30j_ z$5aUW@62wY^AF~N=>G&jH^QC>+irpgKQnIe45`uS_^_t+wMoA ^ccZ$mX9Vj78e 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..fd331ea3a984f18115273a08ed8c92064d256666 GIT binary patch literal 4258 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 E0GLkZo&W#< literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..cc399cbf58ff984d2cdac11ea940bf4ee505c187 GIT binary patch literal 7666 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 W9TI>-u?x!V(!j(Gla?e function configurationCacheProblems() { return ( // begin-report-data -{"diagnostics":[{"locations":[{"path":"E:\\JOKI\\smartinfuse\\android\\app\\build.gradle","line":11},{"path":"build file 'E:\\JOKI\\smartinfuse\\android\\build.gradle'","line":30}],"problem":[{"text":"Groovy DSL script compilation problem"}],"severity":"ERROR","contextualLabel":"Could not compile build file 'E:\\JOKI\\smartinfuse\\android\\app\\build.gradle'.","error":{"summary":[{"text":"at "},{"name":"build_djun6d34t187xj944t8h2kljy$_run_closure3.doCall$original(E:\\JOKI\\smartinfuse\\android\\build.gradle:30)"}],"parts":[{"text":"org.gradle.groovy.scripts.ScriptCompilationException: Could not compile build file 'E:\\JOKI\\smartinfuse\\android\\app\\build.gradle'.\r\n"},{"internalText":"\tat org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler.lambda$wrapCompilationFailure$0(DefaultScriptCompilationHandler.java:224)\r\n\tat org.gradle.api.problems.internal.DefaultProblemReporter.throwing(DefaultProblemReporter.java:69)\r\n\tat org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler.wrapCompilationFailure(DefaultScriptCompilationHandler.java:219)\r\n\tat org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler.compileScript(DefaultScriptCompilationHandler.java:162)\r\n\tat org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler.compileToDir(DefaultScriptCompilationHandler.java:112)\r\n\tat org.gradle.groovy.scripts.internal.BuildOperationBackedScriptCompilationHandler$2.run(BuildOperationBackedScriptCompilationHandler.java:54)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.groovy.scripts.internal.BuildOperationBackedScriptCompilationHandler.compileToDir(BuildOperationBackedScriptCompilationHandler.java:51)\r\n\tat org.gradle.groovy.scripts.internal.GroovyScriptClassCompiler$GroovyScriptCompilationAndInstrumentation.compile(GroovyScriptClassCompiler.java:295)\r\n\tat org.gradle.internal.scripts.BuildScriptCompilationAndInstrumentation.execute(BuildScriptCompilationAndInstrumentation.java:136)\r\n\tat org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:105)\r\n\tat org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:44)\r\n\tat org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:59)\r\n\tat org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:56)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)\r\n\tat org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:56)\r\n\tat org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44)\r\n\tat org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:42)\r\n\tat org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:75)\r\n\tat org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)\r\n\tat org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:50)\r\n\tat org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:28)\r\n\tat org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:61)\r\n\tat org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:26)\r\n\tat org.gradle.internal.execution.steps.NoInputChangesStep.execute(NoInputChangesStep.java:30)\r\n\tat org.gradle.internal.execution.steps.NoInputChangesStep.execute(NoInputChangesStep.java:21)\r\n\tat org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:69)\r\n\tat org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:46)\r\n\tat org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:189)\r\n\tat org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:75)\r\n\tat org.gradle.internal.Either$Right.fold(Either.java:175)\r\n\tat org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:62)\r\n\tat org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73)\r\n\tat org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48)\r\n\tat org.gradle.internal.execution.steps.NeverUpToDateStep.execute(NeverUpToDateStep.java:34)\r\n\tat org.gradle.internal.execution.steps.NeverUpToDateStep.execute(NeverUpToDateStep.java:22)\r\n\tat org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)\r\n\tat org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)\r\n\tat org.gradle.internal.execution.steps.ResolveNonIncrementalCachingStateStep.executeDelegate(ResolveNonIncrementalCachingStateStep.java:50)\r\n\tat org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:71)\r\n\tat org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:39)\r\n\tat org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:107)\r\n\tat org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:56)\r\n\tat org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:64)\r\n\tat org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:43)\r\n\tat org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)\r\n\tat org.gradle.internal.execution.steps.AssignImmutableWorkspaceStep.lambda$executeInTemporaryWorkspace$3(AssignImmutableWorkspaceStep.java:209)\r\n\tat org.gradle.internal.execution.workspace.impl.CacheBasedImmutableWorkspaceProvider$1.withTemporaryWorkspace(CacheBasedImmutableWorkspaceProvider.java:116)\r\n\tat org.gradle.internal.execution.steps.AssignImmutableWorkspaceStep.executeInTemporaryWorkspace(AssignImmutableWorkspaceStep.java:199)\r\n\tat org.gradle.internal.execution.steps.AssignImmutableWorkspaceStep.lambda$execute$0(AssignImmutableWorkspaceStep.java:121)\r\n\tat org.gradle.internal.execution.steps.AssignImmutableWorkspaceStep.execute(AssignImmutableWorkspaceStep.java:121)\r\n\tat org.gradle.internal.execution.steps.AssignImmutableWorkspaceStep.execute(AssignImmutableWorkspaceStep.java:90)\r\n\tat org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:38)\r\n\tat org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)\r\n\tat org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)\r\n\tat org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)\r\n\tat org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)\r\n\tat org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)\r\n\tat org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)\r\n\tat org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:48)\r\n\tat org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)\r\n\tat org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:61)\r\n\tat org.gradle.groovy.scripts.internal.GroovyScriptClassCompiler.doCompile(GroovyScriptClassCompiler.java:167)\r\n\tat org.gradle.groovy.scripts.internal.GroovyScriptClassCompiler.compile(GroovyScriptClassCompiler.java:129)\r\n\tat org.gradle.groovy.scripts.internal.CrossBuildInMemoryCachingScriptClassCache.getOrCompile(CrossBuildInMemoryCachingScriptClassCache.java:53)\r\n\tat org.gradle.groovy.scripts.internal.BuildScopeInMemoryCachingScriptClassCompiler.compile(BuildScopeInMemoryCachingScriptClassCompiler.java:50)\r\n\tat org.gradle.groovy.scripts.DefaultScriptCompilerFactory$ScriptCompilerImpl.compile(DefaultScriptCompilerFactory.java:49)\r\n\tat org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:131)\r\n\tat org.gradle.configuration.BuildOperationScriptPlugin$1.run(BuildOperationScriptPlugin.java:68)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.configuration.BuildOperationScriptPlugin.lambda$apply$0(BuildOperationScriptPlugin.java:65)\r\n\tat org.gradle.internal.code.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)\r\n\tat org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:65)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:411)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:429)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:410)\r\n\tat org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:46)\r\n\tat org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:27)\r\n\tat org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)\r\n\tat org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:109)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:411)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:429)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:410)\r\n\tat org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:100)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:72)\r\n\tat org.gradle.api.internal.project.DefaultProject.evaluateUnchecked(DefaultProject.java:828)\r\n\tat org.gradle.api.internal.project.ProjectLifecycleController.lambda$ensureSelfConfigured$2(ProjectLifecycleController.java:85)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$doTransition$14(StateTransitionController.java:255)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:254)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$maybeTransitionIfNotCurrentlyTransitioning$10(StateTransitionController.java:199)\r\n\tat org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:36)\r\n\tat org.gradle.internal.model.StateTransitionController.maybeTransitionIfNotCurrentlyTransitioning(StateTransitionController.java:195)\r\n\tat org.gradle.api.internal.project.ProjectLifecycleController.ensureSelfConfigured(ProjectLifecycleController.java:85)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.ensureConfigured(DefaultProjectStateRegistry.java:385)\r\n\tat org.gradle.api.internal.project.DefaultProject.evaluationDependsOn(DefaultProject.java:914)\r\n\tat org.gradle.api.internal.project.DefaultProject.evaluationDependsOn(DefaultProject.java:905)\r\n\tat org.gradle.api.Project$evaluationDependsOn$0.call(Unknown Source)\r\n"},{"text":"\tat build_djun6d34t187xj944t8h2kljy$_run_closure3.doCall$original(E:\\JOKI\\smartinfuse\\android\\build.gradle:30)\r\n\tat build_djun6d34t187xj944t8h2kljy$_run_closure3.doCall(E:\\JOKI\\smartinfuse\\android\\build.gradle)\r\n"},{"internalText":"\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat org.gradle.util.internal.ClosureBackedAction.execute(ClosureBackedAction.java:73)\r\n\tat org.gradle.util.internal.ConfigureUtil.configureTarget(ConfigureUtil.java:166)\r\n\tat org.gradle.util.internal.ConfigureUtil.configure(ConfigureUtil.java:107)\r\n\tat org.gradle.util.internal.ConfigureUtil$WrappedConfigureAction.execute(ConfigureUtil.java:178)\r\n\tat org.gradle.api.internal.DefaultMutationGuard$1.execute(DefaultMutationGuard.java:66)\r\n\tat org.gradle.internal.Actions.with(Actions.java:206)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator$1.run(BuildOperationCrossProjectConfigurator.java:68)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator.lambda$runProjectConfigureAction$0(BuildOperationCrossProjectConfigurator.java:65)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:411)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:429)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:410)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator.runProjectConfigureAction(BuildOperationCrossProjectConfigurator.java:65)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator.access$100(BuildOperationCrossProjectConfigurator.java:31)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator$BlockConfigureBuildOperation.run(BuildOperationCrossProjectConfigurator.java:106)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator.runBlockConfigureAction(BuildOperationCrossProjectConfigurator.java:61)\r\n\tat org.gradle.api.internal.project.BuildOperationCrossProjectConfigurator.subprojects(BuildOperationCrossProjectConfigurator.java:47)\r\n\tat org.gradle.api.internal.project.DefaultProject.subprojects(DefaultProject.java:781)\r\n\tat org.gradle.api.internal.project.DefaultProject.subprojects(DefaultProject.java:771)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat org.gradle.internal.metaobject.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:547)\r\n\tat org.gradle.internal.metaobject.BeanDynamicObject.tryInvokeMethod(BeanDynamicObject.java:218)\r\n\tat org.gradle.api.internal.project.MutableStateAccessAwareDynamicObject.tryInvokeMethod(MutableStateAccessAwareDynamicObject.java:111)\r\n\tat org.gradle.internal.metaobject.CompositeDynamicObject.tryInvokeMethod(CompositeDynamicObject.java:99)\r\n\tat org.gradle.internal.extensibility.MixInClosurePropertiesAsMethodsDynamicObject.tryInvokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:38)\r\n\tat org.gradle.groovy.scripts.BasicScript$ScriptDynamicObject.tryInvokeMethod(BasicScript.java:138)\r\n\tat org.gradle.internal.metaobject.AbstractDynamicObject.invokeMethod(AbstractDynamicObject.java:163)\r\n\tat org.gradle.api.internal.project.DefaultDynamicLookupRoutine.invokeMethod(DefaultDynamicLookupRoutine.java:58)\r\n\tat org.gradle.groovy.scripts.BasicScript.invokeMethod(BasicScript.java:87)\r\n"},{"text":"\tat build_djun6d34t187xj944t8h2kljy.run(E:\\JOKI\\smartinfuse\\android\\build.gradle:29)\r\n"},{"internalText":"\tat org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:91)\r\n\tat org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.lambda$apply$1(DefaultScriptPluginFactory.java:141)\r\n\tat org.gradle.configuration.ProjectScriptTarget.addConfiguration(ProjectScriptTarget.java:79)\r\n\tat org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:144)\r\n\tat org.gradle.configuration.BuildOperationScriptPlugin$1.run(BuildOperationScriptPlugin.java:68)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.configuration.BuildOperationScriptPlugin.lambda$apply$0(BuildOperationScriptPlugin.java:65)\r\n\tat org.gradle.internal.code.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)\r\n\tat org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:65)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:411)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:429)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:410)\r\n\tat org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:46)\r\n\tat org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:27)\r\n\tat org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)\r\n\tat org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:109)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$1(DefaultProjectStateRegistry.java:411)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$fromMutableState$2(DefaultProjectStateRegistry.java:434)\r\n\tat org.gradle.internal.work.DefaultWorkerLeaseService.withReplacedLocks(DefaultWorkerLeaseService.java:359)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:434)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:410)\r\n\tat org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:100)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:72)\r\n\tat org.gradle.api.internal.project.DefaultProject.evaluateUnchecked(DefaultProject.java:828)\r\n\tat org.gradle.api.internal.project.ProjectLifecycleController.lambda$ensureSelfConfigured$2(ProjectLifecycleController.java:85)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$doTransition$14(StateTransitionController.java:255)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:254)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$maybeTransitionIfNotCurrentlyTransitioning$10(StateTransitionController.java:199)\r\n\tat org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:36)\r\n\tat org.gradle.internal.model.StateTransitionController.maybeTransitionIfNotCurrentlyTransitioning(StateTransitionController.java:195)\r\n\tat org.gradle.api.internal.project.ProjectLifecycleController.ensureSelfConfigured(ProjectLifecycleController.java:85)\r\n\tat org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.ensureConfigured(DefaultProjectStateRegistry.java:385)\r\n\tat org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:42)\r\n\tat org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:56)\r\n\tat org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:50)\r\n\tat org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:65)\r\n\tat org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)\r\n\tat org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40)\r\n\tat org.gradle.initialization.VintageBuildModelController.lambda$prepareProjects$2(VintageBuildModelController.java:84)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$doTransition$14(StateTransitionController.java:255)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:254)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$transitionIfNotPreviously$11(StateTransitionController.java:213)\r\n\tat org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:36)\r\n\tat org.gradle.internal.model.StateTransitionController.transitionIfNotPreviously(StateTransitionController.java:209)\r\n\tat org.gradle.initialization.VintageBuildModelController.prepareProjects(VintageBuildModelController.java:84)\r\n\tat org.gradle.initialization.VintageBuildModelController.getConfiguredModel(VintageBuildModelController.java:64)\r\n\tat org.gradle.internal.build.DefaultBuildLifecycleController.lambda$withProjectsConfigured$1(DefaultBuildLifecycleController.java:133)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$notInState$3(StateTransitionController.java:132)\r\n\tat org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:46)\r\n\tat org.gradle.internal.model.StateTransitionController.notInState(StateTransitionController.java:128)\r\n\tat org.gradle.internal.build.DefaultBuildLifecycleController.withProjectsConfigured(DefaultBuildLifecycleController.java:133)\r\n\tat org.gradle.internal.build.DefaultBuildToolingModelController.locateBuilderForTarget(DefaultBuildToolingModelController.java:58)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeModelCreator$DefaultBuildTreeModelController.lambda$locateBuilderForTarget$0(DefaultBuildTreeModelCreator.java:64)\r\n\tat org.gradle.internal.build.DefaultBuildLifecycleController.withToolingModels(DefaultBuildLifecycleController.java:327)\r\n\tat org.gradle.internal.build.AbstractBuildState.withToolingModels(AbstractBuildState.java:160)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeModelCreator$DefaultBuildTreeModelController.locateBuilderForTarget(DefaultBuildTreeModelCreator.java:64)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeModelCreator$DefaultBuildTreeModelController.locateBuilderForDefaultTarget(DefaultBuildTreeModelCreator.java:59)\r\n\tat org.gradle.tooling.internal.provider.runner.BuildModelActionRunner$ModelCreateAction.fromBuildModel(BuildModelActionRunner.java:81)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeModelCreator.fromBuildModel(DefaultBuildTreeModelCreator.java:48)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$fromBuildModel$2(DefaultBuildTreeLifecycleController.java:90)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$runBuild$4(DefaultBuildTreeLifecycleController.java:120)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$transition$6(StateTransitionController.java:169)\r\n\tat org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:266)\r\n\tat org.gradle.internal.model.StateTransitionController.lambda$transition$7(StateTransitionController.java:169)\r\n\tat org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:46)\r\n\tat org.gradle.internal.model.StateTransitionController.transition(StateTransitionController.java:169)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.runBuild(DefaultBuildTreeLifecycleController.java:117)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.fromBuildModel(DefaultBuildTreeLifecycleController.java:82)\r\n\tat org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:49)\r\n\tat org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)\r\n\tat org.gradle.internal.buildtree.ProblemReportingBuildActionRunner.run(ProblemReportingBuildActionRunner.java:49)\r\n\tat org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:71)\r\n\tat org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:135)\r\n\tat org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)\r\n\tat org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.lambda$execute$0(RootBuildLifecycleBuildActionExecutor.java:54)\r\n\tat org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:130)\r\n\tat org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.execute(RootBuildLifecycleBuildActionExecutor.java:54)\r\n\tat org.gradle.internal.buildtree.InitDeprecationLoggingActionExecutor.execute(InitDeprecationLoggingActionExecutor.java:62)\r\n\tat org.gradle.internal.buildtree.InitProblems.execute(InitProblems.java:36)\r\n\tat org.gradle.internal.buildtree.DefaultBuildTreeContext.execute(DefaultBuildTreeContext.java:40)\r\n\tat org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.lambda$execute$0(BuildTreeLifecycleBuildActionExecutor.java:71)\r\n\tat org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:60)\r\n\tat org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.execute(BuildTreeLifecycleBuildActionExecutor.java:71)\r\n\tat org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$2.call(RunAsBuildOperationBuildActionExecutor.java:67)\r\n\tat org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$2.call(RunAsBuildOperationBuildActionExecutor.java:63)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)\r\n\tat org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)\r\n\tat org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor.execute(RunAsBuildOperationBuildActionExecutor.java:63)\r\n\tat org.gradle.launcher.exec.RunAsWorkerThreadBuildActionExecutor.lambda$execute$0(RunAsWorkerThreadBuildActionExecutor.java:36)\r\n\tat org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)\r\n\tat org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)\r\n\tat org.gradle.launcher.exec.RunAsWorkerThreadBuildActionExecutor.execute(RunAsWorkerThreadBuildActionExecutor.java:36)\r\n\tat org.gradle.tooling.internal.provider.continuous.ContinuousBuildActionExecutor.execute(ContinuousBuildActionExecutor.java:110)\r\n\tat org.gradle.tooling.internal.provider.SubscribableBuildActionExecutor.execute(SubscribableBuildActionExecutor.java:64)\r\n\tat org.gradle.internal.session.DefaultBuildSessionContext.execute(DefaultBuildSessionContext.java:46)\r\n\tat org.gradle.internal.buildprocess.execution.BuildSessionLifecycleBuildActionExecutor$ActionImpl.apply(BuildSessionLifecycleBuildActionExecutor.java:92)\r\n\tat org.gradle.internal.buildprocess.execution.BuildSessionLifecycleBuildActionExecutor$ActionImpl.apply(BuildSessionLifecycleBuildActionExecutor.java:80)\r\n\tat org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:71)\r\n\tat org.gradle.internal.buildprocess.execution.BuildSessionLifecycleBuildActionExecutor.execute(BuildSessionLifecycleBuildActionExecutor.java:62)\r\n\tat org.gradle.internal.buildprocess.execution.BuildSessionLifecycleBuildActionExecutor.execute(BuildSessionLifecycleBuildActionExecutor.java:41)\r\n\tat org.gradle.internal.buildprocess.execution.StartParamsValidatingActionExecutor.execute(StartParamsValidatingActionExecutor.java:64)\r\n\tat org.gradle.internal.buildprocess.execution.StartParamsValidatingActionExecutor.execute(StartParamsValidatingActionExecutor.java:32)\r\n\tat org.gradle.internal.buildprocess.execution.SessionFailureReportingActionExecutor.execute(SessionFailureReportingActionExecutor.java:51)\r\n\tat org.gradle.internal.buildprocess.execution.SessionFailureReportingActionExecutor.execute(SessionFailureReportingActionExecutor.java:39)\r\n\tat org.gradle.internal.buildprocess.execution.SetupLoggingActionExecutor.execute(SetupLoggingActionExecutor.java:47)\r\n\tat org.gradle.internal.buildprocess.execution.SetupLoggingActionExecutor.execute(SetupLoggingActionExecutor.java:31)\r\n\tat org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:70)\r\n\tat org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.ForwardClientInput.lambda$execute$0(ForwardClientInput.java:40)\r\n\tat org.gradle.internal.daemon.clientinput.ClientInputForwarder.forwardInput(ClientInputForwarder.java:80)\r\n\tat org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:37)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:64)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)\r\n\tat org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)\r\n\tat org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)\r\n\tat org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)\r\n\tat org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)\r\n\tat org.gradle.launcher.daemon.server.DaemonStateCoordinator.lambda$runCommand$0(DaemonStateCoordinator.java:321)\r\n\tat org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)\r\n\tat org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)\r\n"},{"text":"Caused by: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:\r\nbuild file 'E:\\JOKI\\smartinfuse\\android\\app\\build.gradle': 11: unable to resolve class val \n @ line 11, column 5.\r\n val keystoreProperties = Properties()\r\n ^\r\n\r\nbuild file 'E:\\JOKI\\smartinfuse\\android\\app\\build.gradle': 12: unable to resolve class val \n @ line 12, column 5.\r\n val keystorePropertiesFile = rootProject.file(\"key.properties\")\r\n ^\r\n\r\n2 errors\r\n\r\n"},{"internalText":"\tat org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler.compileScript(DefaultScriptCompilationHandler.java:160)\r\n\t... 327 more\r\n"}]},"problemId":[{"name":"groovy-dsl","displayName":"Groovy DSL script compilation"},{"name":"compilation","displayName":"Compilation"},{"name":"compilation-failed","displayName":"Groovy DSL script compilation problem"}]}],"problemsReport":{"totalProblemCount":1,"buildName":"android","requestedTasks":"","documentationLink":"https://docs.gradle.org/8.12/userguide/problems-report.html","documentationLinkCaption":"Problem report","summaries":[]}} +{"diagnostics":[{"locations":[{"path":"build file 'C:\\Users\\rahma\\AppData\\Local\\Pub\\Cache\\hosted\\pub.dev\\awesome_notifications-0.10.1\\android\\build.gradle'","line":8},{"path":"build file 'C:\\Users\\rahma\\AppData\\Local\\Pub\\Cache\\hosted\\pub.dev\\awesome_notifications-0.10.1\\android\\build.gradle'","line":8}],"problem":[{"text":"Space-assignment syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10.0."}],"contextualLabel":"Space-assignment syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/8.12/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"space-assignment-syntax-in-groovy-dsl","displayName":"Space-assignment syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('url = ') instead."}]]}],"problemsReport":{"totalProblemCount":1,"buildName":"android","requestedTasks":"assembleDebug","documentationLink":"https://docs.gradle.org/8.12/userguide/problems-report.html","documentationLinkCaption":"Problem report","summaries":[]}} // end-report-data );} diff --git a/lib/app/models/chart_data_point.dart b/lib/app/models/chart_data_point.dart index 27d0e52..8a885aa 100644 --- a/lib/app/models/chart_data_point.dart +++ b/lib/app/models/chart_data_point.dart @@ -1,14 +1,21 @@ class ChartDataPoint { - final int hour; + final DateTime timestamp; final double dropsPerMinute; ChartDataPoint({ - required this.hour, + required this.timestamp, required this.dropsPerMinute, }); + /// Label waktu format HH:mm, dipakai di sumbu X grafik & tooltip. + String get timeLabel { + final h = timestamp.hour.toString().padLeft(2, '0'); + final m = timestamp.minute.toString().padLeft(2, '0'); + return '$h:$m'; + } + @override String toString() { - return 'ChartDataPoint(hour: $hour, dropsPerMinute: $dropsPerMinute)'; + return 'ChartDataPoint(timestamp: $timestamp, dropsPerMinute: $dropsPerMinute)'; } } \ No newline at end of file diff --git a/lib/app/models/realtime_monitoring_model.dart b/lib/app/models/realtime_monitoring_model.dart index 452f7ca..4fcad42 100644 --- a/lib/app/models/realtime_monitoring_model.dart +++ b/lib/app/models/realtime_monitoring_model.dart @@ -1,5 +1,3 @@ -// lib/app/models/realtime_monitoring_model.dart - class RealtimeMonitoringModel { final String deviceId; final String roomId; @@ -10,6 +8,7 @@ class RealtimeMonitoringModel { final DateTime lastUpdate; final int servoAngleOpen; final int servoAngleClose; + final bool buzzerActive; // ← tambah ini RealtimeMonitoringModel({ required this.deviceId, @@ -21,9 +20,9 @@ class RealtimeMonitoringModel { required this.lastUpdate, this.servoAngleOpen = 90, this.servoAngleClose = 0, + this.buzzerActive = false, // ← tambah ini }); - // From Realtime Database factory RealtimeMonitoringModel.fromRealtimeDB( String deviceId, String roomId, @@ -37,6 +36,7 @@ class RealtimeMonitoringModel { deviceId: deviceId, roomId: roomId, servoOpen: controlling['servo_open'] ?? false, + buzzerActive: controlling['buzzer_active'] ?? false, // ← tambah ini lastCommand: DateTime.parse( controlling['last_command'] ?? DateTime.now().toIso8601String(), ), @@ -50,11 +50,11 @@ class RealtimeMonitoringModel { ); } - // To Realtime Database Map toRealtimeDB() { return { 'controlling': { 'servo_open': servoOpen, + 'buzzer_active': buzzerActive, // ← tambah ini 'last_command': lastCommand.toIso8601String(), }, 'monitoring': { @@ -73,6 +73,7 @@ class RealtimeMonitoringModel { String? deviceId, String? roomId, bool? servoOpen, + bool? buzzerActive, // ← tambah ini DateTime? lastCommand, String? deviceStatus, double? dropRate, @@ -84,6 +85,7 @@ class RealtimeMonitoringModel { deviceId: deviceId ?? this.deviceId, roomId: roomId ?? this.roomId, servoOpen: servoOpen ?? this.servoOpen, + buzzerActive: buzzerActive ?? this.buzzerActive, // ← tambah ini lastCommand: lastCommand ?? this.lastCommand, deviceStatus: deviceStatus ?? this.deviceStatus, dropRate: dropRate ?? this.dropRate, diff --git a/lib/app/modules/device-detail/controllers/device_detail_controller.dart b/lib/app/modules/device-detail/controllers/device_detail_controller.dart index 8c27953..08441d5 100644 --- a/lib/app/modules/device-detail/controllers/device_detail_controller.dart +++ b/lib/app/modules/device-detail/controllers/device_detail_controller.dart @@ -24,7 +24,7 @@ class DeviceDetailController extends GetxController { final isServoActive = false.obs; final deviceStatus = 'disconnected'.obs; final isLoading = true.obs; - + final isBuzzerActive = false.obs; final RxList chartData = [].obs; StreamSubscription? _realtimeSubscription; @@ -117,6 +117,7 @@ class DeviceDetailController extends GetxController { lastUpdate.value = data.lastUpdate; isServoActive.value = data.servoOpen; deviceStatus.value = data.deviceStatus; + isBuzzerActive.value = data.buzzerActive; } }, onError: (error) => AppSnackbar.error('Koneksi realtime terputus')); } @@ -140,15 +141,16 @@ class DeviceDetailController extends GetxController { } void _processHistoryData(List histories) { + // Ambil 10 data TERBARU histories.sort((a, b) => b.timestamp.compareTo(a.timestamp)); - final latestHistories = histories.take(10).toList(); + // Urutkan lagi dari lama -> baru supaya grafik terbaca kiri ke kanan latestHistories.sort((a, b) => a.timestamp.compareTo(b.timestamp)); final List points = latestHistories.map((history) { return ChartDataPoint( - hour: history.timestamp.hour, + timestamp: history.timestamp, dropsPerMinute: history.dropPerMinute, ); }).toList(); @@ -186,7 +188,18 @@ class DeviceDetailController extends GetxController { ); } + Future turnOffBuzzer() async { + try { + final dbRoomId = roomId!.toLowerCase().replaceAll(' ', '_'); + await _realtimeService.controlBuzzer(dbRoomId, deviceId!, false); + isBuzzerActive.value = false; + AppSnackbar.success('Buzzer dimatikan'); + } catch (e) { + AppSnackbar.error('Gagal mematikan buzzer'); + } + } + Future refreshData() async { await _loadDeviceData(); } -} +} \ No newline at end of file diff --git a/lib/app/modules/device-detail/views/device_detail_view.dart b/lib/app/modules/device-detail/views/device_detail_view.dart index 9921e9e..4019ecf 100644 --- a/lib/app/modules/device-detail/views/device_detail_view.dart +++ b/lib/app/modules/device-detail/views/device_detail_view.dart @@ -49,15 +49,24 @@ class DeviceDetailView extends GetView { children: [ _buildSection( title: 'Monitoring Infus', - onSeeAll: () {}, child: _buildMonitoringCard(), ), const SizedBox(height: 20), _buildSection( title: 'Kontrol Alat', - onSeeAll: () {}, child: _buildControlCard(), ), + const SizedBox(height: 12), + // Buzzer card — hanya tampil kalau buzzer aktif + Obx(() { + if (!controller.isBuzzerActive.value) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: _buildBuzzerCard(), + ); + }), const SizedBox(height: 20), _buildSection( title: 'History Data', @@ -100,19 +109,7 @@ class DeviceDetailView extends GetView { fontWeight: FontWeight.bold, color: Colors.black87, ), - ), - if (onSeeAll != null) - GestureDetector( - onTap: onSeeAll, - child: const Text( - 'See all', - style: TextStyle( - fontSize: 13, - color: Color(0xFF2196F3), - fontWeight: FontWeight.w600, - ), - ), - ), + ), ], ), ), @@ -335,6 +332,98 @@ class DeviceDetailView extends GetView { ); } + /// Card buzzer — muncul saat buzzer_active == true + Widget _buildBuzzerCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.red[50], + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.red[200]!), + boxShadow: [ + BoxShadow( + color: Colors.red.withOpacity(0.08), + blurRadius: 12, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Icon dengan animasi pulse + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.notifications_active_rounded, + size: 28, + color: Colors.red[700], + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Buzzer Aktif', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + color: Colors.red[800], + ), + ), + const SizedBox(height: 4), + Text( + 'Tetesan infus berhenti. Harap periksa pasien.', + style: TextStyle( + fontSize: 12, + color: Colors.red[600], + height: 1.4, + ), + ), + ], + ), + ), + const SizedBox(width: 12), + // Tombol matikan buzzer + ElevatedButton( + onPressed: controller.turnOffBuzzer, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[700], + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 0, + ), + child: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.volume_off_rounded, size: 18), + SizedBox(height: 3), + Text( + 'Matikan', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ); + } + Widget _buildEmptyChart() { return Container( padding: const EdgeInsets.all(32), @@ -375,4 +464,4 @@ class DeviceDetailView extends GetView { ), ); } -} +} \ No newline at end of file diff --git a/lib/app/modules/home-patient/controllers/home_patient_controller.dart b/lib/app/modules/home-patient/controllers/home_patient_controller.dart index 0ac39a6..3356d73 100644 --- a/lib/app/modules/home-patient/controllers/home_patient_controller.dart +++ b/lib/app/modules/home-patient/controllers/home_patient_controller.dart @@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart'; import 'dart:async'; import '../../../services/firestore_service.dart'; import '../../../services/realtime_database_service.dart'; +import '../../../services/notification_service.dart'; import '../../../models/patient_model.dart'; import '../../../models/chart_data_point.dart'; import '../../../models/history_model.dart'; @@ -51,6 +52,7 @@ class NotificationData { class HomePatientController extends GetxController { final FirestoreService _firestoreService = FirestoreService(); final RealtimeDatabaseService _realtimeService = RealtimeDatabaseService(); + final NotificationService _notificationService = NotificationService(); final deviceId = ''.obs; final roomId = ''.obs; @@ -71,6 +73,8 @@ class HomePatientController extends GetxController { deviceStatus: 'disconnected', ).obs; + final isBuzzerActive = false.obs; + final chartDataPoints = [].obs; final notificationsList = [].obs; @@ -82,9 +86,12 @@ class HomePatientController extends GetxController { StreamSubscription? _notificationsSubscription; StreamSubscription? _historySubscription; + final Set _alertedDevices = {}; + @override void onInit() { super.onInit(); + _initializeNotificationService(); _loadDataFromArguments(); } @@ -101,6 +108,15 @@ class HomePatientController extends GetxController { super.onClose(); } + Future _initializeNotificationService() async { + try { + await _notificationService.initialize(); + print('Notification service initialized'); + } catch (e) { + print('Error initializing notification service: $e'); + } + } + void _loadDataFromArguments() async { try { final args = Get.arguments as Map?; @@ -176,14 +192,16 @@ class HomePatientController extends GetxController { final dbRoomId = roomId.value.toLowerCase().replaceAll(' ', '_'); - _realtimeSubscription?.cancel(); // Cancel previous subscription if exists + _realtimeSubscription?.cancel(); _realtimeSubscription = _realtimeService .getDeviceStream(dbRoomId, deviceId.value) .listen( - (device) { + (device) async { if (device != null) { - _updateCurrentInfusData(device); + await _updateCurrentInfusData(device); + await _checkDropRate(device); + isBuzzerActive.value = device.buzzerActive; } else { print('Received null device data'); } @@ -200,7 +218,7 @@ class HomePatientController extends GetxController { } } - void _updateCurrentInfusData(RealtimeMonitoringModel device) async { + Future _updateCurrentInfusData(RealtimeMonitoringModel device) async { try { final room = await _firestoreService.getRoomById(device.roomId); @@ -217,11 +235,61 @@ class HomePatientController extends GetxController { } } + Future _checkDropRate(RealtimeMonitoringModel device) async { + try { + final deviceId = device.deviceId; + final dropRate = device.dropRate; + final deviceStatus = device.deviceStatus; + + if (dropRate == 0.0 && deviceStatus == 'connected') { + if (!_alertedDevices.contains(deviceId)) { + final patient = await _firestoreService.getPatientByDeviceId( + deviceId, + ); + final room = await _firestoreService.getRoomById(device.roomId); + + if (patient != null) { + await _notificationService.showDropRateAlertForPatient( + patientId: patient.id, + patientName: patient.namePatient, + deviceId: deviceId, + roomName: room?.roomName ?? device.roomId, + ); + + _alertedDevices.add(deviceId); + print('Alert sent for device: $deviceId'); + } + } + } else if (dropRate > 0.0) { + if (_alertedDevices.contains(deviceId)) { + _alertedDevices.remove(deviceId); + _notificationService.clearCooldown(deviceId); + await _notificationService.cancelNotificationByDeviceId(deviceId); + print('Device reset and notification dismissed: $deviceId'); + } + } + } catch (e) { + print('Error checking drop rate: $e'); + } + } + + Future turnOffBuzzer() async { + try { + final dbRoomId = roomId.value.toLowerCase().replaceAll(' ', '_'); + await _realtimeService.controlBuzzer(dbRoomId, deviceId.value, false); + isBuzzerActive.value = false; + AppSnackbar.success('Buzzer dimatikan'); + } catch (e) { + print('Error turning off buzzer: $e'); + AppSnackbar.error('Gagal mematikan buzzer'); + } + } + void _loadHistoryData() { try { isLoadingHistory.value = true; - _historySubscription?.cancel(); // Cancel previous subscription if exists + _historySubscription?.cancel(); _historySubscription = _firestoreService .getDeviceHistoriesStream(deviceId.value) @@ -257,12 +325,16 @@ class HomePatientController extends GetxController { void _processHistoryData(List histories) { try { + // Ambil 10 data TERBARU histories.sort((a, b) => b.timestamp.compareTo(a.timestamp)); final latestHistories = histories.take(10).toList(); + + // Urutkan lagi dari lama -> baru supaya grafik terbaca kiri ke kanan latestHistories.sort((a, b) => a.timestamp.compareTo(b.timestamp)); + final List points = latestHistories.map((history) { return ChartDataPoint( - hour: history.timestamp.hour, + timestamp: history.timestamp, dropsPerMinute: history.dropPerMinute, ); }).toList(); @@ -278,8 +350,7 @@ class HomePatientController extends GetxController { try { isLoadingNotifications.value = true; - _notificationsSubscription - ?.cancel(); // Cancel previous subscription if exists + _notificationsSubscription?.cancel(); _notificationsSubscription = _firestoreService .getNotificationsStream() @@ -313,13 +384,12 @@ class HomePatientController extends GetxController { ); } catch (e) { print('Error processing notification ${notif.id}: $e'); - // Continue with other notifications } } notifList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); - notificationsList.value = notifList; + notificationsList.value = notifList.take(2).toList(); isLoadingNotifications.value = false; } catch (e) { print('Error processing notifications: $e'); @@ -349,6 +419,7 @@ class HomePatientController extends GetxController { context: context, builder: (BuildContext context) { return AlertDialog( + backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), @@ -370,9 +441,7 @@ class HomePatientController extends GetxController { await _openWhatsApp(); }, style: ElevatedButton.styleFrom( - backgroundColor: const Color( - 0xFF25D366, - ), // WhatsApp green color + backgroundColor: const Color(0xFF25D366), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -395,8 +464,7 @@ class HomePatientController extends GetxController { Future _openWhatsApp() async { try { - const String nursePhoneNumber = - '6281234567890'; + const String nursePhoneNumber = '6281231901277'; final String message = Uri.encodeComponent( 'Halo, saya ${patientName.value} di ruangan ${currentInfusData.value.room}. ' @@ -506,11 +574,19 @@ class HomePatientController extends GetxController { Future refreshData() async { try { await _loadPatientData(); - // Tidak perlu reload history karena sudah menggunakan stream - // History akan auto-update } catch (e) { print('Error refreshing data: $e'); AppSnackbar.error('Gagal refresh data: $e'); } } -} + + void clearDeviceAlert(String deviceId) { + _alertedDevices.remove(deviceId); + _notificationService.clearCooldown(deviceId); + } + + void clearAllAlerts() { + _alertedDevices.clear(); + _notificationService.clearAllCooldowns(); + } +} \ No newline at end of file diff --git a/lib/app/modules/home-patient/views/home_patient_view.dart b/lib/app/modules/home-patient/views/home_patient_view.dart index a9ebbb4..ff8800c 100644 --- a/lib/app/modules/home-patient/views/home_patient_view.dart +++ b/lib/app/modules/home-patient/views/home_patient_view.dart @@ -12,8 +12,6 @@ class HomePatientView extends GetView { @override Widget build(BuildContext context) { - Get.put(HomePatientController()); - return Scaffold( backgroundColor: Colors.white, body: SafeArea( @@ -33,9 +31,9 @@ class HomePatientView extends GetView { Expanded( child: Obx( () => Text( - 'Halo ${controller.patientName.value}', + 'Halo Wali Pasien ${controller.patientName.value}', style: const TextStyle( - fontSize: 24, + fontSize: 20, fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, @@ -67,36 +65,111 @@ class HomePatientView extends GetView { const SizedBox(height: 15), + // ── BUZZER ALERT BANNER ────────────────────────────────── + Obx(() { + if (!controller.isBuzzerActive.value) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 12), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.red[50], + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.red[200]!), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.notifications_active_rounded, + color: Colors.red[700], + size: 26, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Buzzer Aktif!', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + color: Colors.red[800], + ), + ), + const SizedBox(height: 3), + Text( + 'Tetesan infus berhenti. Harap hubungi perawat.', + style: TextStyle( + fontSize: 12, + color: Colors.red[600], + height: 1.4, + ), + ), + ], + ), + ), + const SizedBox(width: 10), + ElevatedButton( + onPressed: controller.turnOffBuzzer, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[700], + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 0, + ), + child: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.volume_off_rounded, size: 18), + SizedBox(height: 2), + Text( + 'Matikan', + style: TextStyle(fontSize: 11), + ), + ], + ), + ), + ], + ), + ), + ); + }), + // ── END BUZZER ALERT BANNER ────────────────────────────── + // Monitoring Infus Section Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( + children: const [ + Text( 'Monitoring Infus', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), - TextButton( - onPressed: () { - // TODO: Navigate to monitoring detail - }, - child: const Text( - 'See all', - style: TextStyle( - color: Color(0xFF2196F3), - fontWeight: FontWeight.w600, - ), - ), - ), ], ), ), - // Monitoring Card - Gunakan MonitoringCard dari widgets + // Monitoring Card Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Obx(() { @@ -114,24 +187,20 @@ class HomePatientView extends GetView { final infusData = controller.currentInfusData.value; - // Konversi ke InfusMonitoring untuk MonitoringCard final monitoring = InfusMonitoring( id: controller.deviceId.value, patientName: controller.patientName.value, deviceId: infusData.deviceId, room: infusData.room, - roomId: '', // Not needed for display + roomId: '', dropsPerMinute: infusData.dropsPerMinute, - lastUpdate: - DateTime.now(), // Will be calculated from updateTime + lastUpdate: DateTime.now(), deviceStatus: infusData.deviceStatus, ); return MonitoringCard( monitoring: monitoring, - onTap: () { - // TODO: Navigate to detail - }, + onTap: () {}, ); }), ), @@ -143,31 +212,19 @@ class HomePatientView extends GetView { padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( + children: const [ + Text( 'History Data', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), - TextButton( - onPressed: () { - // TODO: Navigate to history detail - }, - child: const Text( - 'See all', - style: TextStyle( - color: Color(0xFF2196F3), - fontWeight: FontWeight.w600, - ), - ), - ), ], ), ), - // Chart Card - Gunakan HistoryGrafikCard dari widgets + // Chart Card Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Obx(() { @@ -185,9 +242,7 @@ class HomePatientView extends GetView { return HistoryGrafikCard( dataPoints: controller.chartDataPoints.toList(), - onBookmarkTap: () { - // TODO: Bookmark functionality - }, + onBookmarkTap: () {}, ); }), ), @@ -199,26 +254,14 @@ class HomePatientView extends GetView { padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( + children: const [ + Text( 'History Notifikasi', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), - TextButton( - onPressed: () { - // TODO: Navigate to notifications - }, - child: const Text( - 'See all', - style: TextStyle( - color: Color(0xFF2196F3), - fontWeight: FontWeight.w600, - ), - ), - ), ], ), ), @@ -278,9 +321,7 @@ class HomePatientView extends GetView { room: notification.room, deviceId: notification.deviceId, timeAgo: notification.timeAgo, - onTap: () { - // TODO: Handle notification tap - }, + onTap: () {}, ); }, ); @@ -293,7 +334,6 @@ class HomePatientView extends GetView { ), ), - // Floating Action Button - Call Nurse floatingActionButton: FloatingActionButton( onPressed: () => controller.showCallDialog(context), backgroundColor: const Color(0xFF2196F3), @@ -301,4 +341,4 @@ class HomePatientView extends GetView { ), ); } -} +} \ No newline at end of file diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index 5eb410f..06c6d73 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -4,6 +4,7 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'dart:async'; import '../../../services/firestore_service.dart'; import '../../../services/realtime_database_service.dart'; +import '../../../services/notification_service.dart'; import '../../../models/patient_model.dart'; import '../../../models/room_model.dart'; import '../../../models/realtime_monitoring_model.dart'; @@ -35,6 +36,7 @@ class HomeController extends GetxController { final searchController = TextEditingController(); final FirestoreService _firestoreService = FirestoreService(); final RealtimeDatabaseService _realtimeService = RealtimeDatabaseService(); + final NotificationService _notificationService = NotificationService(); final currentCarouselIndex = 0.obs; final bannerImages = [ @@ -45,12 +47,27 @@ class HomeController extends GetxController { final monitoringList = [].obs; final isLoading = true.obs; + List _cachedRooms = []; + StreamSubscription? _patientsSubscription; - StreamSubscription? _realtimeSubscription; + StreamSubscription? _roomsSubscription; + + // Map deviceId -> StreamSubscription untuk per-device realtime stream + final Map _deviceSubscriptions = {}; + + // Map deviceId -> data realtime terbaru + final Map _realtimeDataMap = {}; + + List _latestPatients = []; + + bool _patientsReady = false; + + final Set _alertedDevices = {}; @override void onInit() { super.onInit(); + _initializeNotificationService(); _initializeMonitoring(); } @@ -58,44 +75,128 @@ class HomeController extends GetxController { void onClose() { searchController.dispose(); _patientsSubscription?.cancel(); - _realtimeSubscription?.cancel(); + _roomsSubscription?.cancel(); + // Cancel semua per-device subscriptions + for (final sub in _deviceSubscriptions.values) { + sub.cancel(); + } + _deviceSubscriptions.clear(); super.onClose(); } + Future _initializeNotificationService() async { + try { + await _notificationService.initialize(); + } catch (e) { + print('Error initializing notification service: $e'); + } + } + void _initializeMonitoring() { isLoading.value = true; + _patientsReady = false; + // 1. Cache rooms + _roomsSubscription = _firestoreService.getRoomsStream().listen((rooms) { + _cachedRooms = rooms; + }); + + // 2. Patients stream _patientsSubscription = _firestoreService.getPatientsStream().listen( - (patients) async { - _realtimeSubscription = _realtimeService.getAllDevicesStream().listen(( - realtimeDevices, - ) { - _combineMonitoringData(patients, realtimeDevices); - }); + (patients) { + _latestPatients = patients; + _patientsReady = true; + _syncDeviceSubscriptions(patients); }, onError: (error) { + print('Error patients stream: $error'); isLoading.value = false; - AppSnackbar.error('Gagal memuat data'); + AppSnackbar.error('Gagal memuat data pasien'); }, ); + + // Timeout fallback + Future.delayed(const Duration(seconds: 8), () { + if (isLoading.value) { + print('Timeout: force stop loading'); + isLoading.value = false; + } + }); } - void _combineMonitoringData( - List patients, - List realtimeDevices, - ) async { + /// Sinkronisasi per-device stream — ikuti pola HomePatientController + /// pakai getDeviceStream(dbRoomId, deviceId) bukan getAllDevicesStream() + void _syncDeviceSubscriptions(List patients) { + final currentDeviceIds = patients.map((p) => p.deviceId).toSet(); + final existingDeviceIds = _deviceSubscriptions.keys.toSet(); + + // Cancel stream untuk device yang sudah tidak ada di patients + final removedIds = existingDeviceIds.difference(currentDeviceIds); + for (final deviceId in removedIds) { + _deviceSubscriptions[deviceId]?.cancel(); + _deviceSubscriptions.remove(deviceId); + _realtimeDataMap.remove(deviceId); + } + + // Tambah stream untuk device baru + final newIds = currentDeviceIds.difference(existingDeviceIds); + for (final patient in patients.where((p) => newIds.contains(p.deviceId))) { + _subscribeToDevice(patient); + } + } + + void _subscribeToDevice(PatientModel patient) { + // Format room ID sama persis seperti HomePatientController + final dbRoomId = patient.roomId.toLowerCase().replaceAll(' ', '_'); + final deviceId = patient.deviceId; + + print('Subscribing to device: $deviceId in room: $dbRoomId'); + + final sub = _realtimeService + .getDeviceStream(dbRoomId, deviceId) + .listen( + (device) { + if (device != null) { + _realtimeDataMap[deviceId] = device; + } else { + // Device null = disconnected, hapus dari map + _realtimeDataMap.remove(deviceId); + } + // Rebuild setiap ada update dari device manapun + if (_patientsReady) { + _rebuildMonitoringList(); + } + }, + onError: (error) { + print('Error stream device $deviceId: $error'); + // Tetap rebuild walau error, device akan tampil sebagai disconnected + if (_patientsReady) { + _rebuildMonitoringList(); + } + }, + cancelOnError: false, + ); + + _deviceSubscriptions[deviceId] = sub; + } + + void _rebuildMonitoringList() { final List combinedData = []; - for (var patient in patients) { - final realtimeDevice = realtimeDevices.firstWhereOrNull( - (device) => device.deviceId == patient.deviceId, - ); - - final roomName = await _getRoomName(patient.roomId); + for (var patient in _latestPatients) { + final realtimeDevice = _realtimeDataMap[patient.deviceId]; + final roomName = _getRoomNameFromCache(patient.roomId); final dropRate = realtimeDevice?.dropRate ?? 0.0; final lastUpdate = realtimeDevice?.lastUpdate ?? DateTime.now(); final deviceStatus = realtimeDevice?.deviceStatus ?? 'disconnected'; + _checkDropRate( + patient: patient, + dropRate: dropRate, + roomName: roomName, + deviceStatus: deviceStatus, + ); + combinedData.add( InfusMonitoring( id: patient.id, @@ -114,16 +215,37 @@ class HomeController extends GetxController { isLoading.value = false; } - Future _getRoomName(String roomId) async { - try { - final rooms = await _firestoreService.getRoomsStream().first; - final room = rooms.firstWhereOrNull( - (r) => - r.id == roomId || r.roomName.toLowerCase() == roomId.toLowerCase(), - ); - return room?.roomName ?? roomId; - } catch (e) { - return roomId; + String _getRoomNameFromCache(String roomId) { + final room = _cachedRooms.firstWhereOrNull( + (r) => r.id == roomId || r.roomName.toLowerCase() == roomId.toLowerCase(), + ); + return room?.roomName ?? roomId; + } + + Future _checkDropRate({ + required PatientModel patient, + required double dropRate, + required String roomName, + required String deviceStatus, + }) async { + final deviceId = patient.deviceId; + + if (dropRate == 0.0 && deviceStatus == 'connected') { + if (!_alertedDevices.contains(deviceId)) { + await _notificationService.showDropRateAlert( + patientId: patient.id, + patientName: patient.namePatient, + deviceId: deviceId, + roomName: roomName, + ); + _alertedDevices.add(deviceId); + } + } else if (dropRate > 0.0) { + if (_alertedDevices.contains(deviceId)) { + _alertedDevices.remove(deviceId); + _notificationService.clearCooldown(deviceId); + await _notificationService.cancelNotificationByDeviceId(deviceId); + } } } @@ -207,7 +329,9 @@ class HomeController extends GetxController { String getLastUpdateText(DateTime lastUpdate) { final difference = DateTime.now().difference(lastUpdate); - if (difference.inMinutes < 60) { + if (difference.inMinutes < 1) { + return 'Just now'; + } else if (difference.inMinutes < 60) { return '${difference.inMinutes}m ago'; } else if (difference.inHours < 24) { return '${difference.inHours}h ago'; @@ -215,4 +339,14 @@ class HomeController extends GetxController { return '${difference.inDays}d ago'; } } + + void clearDeviceAlert(String deviceId) { + _alertedDevices.remove(deviceId); + _notificationService.clearCooldown(deviceId); + } + + void clearAllAlerts() { + _alertedDevices.clear(); + _notificationService.clearAllCooldowns(); + } } diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart index 5a146a7..252ccc4 100644 --- a/lib/app/modules/home/views/home_view.dart +++ b/lib/app/modules/home/views/home_view.dart @@ -9,10 +9,8 @@ class HomeView extends GetView { @override Widget build(BuildContext context) { - Get.put(HomeController()); - return Scaffold( - backgroundColor: Colors.white, + backgroundColor: const Color(0xFFF5F7FA), body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -23,8 +21,9 @@ class HomeView extends GetView { currentCarouselIndex: controller.currentCarouselIndex, onPageChanged: controller.updateCarouselIndex, ), - const SizedBox(height: 15), + const SizedBox(height: 16), _buildSectionHeader(), + const SizedBox(height: 8), Expanded(child: _buildMonitoringList()), ], ), @@ -33,24 +32,49 @@ class HomeView extends GetView { } Widget _buildHeader(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(20), + return Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - 'Halo Perawat', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Selamat Datang 👋', + style: TextStyle( + fontSize: 13, + color: Colors.grey[500], + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 2), + const Text( + 'Halo, Perawat', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Color(0xFF1A1D2E), + ), + ), + ], ), InkWell( onTap: () => controller.showLogoutDialog(context), + borderRadius: BorderRadius.circular(12), child: Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( - border: Border.all(color: Colors.grey[300]!), - borderRadius: BorderRadius.circular(10), + color: const Color(0xFFFFF0F0), + border: Border.all(color: const Color(0xFFFFCDD2)), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.logout_rounded, + color: Colors.red, + size: 20, ), - child: const Icon(Icons.logout, color: Colors.red), ), ), ], @@ -66,15 +90,13 @@ class HomeView extends GetView { children: [ const Text( 'Monitoring Infus', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - TextButton( - onPressed: () => Get.toNamed('/all-monitoring'), - child: const Text( - 'Lihat Semua', - style: TextStyle(color: Color(0xFF0091EA)), + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + color: Color(0xFF1A1D2E), ), ), + ], ), ); @@ -83,52 +105,203 @@ class HomeView extends GetView { Widget _buildMonitoringList() { return Obx(() { if (controller.isLoading.value) { - return const Center( - child: CircularProgressIndicator(color: Color(0xFF0091EA)), - ); + return _buildLoadingState(); } if (controller.monitoringList.isEmpty) { return _buildEmptyState(); } - return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 20), - itemCount: controller.monitoringList.length, - itemBuilder: (context, index) { - final item = controller.monitoringList[index]; - return MonitoringCard( - monitoring: item, - onTap: () => Get.toNamed( - '/device-detail', - arguments: { - 'deviceId': item.deviceId, - 'roomId': item.roomId, - }, - ), - ); + return RefreshIndicator( + color: const Color(0xFF0091EA), + onRefresh: () async { + // Trigger refresh — stream akan otomatis update, + // ini hanya untuk UX pull-to-refresh + await Future.delayed(const Duration(milliseconds: 800)); }, + child: ListView.builder( + padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), + physics: const AlwaysScrollableScrollPhysics(), + itemCount: controller.monitoringList.length, + itemBuilder: (context, index) { + final item = controller.monitoringList[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: MonitoringCard( + monitoring: item, + onTap: () => Get.toNamed( + '/device-detail', + arguments: {'deviceId': item.deviceId, 'roomId': item.roomId}, + ), + ), + ); + }, + ), ); }); } - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.medical_services_outlined, - size: 80, - color: Colors.grey[400], - ), - const SizedBox(height: 16), - Text( - 'Tidak ada data monitoring', - style: TextStyle(fontSize: 16, color: Colors.grey[600]), + Widget _buildLoadingState() { + return ListView.builder( + padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), + itemCount: 4, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: _buildSkeletonCard(), + ); + }, + ); + } + + Widget _buildSkeletonCard() { + return Container( + height: 110, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 8, + offset: const Offset(0, 2), ), ], ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + _shimmerBox(width: 36, height: 36, radius: 10), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _shimmerBox(width: 140, height: 13, radius: 6), + const SizedBox(height: 6), + _shimmerBox(width: 90, height: 11, radius: 6), + ], + ), + const Spacer(), + _shimmerBox(width: 60, height: 26, radius: 20), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + _shimmerBox(width: 80, height: 11, radius: 6), + const SizedBox(width: 16), + _shimmerBox(width: 60, height: 11, radius: 6), + ], + ), + ], + ), + ), ); } -} \ No newline at end of file + + Widget _shimmerBox({ + required double width, + required double height, + double radius = 4, + }) { + return _ShimmerWidget( + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(radius), + ), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: const Color(0xFFE3F2FD), + borderRadius: BorderRadius.circular(30), + ), + child: const Icon( + Icons.water_drop_outlined, + size: 48, + color: Color(0xFF0091EA), + ), + ), + const SizedBox(height: 20), + const Text( + 'Belum Ada Data', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1A1D2E), + ), + ), + const SizedBox(height: 8), + Text( + 'Data monitoring infus akan tampil di sini setelah perangkat terhubung.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Colors.grey[500], + height: 1.5, + ), + ), + ], + ), + ), + ); + } +} + +/// Widget shimmer animasi untuk skeleton loading +class _ShimmerWidget extends StatefulWidget { + final Widget child; + const _ShimmerWidget({required this.child}); + + @override + State<_ShimmerWidget> createState() => _ShimmerWidgetState(); +} + +class _ShimmerWidgetState extends State<_ShimmerWidget> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1200), + )..repeat(reverse: true); + _animation = Tween( + begin: 0.4, + end: 1.0, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition(opacity: _animation, child: widget.child); + } +} diff --git a/lib/app/modules/login/controllers/login_controller.dart b/lib/app/modules/login/controllers/login_controller.dart index 19aa2ac..3ccb131 100644 --- a/lib/app/modules/login/controllers/login_controller.dart +++ b/lib/app/modules/login/controllers/login_controller.dart @@ -18,7 +18,6 @@ class LoginController extends GetxController { } void login() async { - // Validasi input if (usernameController.text.isEmpty || passwordController.text.isEmpty) { Get.snackbar( 'Error', @@ -30,7 +29,6 @@ class LoginController extends GetxController { return; } - // Validasi format email if (!GetUtils.isEmail(usernameController.text)) { Get.snackbar( 'Error', @@ -45,27 +43,23 @@ class LoginController extends GetxController { try { isLoading.value = true; - // Login dengan Firebase Authentication UserCredential userCredential = await _auth.signInWithEmailAndPassword( email: usernameController.text.trim(), password: passwordController.text.trim(), ); if (userCredential.user != null) { - // Cek apakah user ada di collection users final userDoc = await _firestore .collection('users') .doc(userCredential.user!.uid) .get(); if (userDoc.exists) { - // User ada di collection users (Guardian/Patient) final userData = userDoc.data()!; final deviceId = userData['device_id'] as String?; final userName = userData['name'] as String? ?? 'User'; if (deviceId != null && deviceId.isNotEmpty) { - // User adalah guardian/patient dengan device_id Get.snackbar( 'Berhasil', 'Selamat datang, $userName!', @@ -74,7 +68,6 @@ class LoginController extends GetxController { snackPosition: SnackPosition.TOP, ); - // Navigasi ke home_patient dengan device_id Get.offAllNamed( '/home-patient', arguments: { @@ -84,7 +77,6 @@ class LoginController extends GetxController { }, ); } else { - // User ada tapi tidak punya device_id Get.snackbar( 'Error', 'Akun Anda belum terdaftar dengan device', @@ -95,7 +87,6 @@ class LoginController extends GetxController { await _auth.signOut(); } } else { - // User TIDAK ada di collection users = Perawat/Admin Get.snackbar( 'Berhasil', 'Login berhasil! Selamat datang, Perawat', @@ -104,7 +95,6 @@ class LoginController extends GetxController { snackPosition: SnackPosition.TOP, ); - // Navigasi ke navbar/home perawat Get.offAllNamed('/navbar'); } } @@ -156,12 +146,20 @@ class LoginController extends GetxController { } void contactNurse() async { - const phoneNumber = '+6281231901277'; // Tanpa spasi - final url = Uri.parse('https://wa.me/$phoneNumber'); + const phoneNumber = '6281231901277'; + final message = Uri.encodeComponent( + 'Halo, saya wali pasien dari (Nama Pasien) ' + 'Saya ingin meminta akun untuk login ke aplikasi Smart Infus.', + ); + // Construct the WhatsApp URL with the message + final url = Uri.parse('https://wa.me/$phoneNumber?text=$message'); + + // Attempt to launch WhatsApp if (await canLaunchUrl(url)) { await launchUrl(url, mode: LaunchMode.externalApplication); } else { + // Show an error if WhatsApp can't be opened Get.snackbar( 'Error', 'Tidak dapat membuka WhatsApp', diff --git a/lib/app/modules/navbar/bindings/navbar_binding.dart b/lib/app/modules/navbar/bindings/navbar_binding.dart index f99c3db..f8fc104 100644 --- a/lib/app/modules/navbar/bindings/navbar_binding.dart +++ b/lib/app/modules/navbar/bindings/navbar_binding.dart @@ -2,7 +2,7 @@ import 'package:get/get.dart'; import '../controllers/navbar_controller.dart'; import '../../home/controllers/home_controller.dart'; import '../../notification/controllers/notification_controller.dart'; -import '../../schedule/controllers/schedule_controller.dart'; +// import '../../schedule/controllers/schedule_controller.dart'; import '../../profile/controllers/profile_controller.dart'; class NavbarBinding extends Bindings { @@ -11,7 +11,7 @@ class NavbarBinding extends Bindings { Get.lazyPut(() => NavbarController()); Get.lazyPut(() => HomeController()); Get.lazyPut(() => NotificationController()); - Get.lazyPut(() => ScheduleController()); + // Get.lazyPut(() => ScheduleController()); Get.lazyPut(() => ProfileController()); } } diff --git a/lib/app/modules/navbar/views/navbar_view.dart b/lib/app/modules/navbar/views/navbar_view.dart index 2d3c1f3..aec8848 100644 --- a/lib/app/modules/navbar/views/navbar_view.dart +++ b/lib/app/modules/navbar/views/navbar_view.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import '../controllers/navbar_controller.dart'; import '../../home/views/home_view.dart'; import '../../notification/views/notification_view.dart'; -import '../../schedule/views/schedule_view.dart'; +// import '../../schedule/views/schedule_view.dart'; import '../../profile/views/profile_view.dart'; class NavbarView extends StatelessWidget { @@ -16,7 +16,7 @@ class NavbarView extends StatelessWidget { final pages = const [ HomeView(), NotificationView(), - ScheduleView(), + // ScheduleView(), ProfileView(), ]; @@ -52,8 +52,8 @@ class NavbarView extends StatelessWidget { children: [ _navItem(controller, Icons.home, 0, 'Home'), _navItem(controller, Icons.chat_bubble_outline, 1, 'Notification'), - _navItem(controller, Icons.calendar_today, 2, 'Schedule'), - _navItem(controller, Icons.person_outline, 3, 'Account'), + // _navItem(controller, Icons.calendar_today, 2, 'Schedule'), + _navItem(controller, Icons.person_outline, 2, 'Account'), ], ), ); diff --git a/lib/app/modules/notification/controllers/notification_controller.dart b/lib/app/modules/notification/controllers/notification_controller.dart index 0663046..883de71 100644 --- a/lib/app/modules/notification/controllers/notification_controller.dart +++ b/lib/app/modules/notification/controllers/notification_controller.dart @@ -53,7 +53,7 @@ class NotificationItem { class NotificationController extends GetxController { final FirestoreService _firestoreService = FirestoreService(); - + final selectedTab = 0.obs; final _allNotifications = [].obs; final isLoading = true.obs; @@ -76,47 +76,51 @@ class NotificationController extends GetxController { void _initializeNotifications() { isLoading.value = true; - _notificationsSubscription = _firestoreService - .getNotificationsStream() - .listen((notificationModels) async { - final List items = []; + _notificationsSubscription = _firestoreService.getNotificationsStream().listen( + (notificationModels) async { + final List items = []; - for (var notifModel in notificationModels) { - // Get patient info - final patient = await _firestoreService.getPatientById( - notifModel.patientId, - ); - - // Get room name from patient's roomId - String roomName = 'Unknown Room'; - if (patient != null && patient.roomId.isNotEmpty) { - final rooms = await _firestoreService.getRoomsStream().first; - final room = rooms.firstWhereOrNull( - (r) => r.roomName.toLowerCase() == patient.roomId.toLowerCase(), + for (var notifModel in notificationModels) { + // Get patient info + final patient = await _firestoreService.getPatientById( + notifModel.patientId, + ); + + // Get room name from patient's roomId + String roomName = 'Unknown Room'; + if (patient != null && patient.roomId.isNotEmpty) { + final rooms = await _firestoreService.getRoomsStream().first; + final room = rooms.firstWhereOrNull( + (r) => + r.id == + patient + .roomId, // ✅ UBAH INI - cari berdasarkan ID, bukan roomName + ); + roomName = room?.roomName ?? patient.roomId; + } + + items.add( + NotificationItem( + id: notifModel.id, + title: notifModel.title, + message: notifModel.message, + room: roomName, + deviceId: notifModel.deviceId, + patientName: patient?.namePatient ?? 'Unknown Patient', + time: notifModel.createdAt, + isRead: notifModel.isRead, + ), ); - roomName = room?.roomName ?? patient.roomId; } - items.add( - NotificationItem( - id: notifModel.id, - title: notifModel.title, - message: notifModel.message, - room: roomName, - deviceId: notifModel.deviceId, - patientName: patient?.namePatient ?? 'Unknown Patient', - time: notifModel.createdAt, - isRead: notifModel.isRead, - ), - ); - } - - _allNotifications.value = items; - isLoading.value = false; - }, onError: (error) { - print('Error listening to notifications: $error'); - isLoading.value = false; - }); + _allNotifications.value = items; + isLoading.value = false; + }, + onError: (error) { + print('Error listening to notifications: $error'); + isLoading.value = false; + }, + ); } // Computed property untuk notifikasi yang difilter @@ -205,4 +209,4 @@ class NotificationController extends GetxController { print('Error adding notification: $e'); } } -} \ No newline at end of file +} diff --git a/lib/app/modules/notification/views/notification_view.dart b/lib/app/modules/notification/views/notification_view.dart index d05667a..51a58a7 100644 --- a/lib/app/modules/notification/views/notification_view.dart +++ b/lib/app/modules/notification/views/notification_view.dart @@ -87,15 +87,6 @@ class NotificationView extends GetView { notification: notif, onDismissed: () { controller.deleteNotification(notif.id); - Get.snackbar( - 'Notifikasi Dihapus', - 'Notifikasi telah dihapus', - snackPosition: SnackPosition.BOTTOM, - duration: const Duration(seconds: 2), - margin: const EdgeInsets.all(16), - backgroundColor: Colors.grey[800], - colorText: Colors.white, - ); }, onTap: !notif.isRead ? () { controller.markAsRead(notif.id); @@ -105,15 +96,6 @@ class NotificationView extends GetView { controller.changeTab(1); }); - Get.snackbar( - 'Notifikasi Dibaca', - 'Notifikasi telah dipindahkan ke sudah dibaca', - snackPosition: SnackPosition.BOTTOM, - duration: const Duration(seconds: 2), - margin: const EdgeInsets.all(16), - backgroundColor: const Color(0xFF0091EA), - colorText: Colors.white, - ); } : null, ); }, diff --git a/lib/app/modules/profile/controllers/profile_controller.dart b/lib/app/modules/profile/controllers/profile_controller.dart index 5df3f21..1970227 100644 --- a/lib/app/modules/profile/controllers/profile_controller.dart +++ b/lib/app/modules/profile/controllers/profile_controller.dart @@ -369,7 +369,7 @@ class ProfileController extends GetxController { }, 'monitoring': { 'device_status': 'connected', - 'drop_rate': 0, + 'drop_rate': 1, 'last_update': DateTime.now().toIso8601String(), }, 'settings': {'servo_angle_open': 90, 'servo_angle_close': 0}, diff --git a/lib/app/modules/schedule/controllers/schedule_controller.dart b/lib/app/modules/schedule/controllers/schedule_controller.dart index 3b2e979..bbebf03 100644 --- a/lib/app/modules/schedule/controllers/schedule_controller.dart +++ b/lib/app/modules/schedule/controllers/schedule_controller.dart @@ -5,7 +5,8 @@ import '../../../models/schedule_model.dart'; import '../../../widgets/app_snackbar.dart'; class PatientSchedule { - final String id; + final String id; + final String patientId; final String name; final String room; final String deviceId; @@ -15,6 +16,7 @@ class PatientSchedule { PatientSchedule({ required this.id, + required this.patientId, required this.name, required this.room, required this.deviceId, @@ -61,45 +63,48 @@ class ScheduleController extends GetxController { _schedulesSubscription = _firestoreService .getSchedulesStream(date: selectedDate.value) .listen( - (scheduleModels) async { - try { - final List items = []; - - for (var scheduleModel in scheduleModels) { + (scheduleModels) async { try { - final patient = await _firestoreService.getPatientById(scheduleModel.patientId); + final List items = []; - if (patient != null) { - final roomName = await _getRoomName(patient.roomId); - items.add( - PatientSchedule( - id: scheduleModel.id, - name: patient.namePatient, - room: roomName, - deviceId: patient.deviceId, - medicineTime: scheduleModel.medicineDetail, - fluidTime: scheduleModel.fluidDetail, - timeOfDay: scheduleModel.timeOfDay, - ), - ); + for (var scheduleModel in scheduleModels) { + try { + final patient = await _firestoreService.getPatientById( + scheduleModel.patientId, + ); + + if (patient != null) { + final roomName = await _getRoomName(patient.roomId); + items.add( + PatientSchedule( + id: scheduleModel.id, + patientId: scheduleModel.patientId, + name: patient.namePatient, + room: roomName, + deviceId: patient.deviceId, + medicineTime: scheduleModel.medicineDetail, + fluidTime: scheduleModel.fluidDetail, + timeOfDay: scheduleModel.timeOfDay, + ), + ); + } + } catch (e) { + continue; + } } - } catch (e) { - continue; - } - } - allSchedules.value = items; - _applyTimeFilter(); - isLoading.value = false; - } catch (e) { - isLoading.value = false; - } - }, - onError: (error) { - isLoading.value = false; - AppSnackbar.error('Gagal memuat jadwal'); - }, - ); + allSchedules.value = items; + _applyTimeFilter(); + isLoading.value = false; + } catch (e) { + isLoading.value = false; + } + }, + onError: (error) { + isLoading.value = false; + AppSnackbar.error('Gagal memuat jadwal'); + }, + ); } void _applyTimeFilter() { @@ -113,7 +118,8 @@ class ScheduleController extends GetxController { try { final rooms = await _firestoreService.getRoomsStream().first; final room = rooms.firstWhereOrNull( - (r) => r.id == roomId || r.roomName.toLowerCase() == roomId.toLowerCase(), + (r) => + r.id == roomId || r.roomName.toLowerCase() == roomId.toLowerCase(), ); return room?.roomName ?? roomId; } catch (e) { @@ -135,16 +141,14 @@ class ScheduleController extends GetxController { _loadSchedules(); } - Future addSchedule(PatientSchedule schedule, DateTime scheduleDate) async { + Future addSchedule( + PatientSchedule schedule, + DateTime scheduleDate, + ) async { try { - final patients = await _firestoreService.getPatientsStream().first; - final patient = patients.firstWhereOrNull((p) => p.id == schedule.id); - - if (patient == null) throw Exception('Pasien tidak ditemukan'); - final scheduleModel = ScheduleModel( id: '', - patientId: patient.id, + patientId: schedule.patientId, medicineDetail: schedule.medicineTime, fluidDetail: schedule.fluidTime, scheduleDate: scheduleDate, @@ -155,20 +159,19 @@ class ScheduleController extends GetxController { await _firestoreService.addSchedule(scheduleModel); AppSnackbar.success('Jadwal berhasil ditambahkan'); } catch (e) { - AppSnackbar.error('Gagal menambahkan jadwal'); + AppSnackbar.error('Gagal menambahkan jadwal: ${e.toString()}'); } } - Future updateSchedule(String id, PatientSchedule updatedSchedule, DateTime scheduleDate) async { + Future updateSchedule( + String scheduleId, + PatientSchedule updatedSchedule, + DateTime scheduleDate, + ) async { try { - final patients = await _firestoreService.getPatientsStream().first; - final patient = patients.firstWhereOrNull((p) => p.id == updatedSchedule.id); - - if (patient == null) throw Exception('Pasien tidak ditemukan'); - final scheduleModel = ScheduleModel( - id: id, - patientId: patient.id, + id: scheduleId, + patientId: updatedSchedule.patientId, medicineDetail: updatedSchedule.medicineTime, fluidDetail: updatedSchedule.fluidTime, scheduleDate: scheduleDate, @@ -176,10 +179,10 @@ class ScheduleController extends GetxController { createdAt: DateTime.now(), ); - await _firestoreService.updateSchedule(id, scheduleModel); + await _firestoreService.updateSchedule(scheduleId, scheduleModel); AppSnackbar.success('Jadwal berhasil diperbarui'); } catch (e) { - AppSnackbar.error('Gagal memperbarui jadwal'); + AppSnackbar.error('Gagal memperbarui jadwal: ${e.toString()}'); } } @@ -188,7 +191,7 @@ class ScheduleController extends GetxController { await _firestoreService.deleteSchedule(id); AppSnackbar.success('Jadwal berhasil dihapus'); } catch (e) { - AppSnackbar.error('Gagal menghapus jadwal'); + AppSnackbar.error('Gagal menghapus jadwal: ${e.toString()}'); } } @@ -196,9 +199,13 @@ class ScheduleController extends GetxController { try { final patients = await _firestoreService.getPatientsStream().first; final List> result = []; + final Set seenIds = {}; for (var patient in patients) { try { + // Skip jika ID sudah pernah ditambahkan (mencegah duplikasi) + if (seenIds.contains(patient.id)) continue; + final roomName = await _getRoomName(patient.roomId); result.add({ 'id': patient.id, @@ -206,6 +213,8 @@ class ScheduleController extends GetxController { 'room': roomName, 'device': patient.deviceId, }); + + seenIds.add(patient.id); } catch (e) { continue; } @@ -216,4 +225,4 @@ class ScheduleController extends GetxController { return []; } } -} \ No newline at end of file +} diff --git a/lib/app/modules/splash-screen/controllers/splash_screen_controller.dart b/lib/app/modules/splash-screen/controllers/splash_screen_controller.dart index 616649e..3e8dea4 100644 --- a/lib/app/modules/splash-screen/controllers/splash_screen_controller.dart +++ b/lib/app/modules/splash-screen/controllers/splash_screen_controller.dart @@ -1,9 +1,11 @@ import 'package:get/get.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import '../../../routes/app_pages.dart'; class SplashScreenController extends GetxController { final FirebaseAuth _auth = FirebaseAuth.instance; + final FirebaseFirestore _firestore = FirebaseFirestore.instance; @override void onInit() { @@ -13,16 +15,44 @@ class SplashScreenController extends GetxController { void _checkAuthStatus() async { await Future.delayed(const Duration(seconds: 4)); - - // Cek apakah user sudah login + User? user = _auth.currentUser; - + if (user != null) { - // User sudah login, langsung ke home/navbar - Get.offAllNamed(Routes.NAVBAR); + try { + final userDoc = await _firestore + .collection('users') + .doc(user.uid) + .get(); + + if (userDoc.exists) { + final userData = userDoc.data()!; + final deviceId = userData['device_id'] as String?; + final userName = userData['name'] as String? ?? 'User'; + + if (deviceId != null && deviceId.isNotEmpty) { + Get.offAllNamed( + Routes.HOME_PATIENT, + arguments: { + 'deviceId': deviceId, + 'userName': userName, + 'userEmail': user.email ?? '', + }, + ); + } else { + await _auth.signOut(); + Get.offAllNamed(Routes.LOGIN); + } + } else { + Get.offAllNamed(Routes.NAVBAR); + } + } catch (e) { + print('Error checking user data: $e'); + await _auth.signOut(); + Get.offAllNamed(Routes.LOGIN); + } } else { - // User belum login, ke halaman login Get.offAllNamed(Routes.LOGIN); } } -} \ No newline at end of file +} diff --git a/lib/app/modules/splash-screen/views/splash_screen_view.dart b/lib/app/modules/splash-screen/views/splash_screen_view.dart index 9a7d03a..f63da97 100644 --- a/lib/app/modules/splash-screen/views/splash_screen_view.dart +++ b/lib/app/modules/splash-screen/views/splash_screen_view.dart @@ -37,7 +37,7 @@ class SplashScreenView extends GetView { ), const SizedBox(height: 30), const Text( - 'Smart Infuse', + 'SMART INFUSE', style: TextStyle( color: Colors.white, fontSize: 32, diff --git a/lib/app/services/notification_service.dart b/lib/app/services/notification_service.dart index e69de29..7058d81 100644 --- a/lib/app/services/notification_service.dart +++ b/lib/app/services/notification_service.dart @@ -0,0 +1,261 @@ +// lib/app/services/notification_service.dart +import 'package:flutter/material.dart'; +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import '../models/notification_model.dart' as local; + +class NotificationService { + static final NotificationService _instance = NotificationService._internal(); + factory NotificationService() => _instance; + NotificationService._internal(); + + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final Map _lastNotificationTime = {}; + final Duration _notificationCooldown = const Duration(minutes: 5); + + Future initialize() async { + await AwesomeNotifications().initialize(null, [ + NotificationChannel( + channelKey: 'infus_alert_channel', + channelName: 'Infus Alerts', + channelDescription: 'Alerts for infusion monitoring', + defaultColor: const Color(0xFF9D50DD), + ledColor: Colors.white, + importance: NotificationImportance.High, + channelShowBadge: true, + playSound: true, + enableVibration: true, + ), + ]); + + await _requestPermissions(); + _setupNotificationListeners(); + } + + Future _requestPermissions() async { + await AwesomeNotifications().isNotificationAllowed().then(( + isAllowed, + ) async { + if (!isAllowed) { + await AwesomeNotifications().requestPermissionToSendNotifications(); + } + }); + } + + void _setupNotificationListeners() { + AwesomeNotifications().setListeners( + onActionReceivedMethod: _onNotificationTapped, + onNotificationCreatedMethod: _onNotificationCreated, + onNotificationDisplayedMethod: _onNotificationDisplayed, + onDismissActionReceivedMethod: _onNotificationDismissed, + ); + } + + @pragma("vm:entry-point") + static Future _onNotificationCreated( + ReceivedNotification receivedNotification, + ) async { + print('Notification created: ${receivedNotification.id}'); + } + + @pragma("vm:entry-point") + static Future _onNotificationDisplayed( + ReceivedNotification receivedNotification, + ) async { + print('Notification displayed: ${receivedNotification.id}'); + } + + @pragma("vm:entry-point") + static Future _onNotificationTapped( + ReceivedAction receivedAction, + ) async { + print('Notification tapped: ${receivedAction.payload}'); + } + + @pragma("vm:entry-point") + static Future _onNotificationDismissed( + ReceivedAction receivedAction, + ) async { + print('Notification dismissed: ${receivedAction.id}'); + } + + Future showDropRateAlert({ + required String patientId, + required String patientName, + required String deviceId, + required String roomName, + }) async { + if (_isInCooldown(deviceId)) { + return; + } + + final title = 'Peringatan Infus ⚠️'; + final message = + 'Infus $patientName di ruang $roomName telah berhenti menetes! Segera lakukan Tindakan!'; + + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: deviceId.hashCode, + channelKey: 'infus_alert_channel', + title: title, + body: message, + notificationLayout: NotificationLayout.Default, + payload: { + 'device_id': deviceId, + 'patient_id': patientId, + 'patient_name': patientName, + 'room_name': roomName, + }, + category: NotificationCategory.Alarm, + wakeUpScreen: true, + fullScreenIntent: true, + autoDismissible: false, + backgroundColor: Colors.red, + color: Colors.red, + ), + actionButtons: [ + NotificationActionButton( + key: 'VIEW', + label: 'Lihat Detail', + actionType: ActionType.Default, + ), + NotificationActionButton( + key: 'DISMISS', + label: 'Tutup', + actionType: ActionType.DismissAction, + isDangerousOption: true, + ), + ], + ); + + await _saveNotificationToFirestore( + patientId: patientId, + deviceId: deviceId, + title: title, + message: message, + ); + + _lastNotificationTime[deviceId] = DateTime.now(); + print('Notification sent for device: $deviceId'); + } + + Future showDropRateAlertForPatient({ + required String patientId, + required String patientName, + required String deviceId, + required String roomName, + }) async { + if (_isInCooldown(deviceId)) { + return; + } + + final title = 'Peringatan Infus ⚠️'; + final message = + 'Infus $patientName di ruang $roomName telah berhenti menetes! Segera hubungi Perawat!'; + + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: deviceId.hashCode, + channelKey: 'infus_alert_channel', + title: title, + body: message, + notificationLayout: NotificationLayout.Default, + payload: { + 'device_id': deviceId, + 'patient_id': patientId, + 'patient_name': patientName, + 'room_name': roomName, + }, + category: NotificationCategory.Alarm, + wakeUpScreen: true, + fullScreenIntent: true, + autoDismissible: false, + backgroundColor: Colors.red, + color: Colors.red, + ), + actionButtons: [ + NotificationActionButton( + key: 'CALL', + label: 'Hubungi Perawat', + actionType: ActionType.Default, + ), + NotificationActionButton( + key: 'DISMISS', + label: 'Tutup', + actionType: ActionType.DismissAction, + isDangerousOption: true, + ), + ], + ); + + await _saveNotificationToFirestore( + patientId: patientId, + deviceId: deviceId, + title: title, + message: message, + ); + + _lastNotificationTime[deviceId] = DateTime.now(); + print('Notification sent for patient device: $deviceId'); + } + + Future _saveNotificationToFirestore({ + required String patientId, + required String deviceId, + required String title, + required String message, + }) async { + try { + final notification = local.NotificationModel( + id: '', + patientId: patientId, + deviceId: deviceId, + title: title, + message: message, + isRead: false, + createdAt: DateTime.now(), + ); + + await _firestore + .collection('notifications') + .add(notification.toFirestore()); + print('Notification saved to Firestore'); + } catch (e) { + print('Error saving notification to Firestore: $e'); + } + } + + bool _isInCooldown(String deviceId) { + if (!_lastNotificationTime.containsKey(deviceId)) { + return false; + } + + final lastTime = _lastNotificationTime[deviceId]!; + final difference = DateTime.now().difference(lastTime); + return difference < _notificationCooldown; + } + + void clearCooldown(String deviceId) { + _lastNotificationTime.remove(deviceId); + } + + void clearAllCooldowns() { + _lastNotificationTime.clear(); + } + + Future cancelNotification(int id) async { + await AwesomeNotifications().cancel(id); + } + + Future cancelNotificationByDeviceId(String deviceId) async { + await AwesomeNotifications().cancel(deviceId.hashCode); + } + + Future cancelAllNotifications() async { + await AwesomeNotifications().cancelAll(); + } + + void dispose() { + _lastNotificationTime.clear(); + } +} diff --git a/lib/app/services/realtime_database_service.dart b/lib/app/services/realtime_database_service.dart index 2252e72..6504eb8 100644 --- a/lib/app/services/realtime_database_service.dart +++ b/lib/app/services/realtime_database_service.dart @@ -9,10 +9,9 @@ class RealtimeDatabaseService { Stream> getRoomDevicesStream( String roomId, ) { - return _database - .child('realtime/rooms/$roomId/devices') - .onValue - .map((event) { + return _database.child('realtime/rooms/$roomId/devices').onValue.map(( + event, + ) { final Map devices = {}; if (event.snapshot.value != null) { @@ -40,12 +39,16 @@ class RealtimeDatabaseService { .child('realtime/rooms/$roomId/devices/$deviceId') .onValue .map((event) { - if (event.snapshot.value != null) { - final data = event.snapshot.value as Map; - return RealtimeMonitoringModel.fromRealtimeDB(deviceId, roomId, data); - } - return null; - }); + if (event.snapshot.value != null) { + final data = event.snapshot.value as Map; + return RealtimeMonitoringModel.fromRealtimeDB( + deviceId, + roomId, + data, + ); + } + return null; + }); } Stream> getAllDevicesStream() { @@ -83,8 +86,9 @@ class RealtimeDatabaseService { String deviceId, ) async { try { - final snapshot = - await _database.child('realtime/rooms/$roomId/devices/$deviceId').get(); + final snapshot = await _database + .child('realtime/rooms/$roomId/devices/$deviceId') + .get(); if (snapshot.value != null) { final data = snapshot.value as Map; @@ -102,9 +106,9 @@ class RealtimeDatabaseService { await _database .child('realtime/rooms/$roomId/devices/$deviceId/controlling') .update({ - 'servo_open': open, - 'last_command': DateTime.now().toIso8601String(), - }); + 'servo_open': open, + 'last_command': DateTime.now().toIso8601String(), + }); } Future updateDropRate( @@ -115,9 +119,9 @@ class RealtimeDatabaseService { await _database .child('realtime/rooms/$roomId/devices/$deviceId/monitoring') .update({ - 'drop_rate': dropRate, - 'last_update': DateTime.now().toIso8601String(), - }); + 'drop_rate': dropRate, + 'last_update': DateTime.now().toIso8601String(), + }); } Future updateDeviceStatus( @@ -128,9 +132,9 @@ class RealtimeDatabaseService { await _database .child('realtime/rooms/$roomId/devices/$deviceId/monitoring') .update({ - 'device_status': status, - 'last_update': DateTime.now().toIso8601String(), - }); + 'device_status': status, + 'last_update': DateTime.now().toIso8601String(), + }); } Future updateServoSettings( @@ -142,9 +146,9 @@ class RealtimeDatabaseService { await _database .child('realtime/rooms/$roomId/devices/$deviceId/settings') .update({ - 'servo_angle_open': angleOpen, - 'servo_angle_close': angleClose, - }); + 'servo_angle_open': angleOpen, + 'servo_angle_close': angleClose, + }); } // ==================== UTILITY ==================== @@ -158,11 +162,25 @@ class RealtimeDatabaseService { } } + Future controlBuzzer( + String roomId, + String deviceId, + bool active, + ) async { + await _database + .child('realtime/rooms/$roomId/devices/$deviceId/controlling') + .update({ + 'buzzer_active': active, + 'last_command': DateTime.now().toIso8601String(), + }); + } + Future getDeviceDropRate(String roomId, String deviceId) async { try { final snapshot = await _database .child( - 'realtime/rooms/$roomId/devices/$deviceId/monitoring/drop_rate') + 'realtime/rooms/$roomId/devices/$deviceId/monitoring/drop_rate', + ) .get(); if (snapshot.value != null) { @@ -173,4 +191,4 @@ class RealtimeDatabaseService { return 0.0; } } -} \ No newline at end of file +} diff --git a/lib/app/widgets/history_grafik_card.dart b/lib/app/widgets/history_grafik_card.dart index 0881a70..f0cf244 100644 --- a/lib/app/widgets/history_grafik_card.dart +++ b/lib/app/widgets/history_grafik_card.dart @@ -37,11 +37,10 @@ class HistoryGrafikCard extends StatelessWidget { ); } - final hours = dataPoints.map((p) => p.hour.toDouble()).toList(); final drops = dataPoints.map((p) => p.dropsPerMinute.toDouble()).toList(); - final minX = hours.reduce((a, b) => a < b ? a : b); - final maxX = hours.reduce((a, b) => a > b ? a : b); + final minX = 0.0; + final maxX = (dataPoints.length - 1).toDouble(); final minY = (drops.reduce((a, b) => a < b ? a : b) - 5.0).clamp( 0.0, double.infinity, @@ -113,10 +112,14 @@ class HistoryGrafikCard extends StatelessWidget { reservedSize: 30, interval: dataPoints.length > 5 ? 2.0 : 1.0, getTitlesWidget: (double value, TitleMeta meta) { + final index = value.toInt(); + if (index < 0 || index >= dataPoints.length) { + return const SizedBox.shrink(); + } return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - '${value.toInt()}:00', + dataPoints[index].timeLabel, style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.w500, @@ -158,14 +161,12 @@ class HistoryGrafikCard extends StatelessWidget { maxY: maxY, lineBarsData: [ LineChartBarData( - spots: dataPoints - .map( - (point) => FlSpot( - point.hour.toDouble(), - point.dropsPerMinute.toDouble(), - ), - ) - .toList(), + spots: List.generate(dataPoints.length, (index) { + return FlSpot( + index.toDouble(), + dataPoints[index].dropsPerMinute.toDouble(), + ); + }), isCurved: true, gradient: const LinearGradient( colors: [Color(0xFF2196F3), Color(0xFF1976D2)], @@ -208,8 +209,12 @@ class HistoryGrafikCard extends StatelessWidget { tooltipMargin: 8, getTooltipItems: (List touchedBarSpots) { return touchedBarSpots.map((barSpot) { + final index = barSpot.x.toInt(); + final label = (index >= 0 && index < dataPoints.length) + ? dataPoints[index].timeLabel + : ''; return LineTooltipItem( - '${barSpot.y.toInt()} tpm\n${barSpot.x.toInt()}:00', + '${barSpot.y.toInt()} tpm\n$label', const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, diff --git a/lib/app/widgets/modal_schedule_add_edit.dart b/lib/app/widgets/modal_schedule_add_edit.dart index 9078a36..6df73a9 100644 --- a/lib/app/widgets/modal_schedule_add_edit.dart +++ b/lib/app/widgets/modal_schedule_add_edit.dart @@ -30,13 +30,17 @@ class _ModalScheduleAddEditState extends State { @override void initState() { super.initState(); - _medicineController = TextEditingController(text: widget.schedule?.medicineTime ?? ''); - _fluidController = TextEditingController(text: widget.schedule?.fluidTime ?? ''); + _medicineController = TextEditingController( + text: widget.schedule?.medicineTime ?? '', + ); + _fluidController = TextEditingController( + text: widget.schedule?.fluidTime ?? '', + ); _selectedDate = DateTime.now(); if (widget.isEdit && widget.schedule != null) { - _selectedPatientId = widget.schedule!.id; _selectedTime = widget.schedule!.timeOfDay; + // _selectedPatientId akan diset setelah _loadPatients() selesai } _loadPatients(); @@ -46,11 +50,16 @@ class _ModalScheduleAddEditState extends State { try { final controller = Get.find(); final patientsList = await controller.getPatientsList(); - + if (mounted) { setState(() { _patients = patientsList; _isLoadingPatients = false; + + // Set selected patient ID untuk mode edit + if (widget.isEdit && widget.schedule != null) { + _selectedPatientId = widget.schedule!.patientId; + } }); } } catch (e) { @@ -101,7 +110,9 @@ class _ModalScheduleAddEditState extends State { ? const Center( child: Padding( padding: EdgeInsets.all(40), - child: CircularProgressIndicator(color: Color(0xFF0091EA)), + child: CircularProgressIndicator( + color: Color(0xFF0091EA), + ), ), ) : SingleChildScrollView( @@ -140,7 +151,10 @@ class _ModalScheduleAddEditState extends State { }, ), const SizedBox(height: 20), - _buildLabel('Detail Cairan & Tetes', Icons.water_drop), + _buildLabel( + 'Detail Cairan & Tetes', + Icons.water_drop, + ), const SizedBox(height: 8), _buildTextField( controller: _fluidController, @@ -168,7 +182,9 @@ class _ModalScheduleAddEditState extends State { return Container( padding: const EdgeInsets.all(24), decoration: const BoxDecoration( - gradient: LinearGradient(colors: [Color(0xFF0091EA), Color(0xFF0277BD)]), + gradient: LinearGradient( + colors: [Color(0xFF0091EA), Color(0xFF0277BD)], + ), borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), @@ -182,7 +198,11 @@ class _ModalScheduleAddEditState extends State { color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), - child: const Icon(Icons.calendar_today, color: Colors.white, size: 24), + child: const Icon( + Icons.calendar_today, + color: Colors.white, + size: 24, + ), ), const SizedBox(width: 16), Expanded( @@ -258,16 +278,25 @@ class _ModalScheduleAddEditState extends State { ), child: Row( children: [ - const Icon(Icons.calendar_today, size: 18, color: Color(0xFF0091EA)), + const Icon( + Icons.calendar_today, + size: 18, + color: Color(0xFF0091EA), + ), const SizedBox(width: 12), Text( _selectedDate != null - ? DateFormat('EEEE, dd MMMM yyyy', 'id_ID').format(_selectedDate!) + ? DateFormat( + 'EEEE, dd MMMM yyyy', + 'id_ID', + ).format(_selectedDate!) : 'Pilih tanggal...', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: _selectedDate != null ? const Color(0xFF424242) : Colors.grey[400], + color: _selectedDate != null + ? const Color(0xFF424242) + : Colors.grey[400], ), ), const Spacer(), @@ -296,6 +325,7 @@ class _ModalScheduleAddEditState extends State { return DropdownButtonFormField( value: _selectedPatientId, + dropdownColor: Colors.white, decoration: InputDecoration( hintText: 'Cari dan pilih pasien...', hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14), @@ -317,7 +347,10 @@ class _ModalScheduleAddEditState extends State { borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.redAccent), ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), ), icon: const Icon(Icons.arrow_drop_down, color: Color(0xFF0091EA)), isExpanded: true, @@ -334,11 +367,13 @@ class _ModalScheduleAddEditState extends State { ), ); }).toList(), - onChanged: (value) { - setState(() { - _selectedPatientId = value; - }); - }, + onChanged: widget.isEdit + ? null + : (value) { + setState(() { + _selectedPatientId = value; + }); + }, validator: (value) { if (value == null) { return 'Pilih pasien terlebih dahulu'; @@ -388,7 +423,10 @@ class _ModalScheduleAddEditState extends State { Row( children: [ Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), decoration: BoxDecoration( color: const Color(0xFFE3F2FD), borderRadius: BorderRadius.circular(6), @@ -404,7 +442,10 @@ class _ModalScheduleAddEditState extends State { ), const SizedBox(width: 8), Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(6), @@ -445,6 +486,7 @@ class _ModalScheduleAddEditState extends State { Widget _buildTimeDropdown() { return DropdownButtonFormField( value: _selectedTime, + dropdownColor: Colors.white, decoration: InputDecoration( hintText: 'Pilih waktu...', hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14), @@ -466,7 +508,10 @@ class _ModalScheduleAddEditState extends State { borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.redAccent), ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), ), icon: const Icon(Icons.arrow_drop_down, color: Color(0xFF0091EA)), isExpanded: true, @@ -561,7 +606,10 @@ class _ModalScheduleAddEditState extends State { borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.redAccent, width: 2), ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), ), ); } @@ -584,7 +632,9 @@ class _ModalScheduleAddEditState extends State { style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), side: BorderSide(color: Colors.grey.shade300, width: 2), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), child: Text( 'Batal', @@ -618,7 +668,9 @@ class _ModalScheduleAddEditState extends State { backgroundColor: Colors.transparent, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), child: Text( widget.isEdit ? 'Simpan' : 'Tambah', @@ -649,19 +701,25 @@ class _ModalScheduleAddEditState extends State { } final controller = Get.find(); + final patientData = _selectedPatientData!; final schedule = PatientSchedule( - id: _selectedPatientId!, - name: _selectedPatientData!['name']!, - room: _selectedPatientData!['room']!, - deviceId: _selectedPatientData!['device']!, + id: widget.isEdit ? widget.schedule!.id : '', + patientId: _selectedPatientId!, + name: patientData['name']!, + room: patientData['room']!, + deviceId: patientData['device']!, medicineTime: _medicineController.text.trim(), fluidTime: _fluidController.text.trim(), timeOfDay: _selectedTime!, ); if (widget.isEdit && widget.schedule != null) { - controller.updateSchedule(widget.schedule!.id, schedule, _selectedDate!); + controller.updateSchedule( + widget.schedule!.id, + schedule, + _selectedDate!, + ); } else { controller.addSchedule(schedule, _selectedDate!); } @@ -669,4 +727,4 @@ class _ModalScheduleAddEditState extends State { Get.back(); } } -} \ No newline at end of file +} diff --git a/lib/app/widgets/monitoring_card.dart b/lib/app/widgets/monitoring_card.dart index 0dd9e62..16121e4 100644 --- a/lib/app/widgets/monitoring_card.dart +++ b/lib/app/widgets/monitoring_card.dart @@ -5,11 +5,7 @@ class MonitoringCard extends StatelessWidget { final InfusMonitoring monitoring; final VoidCallback? onTap; - const MonitoringCard({ - super.key, - required this.monitoring, - this.onTap, - }); + const MonitoringCard({super.key, required this.monitoring, this.onTap}); @override Widget build(BuildContext context) { @@ -158,11 +154,7 @@ class MonitoringCard extends StatelessWidget { const Color(0xFFE3F2FD), const Color(0xFF2196F3), ), - _buildBadge( - monitoring.room, - Colors.grey[100]!, - Colors.grey[700]!, - ), + _buildBadge(monitoring.room, Colors.grey[100]!, Colors.grey[700]!), _buildBadge( isConnected ? 'Online' : 'Offline', isConnected ? const Color(0xFFE8F5E9) : Colors.red[50]!, @@ -196,4 +188,4 @@ class MonitoringCard extends StatelessWidget { if (diff.inHours < 24) return '${diff.inHours}h ago'; return '${diff.inDays}d ago'; } -} \ No newline at end of file +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bf..4c2edbf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) awesome_notifications_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AwesomeNotificationsPlugin"); + awesome_notifications_plugin_register_with_registrar(awesome_notifications_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f16b4c3..1845e78 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + awesome_notifications url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 21dd2cc..61ac1ff 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import awesome_notifications import cloud_firestore import firebase_auth import firebase_core @@ -13,6 +14,7 @@ import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AwesomeNotificationsPlugin.register(with: registry.registrar(forPlugin: "AwesomeNotificationsPlugin")) FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 9ccebb0..868e390 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + awesome_notifications: + dependency: "direct main" + description: + name: awesome_notifications + sha256: "0d5fa4457f2ba4e536adc3ef6af709cdcecf4a05a1f3035981e9afa2f899b2a8" + url: "https://pub.dev" + source: hosted + version: "0.10.1" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0a25aaf..d17e8ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: firebase_auth: ^6.1.4 firebase_database: ^12.1.2 cloud_firestore: ^6.1.2 + awesome_notifications: ^0.10.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index a3c4d16..0922b2d 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AwesomeNotificationsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AwesomeNotificationsPluginCApi")); CloudFirestorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); FirebaseAuthPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index c215e65..43e6e66 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + awesome_notifications cloud_firestore firebase_auth firebase_core