From 802a80a5499db8feaeb65a2ba7eccb7f19c7f138 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 5 Mar 2025 18:04:55 +0530 Subject: [PATCH] Nottification and charts changes added --- android/app/build.gradle | 17 +- android/app/google-services.json | 29 + android/app/src/main/AndroidManifest.xml | 2 +- android/build.gradle | 11 + android/settings.gradle | 2 +- assets/FCSC-GIF.gif | Bin 0 -> 11556 bytes .../backgrounds/Notification/App-Update.png | Bin 12895 -> 19961 bytes .../Notification/Update_notific.png | Bin 18486 -> 14162 bytes lib/config/api_config.dart | 3 +- lib/config/loading_overlay.dart | 36 + lib/config/my_router.dart | 19 +- lib/l10n/intl_ar.arb | 1 + lib/l10n/intl_en.arb | 1 + lib/main.dart | 53 +- .../UAE_Numbers/uae_numbers.dart | 15 +- .../Screens/charts/screens/chart_screen.dart | 340 ++-- .../Screens/charts/services/api_service.dart | 15 +- .../Screens/charts/widgets/chart_widget.dart | 1512 ++++++++++++----- lib/presentation/Screens/profilepage.dart | 267 +-- .../components/constant/constant.dart | 6 +- .../routes/auth_routes/login_route.dart | 199 ++- .../tab_routes/home_route.dart | 6 +- .../Drawer Items/edit_profile.dart | 44 +- .../Drawer Items/manage_users.dart | 12 +- .../notification/notification.dart | 167 +- .../notification/notification_details.dart | 86 + macos/Flutter/GeneratedPluginRegistrant.swift | 8 +- pubspec.lock | 383 +++-- pubspec.yaml | 12 +- 29 files changed, 2268 insertions(+), 978 deletions(-) create mode 100644 android/app/google-services.json create mode 100644 assets/FCSC-GIF.gif create mode 100644 lib/config/loading_overlay.dart create mode 100644 lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 64443c26..20b03edd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,6 +3,7 @@ plugins { id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" + id "com.google.gms.google-services" } def localProperties = new Properties() @@ -13,14 +14,18 @@ if (localPropertiesFile.exists()) { } } +if (project.hasProperty('google-services.json')) { + apply plugin: 'com.google.gms.google-services' +} + def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "28" + flutterVersionCode = "31" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.27" + flutterVersionName = "1.0.30" } def keystorePropertiesFile = rootProject.file("key.properties") @@ -46,7 +51,7 @@ android { applicationId = "ae.gov.fcsc.frontend" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdk = flutter.minSdkVersion + minSdk = 23 targetSdk = flutter.targetSdkVersion versionCode = flutterVersionCode.toInteger() versionName = flutterVersionName @@ -75,3 +80,9 @@ android { flutter { source = "../.." } + +dependencies { + implementation(platform("com.google.firebase:firebase-bom:33.1.2")) + implementation 'com.google.firebase:firebase-messaging:22.0.0' + implementation("com.google.firebase:firebase-auth:23.0.0") +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..2dfa060b --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "62132243005", + "project_id": "fcsc-a161c", + "storage_bucket": "fcsc-a161c.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:62132243005:android:18b7ca94608afaeb7c7854", + "android_client_info": { + "package_name": "ae.gov.fcsc.frontend" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyB-QQbnq366f7rrKLVVFJKnSZgR9LcDWFI" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a570820b..ee29d442 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/android/build.gradle b/android/build.gradle index 8e9de9d8..0f6859f8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,14 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.1.0' // Update this if needed + classpath 'com.google.gms:google-services:4.3.10' // ✅ Required for Firebase + } +} + allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 2db302b0..f01fbe2d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -20,7 +20,7 @@ plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.2.1" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false - + id("com.google.gms.google-services") version "4.4.2" apply false } include ":app" diff --git a/assets/FCSC-GIF.gif b/assets/FCSC-GIF.gif new file mode 100644 index 0000000000000000000000000000000000000000..46e243b0dffeb06ca6ee39b4f681b6c97994e12c GIT binary patch literal 11556 zcmeHtd03NY_V$}M3)vtGAq3eHHUk6*h!`lvHz7bkz_18m5d#9EfCQBmEVW5UNC3r% zh^SZtMHWR22#6i4A%Mk&iq;}ktANx}wVha<4lXnE#nyI4hwr++KYssxzw>93>zs3+ zb3gZU&T}OqGMp2bNe0Nk-+|vu8cZM%h(w~Jqa)qk-ka&}_?H3&`^YB+G%)Ks1erk@0XSGdv8A$KmiSTYPB{ zK_wtc!-(og%XJ(|ls64T(abGqTi&w)QMT4JTj?st-HCLQY1X*FF)pRiu67Qt`9Vtw zmP?sSy=&~Uc(%!eE7{qB?8Z!A1l~70&{r;6W?>azOAlmv2Cn7@BMHH4Iq$FK ztPBiVd87c+7KS>y@`K+OpvVG~C9Q}=k)!fNG4h;PqHS!0WOaL)&}1Fk8yVAvcfWywTNnV_ES=SxG+E zzW}qOVCC|H{ht(KmJ}{uS=3%#bb61(WP^1d_`LUFkpGA4b3ZKouoP=wTCt-HYhM=V z{}G2@ZsYOE`NLBErwSZh;kT@^q_k37r?T}{sbn=9i*_bw>}L7x{_>ax@2tt%q%qmp z@jZCwJ)VAhb%*!Sy!RQ7H=J(Uzx|W_yVM8b*EM-_51YFkcJV#z>U-qsfYxMJPq(-P zeHNYiSx#Zwsjf~7_fC8EvEbERtoKjY`k%aM(3xy%xMz7}&&L)01kZi~tDoxAZ|&3X z7BZk|7|bg<(|q(yU+<7DXUI2d#NoYB$M;6V)4%kJxab{uu~>Sk_4t+GHRJCmU5#FU zbywrnA8t>q%DVo^uCLQ1H&TlxOVyJn`flF%dTL|&)TWQ`lvdp-tG@TwAEwp&XHTD> z9lbQy&^oujb^d72d|U6MJNF*PjoWjw=^t*~mPgOqwoS4%$j2vV`-Y9V-s_7BeSZHM zV7+$t`b+`n{eVw7O`y{b5x|sI(HI+9qrzJE(i%bOXblh*%qMBrW2K2=u*y9J6J1@mj*Vj@fBvR}mDH6tPjnit0tr}$ zQU?7uU(Pg43|2R9dtXbc( zo5+gNHi_iQ(Cabd8A^SL&C8spWhd6j+AWYR{(tWS z28z}&!oPp+f1AS%NpF@eu|f`jce^&ign$&&YcW3Bm;2;LME%GORCE4l!Gsg=@r9kzW; zo-F??aUWkyutAYVFV!-=>rqcVk%?8Q_GE8yUnb@-2nXM^7WJPM_z^HiXE(EyFe*Al zJ1rJ!L*WdeU3&9oI|I3%(^xvV`LJR8x8XbIzcg3@fSp6-{EArZ7>WAv;hjWz7&h%e zcrc~RT-m?3TgZI+tKaQ`!m03rLuOB0kZ|{hm z(S2yU$$eeJ^%gPNeuHBtAcU(!5T7PR0V)A8Wv>j(8DDE4ke zZHh#g3*7w-(89;aj|b^BM2Ap3tPqHy!{yq?EwYL8!jZd&o6ou*blQHn2L!k-9J`N20E|avbhRQm75wKGUYB9^eqm8>s+jiRzq3 z15*scIfj5%KG&O>W(hDUx?ElTWg_bKx#ib1VVA*Sbx5@DbGI6TcUg7F%S`R^%P<#G ze+u384(PV&qD|6A1Kj+=npEdKSpn50f3*D_;?J0Xi-%hDpXWLUj74#U+T}9vX39&k z+@jLNZgZD~p&7H%0|Eh-NDq~f9tbJ?o^Cph0swkFX}V&C?Ay7(Z7EK?85 zY3FMZ`>c!@XccDH(i%g4mBE6q2D%D|OHVlB^4%T2DJ?E>SC!rL+fpCq2cfIFUjXzt z?LCg1HQjGtxnEpO0blmg$Xd$XURgr9M4Z*=QfYp zN-LuBn;G$drgKCudz2Vn*>ObyQ5+q1grrk5elAHa`TO4r%}eK`i?w?mz%{btv_K7+ zC9H4!U>72o!opI~LoH4!s!O~kAz)S{jj%MG={;HYmVnn51X@xsqKL4H(Uv}AO7Q?^ z-D9*gQ(+c_!s*k>LY=;{Iof7bpWFjr*f7aiHde|%FaMU_yFhFi8OD)u?7>v=2&cl) zoA?`2PK5Ejj-*1YIg;5-8~RYOf|BZ7xi&&#cW=8VF~HpR1Lc_=R$qJPrt5y5 zyjX93%av5$i!omk(p53o?U!TDfl>4uXDgs9m*U$U*w^`LuqA;Koq5|DiBO5|1niQJ zs0gm0^>hsTcddiJt#v;GIGvc(qG(#jj1oebCr0!j5!r%{GZESr02;-x)hX|n6H-K; zK)7n>PT&f@;>)ZNe+oRNYrV_>8`1MUVV z;(q07LuD)uEjx~9X0gQEq8KWkU|?!dK4=6mErgMgW_3*VwOayUS!EB@(*u~PP%#_? z@ZCuvvkW_4J@N>=|ZGTJP z=S%{Rm+|N|74A{yFI{!bG74jfLw1}K1hC!gmgp|PE8Vf^fI{A_R?j~iHiF8Vsq_$9^~+C zB@x7j&R9!lYyT0RN1b{+>qDN*$oP5C=g@=g?v*)-ugLnW3;!56aKEZA6Ki>Hrvqg$ zODQADmN)3A>GbLYe@NUreEJ_lh&Krpzd2arF|Wq@{`s z<9Vbd)A`t1q+1e`4UYh*Myx3G067dN12QDyp05xwR67e0AkNawj*zXU5l|?-3e2-8 zzN1dKP)lLO*VC#K>BuJE4-op)Y0qmFzBoYV{6PVh0w6vJ8)C>jOBryj@JmLww#qD7 z3(1PW%Jt6OXR2PpjMc@?oLcc@LIsr~9S{3pKGVQ%v<$VLU(vEnmQPb47T6Q1m@e~2 z`y5n0KH)MZ?+RtmV7pS!s=wwEIXlN~bipAOrTLElPGJ&v=@959C|@8N_|+B25Q+l5 zC(NK^IK{S#q}m9%yP%*|Fv0gqVF(-93{6fL^$}tUO_bizmqc?keA$KRo%Ze_`cSyu zYLud~06AhqvLgVLuL(X%@i21)uze!4xOQ%bPe`LQRe$ROc}@Ofh%4v9%n^ix61YCX z&K^0hTaX%((PY>!HO&{vNPW zCy*HwRVA*xa;9*)Vps**L^X|Rlwma_n>Ov3Z&-M~6^A-F*22J9k^n_(X-^{Ba+1S_ z0g%lqDl_&+G?5M)2UiQD`$SG$nGLM|)#FpOS=eODqWbONNOE-@`KE6pTC4Q5aAUD%e*tDIdW#}6m3bsV{=EG%q1fA@lv!wQT#AHw0z7Ms*aBW z@#kK=nPcDK`jy{iZi!e{fhJ@K#&k171bobb-;8l)sN~!o0Fbw2$p|Xb^=%qEk5{uq z?y%nM`f@D6RHvqa)z z$~|aOZoAh@MFkqyh;EJ^?hMUKcV%f|(oqoT(yv;O!MUEUZae7QAuAPuNo-jV{{*ku z??dWku7T96sF-yPXrZ({nqrKuS8sKe*2oo?xW2O}OTxx}j%Z2-ft74Q@$9O`L-tHG27 zC$!!?cqdY%nUHwM?ai4`W%f{=>jjEyNC!)FTv~qse$i&`^ob*Sl~=+9MkrS4%Wj4j zq{V^K!wKhj^|wgch)(dAaMR^_b;$_L5BF?1KqnvePV}CseZDmcKfk6{DZ|!`F z$VeLkN873a-W~gn&5zFZi?>Z^2R9q_#m;AY22-_TK^E?e?Y41Vz%}x+K+aw_7iql? z{^ug`)+l|}1nQZUtU_Obw(MOn4T_9)u0of@QU*hcc6Y;OH^aucDx?4A>>&eRo$1Oy zc0)b8zkeTQl@)P|=E&d!p1tQF;QHoM0X^j$TcvpK$WuBRmY1vxIhaX;N~H!gIi?@$c57J1ddA zS24^Y{uk$mu~UE;I2K+!8|A>t5oVtvO_tkR{X)+CT}R#-kw;8`UP)lmLz$qZN8;FQ zA`FQ3t``YNNR1L(9~V7lK$xunVcEQ%J&c+XMs$68q_4uo9P_gCP_iGg5rkpKZU9GdBwBWi`6np(&M`NI6p8E=Jw z41VO2?kl+yN*e&W6d*z~kaP!CybE1!(-=$4*7!7#gpo>)I~ zmh`s)kFCc)?BHce2l=))-U^Z!jHRdFT08%uoX-r zo1VM>x-Yr1)`@A=EpFzp0o#DjYVA5)rU2LrNS=sSqajT;Tc#`Lv<1%Wo99yxGA;;hTL)Qp_Qj4YL8z(8E5(t4iF32qGtX`mD_Pg;)BDy|E+3@YBApT!l$|<%;DN zELS!nw9L^WF5aXh7}-n2|`{K<3(q+LpeKE>N=K8Z<1|#`G5))Jjs^X*iHsSpksw;GF zUq`dNU+oxzU*^8}dHW6F0{5~>83}bXfj-rsT0(ANaxF%=We<&3qA$f-c_s`Lyr64L zcaHPGxUHx~?<4CMP$xJ-2VM%o?3gVl3(1QF)0{>woRp5rgouMgfu(H-Hu?oVngmT> z+m7s=Sq6i|)hc9Hp{K5HSbr2b;pQdIAK_4uOmjRbkv;)_KJc8S&(``3`V~{P0|f80 z-Jtu>30Gr|j38Oz9J7WSQs}?$ z81HcOi{EE)7DWh*Q1NU>WN>V1n!$2FsttHm1RP zfEwjG<%#q-{Sb5t#_*S1uAe#GKqGSklv{mo6qIXMQ_%Fi5u4?l+CR2_ao*-gi!W z%MDn!?L-?nZ>Adj_QslJY~zH5%-(F$R$Z6@BfG^Tpg`hizw8IfB}yajC~rs52I_o@ zz6~mJiR!WyZ0pb$gWp(jgnD|6^6PMl=F3?*bPWFy=*ix##2m+q(yfAchi@gok)2*1 z!g>FGW-7kb-#TNh%n^}#Dpku{Cwrfo>r_4?fjLzZzU8u^iu(*)O5WXI4YK+wIn-xy zX>5CqW#qPiV4d|ANf3{`-Fo4$K*jL)bO)yFbnMdhZrjI_o<@w~_4sHF;Y@HQNM|*Yp z_?4Pr1EwKwrMFlO`1mP zI+`vrPXmhnk$w>mv7q;_WQgH2i{6dYDx^Rh3PGm9ZmeD>2H!{l&Dtmpe7Cwz^~y$xy8-h zelWy=>8avolZsi_>i7FeS15l;y?~c`>D`fbQL&e)db@LI_)N(*D&$Zd3U{wR8{mU< zRk}tUb4JshFV(povv~GRT?JL?mWmQ^XE(MT&w@9i4-K41Q@*Jd;_ak1zfMY`zUcm% zhLlD~dpOfg#EKsDUJb;+ztc1_iO5q5usm|ELt=}qVg{d`Kogx+sx8KRXXi#|48L1Y0-!;dRPcGiD5>pC^ZQslL z39pB`K?|S`(aqC}1bZqV0p?|NE7J(?RbM9V0+PWQmG>)TU#gg;lLx3VcVWn#8n_(A|~c?w0>&%cXOn%V|b;C1|Pf@MqDRQ-;Kp~wf9a}=i8k^ zaJJRC`f|MBiGM*oiuejR8)8*Yt6MWf_2~SV!=6Zvt%TXVvdhal_RVq8pDW#4@RR=t zO<~3}qcbPyQpZmKyvsES9T33O+!w{oEn!*k&rTSb#CvMleE~KxLU-^I@NEXUe(6?i ze__`qlzudFgY?so_XlrmjG-fs2=;pX&1O--%J`$y6nDHy8`xN~+?9$FFfZA7a)9*6 zCK~W9l*zHS$mz2Vq9m3;&6HRH`dKx3qPuHB{aw^NT)K!kBORAYqj(+hG(|IL=@RboZu z0!Y97)E9bxZCq;Vuu#Jt4)HR3XfS4Lk5`Q^3|Y>*wd={@dp*V%hnM<0yw|Zz(w7&t z*BZbfx7#o@Q+;aW^RTWJ5%Wpgs8<@Bi zumi?<776R^{JQ<$j~s7BO#jb_>1vH7D^9b(#)~&=PghwZYeRkG=)J*S=`l_Ht?Pj} zgb#B>wS^8?Voz964ovK)%0s5T?Ed~^6?71@vhldeaMQ9D_woc$eWNvD@8VbHTUAY0 z6YJdgWs_|Dbm8=1)hufAkUB=YEHv7mna$8N?yvF$=Xrj{dc%&-cSM^68#@$@-4{ql zi@!4zsR4BGi151r%Wg!ySiSq+m3E=|;?nz0NaN~H12V8|ysl9EMU@ru*s3ApExo|3 zJcgf?+X7{0ppt}YL&_!DBg$+==!DEbSc2Ul%sY{KNS4a+Xj;)S(n)o)$-n~y<;8X( za`BLvZ?ImcvYEOr(4&Gg5;pmT3%Qq@`vJLN(8l z>+eqa%4L4ljM=(46v*x&9#L(o2ZUKuS8XFL`L0oiqu%Ug@0@?p+fj+q&`NkjsnHni zFhU{{Y8xjaZ1@BPsztCN_jJXm*wJ@5&s?k&BH45wMxkt zwxwGprv+X1o49Ep?Ls$*1Uu;f1)Ds_$Wvv0#s(0ZBoif288UH;_b-0K=MqVMKNA5~{@k2R=toignONFM`p7s`heYF=IrH` zm_N3Lcj~}f#Orx3o``gEktHo5+s?Pmgvmap1-Q1i8b8rJB~S|)Vi@5QE&`V7SDQI($YV~C53Ymv`?dn7tX6)Y5J zcUQKt0t!AWX86gi8I|RnEiD&ie_l+a|JZzyR$`ah?2Vu19qTT5bWXy$x~=dY$Kj8E h82!)x5&`_r`-J~L`~2VNz?*;f{cqp@f8hLk{|6=4hc5sC literal 0 HcmV?d00001 diff --git a/assets/backgrounds/Notification/App-Update.png b/assets/backgrounds/Notification/App-Update.png index 8767ad9b77de16f8f9d948a902ad21ec4766ff14..a19e94e998e602bab68ebdf25b881bcc1dfdccb1 100644 GIT binary patch literal 19961 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 Z{}&QNt*NbX)LH-l002ovPDHLkV1fv@lf(c3 literal 12895 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 JPDHLkV1iWOTR{K- diff --git a/assets/backgrounds/Notification/Update_notific.png b/assets/backgrounds/Notification/Update_notific.png index c772f7a6856c4ed72c55a0652aad194b08b8b846..e1b87b59c79785dcab099731a81de0110471e47c 100644 GIT binary patch literal 14162 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 _isLoading = ValueNotifier(false); + + static void show() { + _isLoading.value = true; + } + + static void hide() { + _isLoading.value = false; + } + + static Widget buildLoader() { + return ValueListenableBuilder( + valueListenable: _isLoading, + builder: (context, isLoading, child) { + return isLoading + ? Stack( + children: [ + ModalBarrier( + color: Colors.black.withOpacity(0.5), dismissible: false), + Center( + child: Image.asset( + "assets/images/loading.gif", // Make sure to add the GIF in assets + width: 100, + height: 100, + ), + ), + ], + ) + : const SizedBox.shrink(); + }, + ); + } +} diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 00f2c6a0..a003bdec 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -307,6 +307,7 @@ import 'package:uae_stat/presentation/Screens/charts/screens/chart_screen.dart'; import 'package:uae_stat/presentation/Screens/demo_home2.dart'; import 'package:uae_stat/presentation/Screens/online_offline_verification/internet_check.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/notification/notification.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/notification/notification_details.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/AboutTheApp/AboutFCSC.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/AboutTheApp/getStarted.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/KeyFeatures/AppFeatures.dart'; @@ -366,7 +367,6 @@ final GoRouter router = GoRouter( path: '/', //builder: (context, state) => LoginRoute(), builder: (context, state) => SessionCheckScreen(), - ), // GoRoute( // path: '/', @@ -426,8 +426,7 @@ final GoRouter router = GoRouter( state.uri.queryParameters['filter_data']!.isNotEmpty) { try { filterData = List>.from( - jsonDecode(state.uri.queryParameters['filter_data']!) - ); + jsonDecode(state.uri.queryParameters['filter_data']!)); } catch (e) { print('Error parsing filter_data: $e'); filterData = []; // Assign an empty list to prevent null issues @@ -436,8 +435,6 @@ final GoRouter router = GoRouter( filterData = []; // Ensure it's a valid list } - - print('Router dataSets: $dataSets'); print('Router bgColor: $bgColor'); print('Router mainTopic: $mainTopic'); @@ -521,6 +518,18 @@ final GoRouter router = GoRouter( return NotificationPage(); }, ), + GoRoute( + path: '/notification_details', + builder: (context, state) { + final Map data = state.extra as Map; + return NotificationDetails( + title: data['title'] ?? '', + message: data['message'] ?? '', + date: data['date'] ?? '', + category: data['category'] ?? '', + ); + }, + ), GoRoute( path: '/user-guide', builder: (context, state) => Userguide(), diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 6e680d81..0d4c6344 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -51,6 +51,7 @@ "full_name": "الاسم الكامل", "dob": "تاريخ الميلاد", "region": "الدولة/المنطقة", + "preferredLang": "اللغة المفضلة", "cancel": "إلغاء", "confirm": "تأكيد", "change_password": "تغيير كلمة المرور", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 10d131d5..1ca84b01 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -49,6 +49,7 @@ "full_name": "Full Name*", "dob": "Date of Birth*", "region": "Country/Region", + "preferredLang": "Preferred Language", "cancel": "Cancel", "confirm": "Confirm", "change_password": "Change Password", diff --git a/lib/main.dart b/lib/main.dart index a795ed32..31f051b6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:external_repos/external_repos.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,8 +18,6 @@ import 'package:uae_stat/presentation/Screens/auth_verification/registration.dar import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - - // void main() async { // WidgetsFlutterBinding.ensureInitialized(); // // await (await SharedPreferences.getInstance()).clear(); @@ -46,8 +46,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // } // } - - // void main() async { // WidgetsFlutterBinding.ensureInitialized(); // // await (await SharedPreferences.getInstance()).clear(); @@ -91,10 +89,31 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // } // } - +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // Handle background messages + print("Handling background message: ${message.messageId}"); +} void main() async { WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: const FirebaseOptions( + apiKey: 'AIzaSyB-QQbnq366f7rrKLVVFJKnSZgR9LcDWFI', + appId: '1:62132243005:android:18b7ca94608afaeb7c7854', + messagingSenderId: '62132243005', + projectId: 'fcsc-a161c')); + + FirebaseMessaging messaging = FirebaseMessaging.instance; + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + // Request permission for notifications + NotificationSettings settings = await messaging.requestPermission( + alert: true, + badge: true, + sound: true, + ); + + print('User granted permission: ${settings.authorizationStatus}'); configureDependencies(); runApp( const ProviderScope( @@ -106,9 +125,6 @@ void main() async { class MainApp extends ConsumerWidget { const MainApp({super.key}); - - - @override Widget build(BuildContext context, WidgetRef ref) { final locale = ref.watch(localeProvider); @@ -117,14 +133,13 @@ class MainApp extends ConsumerWidget { return true; // Replace with actual logic }); - - // ref.listen(connectivityProvider, (previous, hasInternet) { - // print('Previous Internet Status: $previous'); - // print('Current Internet Status: $hasInternet'); - // if (previous != null && hasInternet != previous) { - // handleConnectivityChange(context, hasInternet); - // } - // }); + ref.listen(connectivityProvider, (previous, hasInternet) { + print('Previous Internet Status: $previous'); + print('Current Internet Status: $hasInternet'); + if (previous != null && hasInternet != previous) { + handleConnectivityChange(context, hasInternet); + } + }); return MaterialApp.router( debugShowCheckedModeBanner: false, @@ -137,7 +152,8 @@ class MainApp extends ConsumerWidget { void handleConnectivityChange(BuildContext context, bool hasInternet) { //final currentPath = GoRouter.of(context).location; - final currentPath = GoRouter.of(context).routerDelegate.currentConfiguration.fullPath; + final currentPath = + GoRouter.of(context).routerDelegate.currentConfiguration.fullPath; if (!hasInternet && currentPath != '/internetcheck') { print("Status : Not connected"); context.go('/internetcheck'); @@ -147,6 +163,3 @@ class MainApp extends ConsumerWidget { } } } - - - diff --git a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart index 9f419cfa..b81677dd 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/UAE_Numbers/uae_numbers.dart @@ -12,7 +12,7 @@ import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes. import 'package:uae_stat/presentation/components/constant/constant.dart'; import 'package:http/http.dart' as http; import 'package:uae_stat/config/api_config.dart'; - +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class UaeNumbers extends StatelessWidget { const UaeNumbers({super.key}); @@ -198,14 +198,17 @@ class _UaenumberWidgetState extends ConsumerState { value); // Call filter function directly on input change }, decoration: InputDecoration( - hintText: "Search", - hintStyle: TextStyle(color: Color(0xFF898C81)), - prefixIconConstraints: BoxConstraints(maxWidth: 42, maxHeight: 42), + hintText: AppLocalizations.of(context)!.search, + hintStyle: TextStyle(color: Color(0xFFAA8E83)), + prefixIconConstraints: + BoxConstraints(maxWidth: 42, maxHeight: 42), prefixIcon: Container( - padding: EdgeInsets.only(right: 5), + padding: EdgeInsets.only(right: 5, left: 5), child: SvgPicture.asset( MiscIconAssetPath.Search, semanticsLabel: 'Search', + colorFilter: + ColorFilter.mode(Color(0xFFAA8E83), BlendMode.srcIn), ), ), @@ -215,7 +218,7 @@ class _UaenumberWidgetState extends ConsumerState { // ), border: InputBorder.none, contentPadding: - EdgeInsets.symmetric(vertical: 8.0, horizontal: 18.0), + EdgeInsets.symmetric(vertical: 0.5, horizontal: 18.0), ), ), ), diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 692800f1..da83f816 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -54,7 +54,7 @@ class ChartScreen1 extends ConsumerStatefulWidget { class _ChartScreen1State extends ConsumerState { // final _pb = PocketBase('https://pb.venbait.in'); final _pb = PocketBase(apiUrl); - List> selectedFiltersStorage = []; + final GlobalKey _scaffoldKey = GlobalKey(); final ApiService apiService = ApiService(); bool isLoading = true; @@ -68,6 +68,8 @@ class _ChartScreen1State extends ConsumerState { List tabFilteredCardData = []; List cardData = []; List> filteredAndSortedData = []; + List> selectedFiltersStorage = []; + List> selectedFiltersApi = []; List originalChartsData = []; List originalCardData = []; List originalTabCardData = []; @@ -75,11 +77,12 @@ class _ChartScreen1State extends ConsumerState { late Color backgroundColor; final ScrollController _scrollController = ScrollController(); int _activeTabIndex = 0; - dynamic tabWiseKpi=[]; - dynamic tabFilteredKpi=[]; - List> formattedFilters=[]; + dynamic tabWiseKpi = []; + dynamic tabFilteredKpi = []; + List> formattedFilters = []; dynamic _tabsData = []; + dynamic currentTab = []; // List> _tabsData = []; late List marriageTargets; late List previousMarriageTargets; @@ -90,6 +93,7 @@ class _ChartScreen1State extends ConsumerState { bool isBookmarked = false; // Track bookmark state String? bookmarkId; // Stores the ID of the bookmark record in PocketBase late final Locale locale; + late String mainTopic; @override void initState() { @@ -98,10 +102,12 @@ class _ChartScreen1State extends ConsumerState { print(' bgColor $bgColor'); checkIfBookmarked(); locale = ref.read(localeProvider) ?? const Locale('en'); + print('locale-$locale'); - fetchChartData(widget.dataSets, locale?.languageCode ?? 'en', widget.kpi, widget.filter_data) - .then((_) { - if(_tabsData.isNotEmpty) { + fetchChartData(widget.dataSets, locale?.languageCode ?? 'en', widget.kpi, + widget.filter_data) + .then((_) { + if (_tabsData.isNotEmpty) { // Call onTabSelected for the first tab onTabSelected(_tabsData[0]['id']); } @@ -313,7 +319,8 @@ class _ChartScreen1State extends ConsumerState { backgroundColor: Color(0xFFAA8E83), // Set background color foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), // Set text color + borderRadius: + BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -324,7 +331,6 @@ class _ChartScreen1State extends ConsumerState { ), ], ) - ], ), ); @@ -371,29 +377,30 @@ class _ChartScreen1State extends ConsumerState { SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ SizedBox( - width: 100, // Set the desired width - child: TextButton( - onPressed: () => Navigator.pop(context), - style: TextButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 10.0), // Adjust the radius as needed + children: [ + SizedBox( + width: 100, // Set the desired width + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 10.0), // Adjust the radius as needed + ), + side: BorderSide( + color: Color(0xFFAA8E83), // Set the outline color + width: 1, // Set the border width + ), ), - side: BorderSide( - color: Color(0xFFAA8E83), // Set the outline color - width: 1, // Set the border width - ), - ), - child: Text( - context.translate('No', 'لا'), - style: TextStyle( - color: Color(0xFFAA8E83), - fontSize: 16, + child: Text( + context.translate('No', 'لا'), + style: TextStyle( + color: Color(0xFFAA8E83), + fontSize: 16, + ), ), ), ), - ), SizedBox( width: 100, // Set the desired width child: TextButton( @@ -405,7 +412,8 @@ class _ChartScreen1State extends ConsumerState { backgroundColor: Color(0xFFAA8E83), // Set background color foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), // Set text color + borderRadius: + BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -413,7 +421,8 @@ class _ChartScreen1State extends ConsumerState { style: TextStyle(color: Colors.white, fontSize: 16), ), ), - ),], + ), + ], ), ], ), @@ -1268,13 +1277,28 @@ class _ChartScreen1State extends ConsumerState { }); } - Future fetchChartData(String dataSets, locale, kpi, filter_data ) async { - var data = await apiService.fetchChartData(dataSets, locale,kpi,filter_data); + Future fetchChartData(String dataSets, locale, kpi, filter_data) async { + var data = + await apiService.fetchChartData(dataSets, locale, kpi, filter_data); tabFilteredKpi = kpi; print('FETCH KPI- $kpi'); print('FETCH KFilter- $filter_data'); + print('locale1 - $locale'); + // print('locale11 - $loacleData'); + // Store filter_data if it has a value + if (filter_data != null && filter_data is List && filter_data.isNotEmpty) { + selectedFiltersApi = filter_data.map>((entry) { + if (entry is Map) { + return entry.map( + (key, value) => MapEntry(key.toString(), value)); + } + return {}; + }).toList(); + + print('FETVCFILTEr: $selectedFiltersApi'); + } if (data.containsKey('filterData')) { var filterData = data['filterData']; @@ -1316,7 +1340,6 @@ class _ChartScreen1State extends ConsumerState { void applyFilters(BuildContext context, List filters, List data, List dataCard, List selectedFilters) { - print('applyFilters called'); print('Selected ApplyFilters: $selectedFilters'); @@ -1332,37 +1355,38 @@ class _ChartScreen1State extends ConsumerState { } // Transform the selectedFilters list into the required format - formattedFilters = selectedFilters - .where((filter) => filter['filter_data'] != null && filter['filter_data'].isNotEmpty) - .map((filter){ - return{ + formattedFilters = selectedFilters + // .where((filter) => filter['filter_data'] != null && filter['filter_data'].isNotEmpty) + .map((filter) { + return { 'filter_key': filter['filter_key'], - 'filter_data':filter['filter_data'].map((item)=> item.toString()).toList() + 'filter_data': + filter['filter_data'].map((item) => item.toString()).toList() }; }).toList(); print('Formatted Filters1: $formattedFilters'); // Convert to JSON format - String selectedFormatFilters = jsonEncode({ - 'kpi': tabWiseKpi, - 'filter_data': formattedFilters - }); + String selectedFormatFilters = + jsonEncode({'kpi': tabWiseKpi, 'filter_data': formattedFilters}); print('Formatted Filters2: $selectedFormatFilters'); + final locale = ref.watch(localeProvider)?.languageCode ?? 'en'; - // Call fetchChartData with correct parameters + print('locale2- $locale'); + + // Call fetchChartData with correct parameters fetchChartData( widget.dataSets, - locale.languageCode ?? 'en', + locale, tabWiseKpi, // Ensure you are passing the correct KPI here formattedFilters // Pass the formatted filter data here - ); + ); Navigator.pop(context); selectedFiltersStorage = List.from(selectedFilters); print('selectedFiltersStoraged- $selectedFiltersStorage'); - } void applyFilters1(BuildContext context, List filters, List data, @@ -1503,7 +1527,6 @@ class _ChartScreen1State extends ConsumerState { } void showRightSideModal(BuildContext context, List filters, List data) { - print('selectedFiltersStoraged1- $selectedFiltersStorage'); // Initialize selected filters structure from the stored value, if exists @@ -1532,7 +1555,7 @@ class _ChartScreen1State extends ConsumerState { // Header Section Padding( padding: const EdgeInsets.only( - top: 16.0, bottom: 0.0, left: 16.0, right: 16.0), + top: 26.0, bottom: 0.0, left: 16.0, right: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -1541,7 +1564,8 @@ class _ChartScreen1State extends ConsumerState { SvgPicture.asset( UaeNumbersAssetPath.filterCharts, semanticsLabel: 'Filter', - colorFilter:ColorFilter.mode(Color(0xFF8E8E8E),BlendMode.srcIn), + colorFilter: ColorFilter.mode( + Color(0xFF8E8E8E), BlendMode.srcIn), width: 25, height: 25, ), @@ -1617,6 +1641,7 @@ class _ChartScreen1State extends ConsumerState { builder: (context, dialogSetState) { final locale = ref.watch(localeProvider); + print('localelocale $locale'); return AlertDialog( title: Text( @@ -1786,13 +1811,13 @@ class _ChartScreen1State extends ConsumerState { cardData = List.from(originalTabCardData); }); selectedFiltersStorage.clear(); - formattedFilters = []; + formattedFilters = []; fetchChartData( widget.dataSets, locale.languageCode ?? 'en', tabWiseKpi, // Ensure you are passing the correct KPI here formattedFilters // Pass the formatted filter data here - ); + ); Navigator.pop(context); }, style: ElevatedButton.styleFrom( @@ -1877,9 +1902,14 @@ class _ChartScreen1State extends ConsumerState { void onTabSelected(String tabId) { print('Selected Tab on: $tabId'); tabWiseKpi = tabId; + print('Selected Tab tabWiseKpi: $tabWiseKpi'); + + selectedFiltersStorage.clear(); + + if (selectedFiltersApi.isNotEmpty && tabWiseKpi == tabFilteredKpi) { + selectedFiltersStorage = List.from(selectedFiltersApi); + } - // selectedFiltersStorage.clear(); - // Filter the filterDataSet filterData = filterDataSet .where((item) => item['key'] == tabId) .map((item) => item['value']) @@ -1887,9 +1917,63 @@ class _ChartScreen1State extends ConsumerState { .expand((item) => item) .toList(); - print("FilteringDAtss START1 - $filterData"); + print("FilteringDAtss START1d - $filterData"); print('TABId1 - $tabId'); + Map monthMap = { + 'jan': 1, + 'feb': 2, + 'mar': 3, + 'apr': 4, + 'may': 5, + 'jun': 6, + 'jul': 7, + 'aug': 8, + 'sep': 9, + 'oct': 10, + 'nov': 11, + 'dec': 12 + }; + + for (var item in filterData) { + if (item['filter_key'] == 'TIME_PERIOD') { + List timePeriods = List.from(item['filter_data']); + + timePeriods.sort((a, b) { + List aParts = a.split(RegExp(r'[-_]')); + List bParts = b.split(RegExp(r'[-_]')); + + int yearA = int.parse(aParts[0]); + int yearB = int.parse(bParts[0]); + + // if (yearA != yearB) return yearA.compareTo(yearB); + if (yearA != yearB) + return yearB.compareTo(yearA); // Sort years in descending order + + if (aParts.length == 1) return -1; // Year-only comes first + if (bParts.length == 1) return 1; // Year-only comes first + + int monthA = + int.tryParse(aParts[1]) ?? monthMap[aParts[1].toLowerCase()] ?? 0; + int monthB = + int.tryParse(bParts[1]) ?? monthMap[bParts[1].toLowerCase()] ?? 0; + + return monthB.compareTo( + monthA); // Sort months in descending order within the same year + // return monthA.compareTo(monthB); + }); + + item['filter_data'] = timePeriods; + } else if (item['filter_key'] == 'REF_AREA') { + continue; + } else { + // Sort alphabetically for all other cases + List otherFilters = List.from(item['filter_data']); + otherFilters.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase())); + item['filter_data'] = otherFilters; + } + } + // for (var item in filterData) { // if (item['filter_key'] == 'TIME_PERIOD') { // // Convert the values to integers, sort them, and convert back to strings @@ -1914,7 +1998,6 @@ class _ChartScreen1State extends ConsumerState { } void performActionForTab(String tabId) { - print('Selected Tab perform: $tabId'); // Filter the chartsData array based on the tabId @@ -2007,25 +2090,39 @@ class _ChartScreen1State extends ConsumerState { Widget build(BuildContext context) { final locale = ref.watch(localeProvider); final localeNotifier = ref.read(localeProvider.notifier); + ref.listen(localeProvider, (previous, next) { final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + // fetchChartData(widget.dataSets, localeCode); + print('Saving currentTab: $tabWiseKpi before locale change'); + print('localeCodeWgt- $localeCode'); + currentTab = tabWiseKpi; + print('Saving currentTab: $currentTab After locale change'); - - final List> safeFilterData = widget.filter_data ?? []; + final List> safeFilterData = + widget.filter_data ?? []; final String safeKpi = widget.kpi ?? ''; - fetchChartData(widget.dataSets,localeCode, safeKpi, safeFilterData).then((_) { + fetchChartData(widget.dataSets, localeCode, safeKpi, safeFilterData) + .then((_) { print('DEBUG KPI: $safeKpi'); - if (_tabsData.isNotEmpty) { + + print('TAB11: $currentTab'); + + if (currentTab.isNotEmpty) { // Call onTabSelected for the first tab + onTabSelected(currentTab); + print('TAB1: $currentTab'); + } else if (_tabsData.isNotEmpty) { + // Call onTabSelected for the first tab + print('TAB2: $_tabsData'); onTabSelected(_tabsData[0]['id']); } }); - - }); double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; + mainTopic = chartScreenData['main_topic'] ?? ''; int crossAxisCount = cardData.isNotEmpty ? (cardData.length / 2).ceil().clamp(1, 2) : 1; @@ -2046,8 +2143,10 @@ class _ChartScreen1State extends ConsumerState { (chartScreenData['body_color'] ?? '#898C81').replaceFirst('#', '0xff'), ), ); + final chartHeader = chartScreenData['main_topic'] ?? ''; print('ChartScrnBodyColor'); + print('chartHeader - $chartHeader'); print(chartScreenData['body_color']); print( Color(int.parse((chartScreenData['body_color'] ?? '#898C81') @@ -2098,7 +2197,8 @@ class _ChartScreen1State extends ConsumerState { children: [ Text( // widget.mainTopic ?? '', - chartScreenData['main_topic'] ?? '', + // chartScreenData['main_topic'] ?? '', + mainTopic, style: TextStyle( color: Colors.white, fontSize: 26, @@ -2223,28 +2323,29 @@ class _ChartScreen1State extends ConsumerState { Row( children: [ GestureDetector( - onTap: () => shareCurrentPage(context), - - child: Row( + onTap: () => shareCurrentPage(context), + child: Row( children: [ - Text( - context.translate( - 'Share', - 'يشارك', + Text( + context.translate( + 'Share', + 'يشارك', + ), + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), ), - style: const TextStyle( - fontSize: 16, - color: Colors.white, - ), - ), - SizedBox(width: 5), - SvgPicture.asset( - UaeNumbersAssetPath.share, - semanticsLabel: 'share', - width: 24, - height: 24, - ) - ],),), + SizedBox(width: 5), + SvgPicture.asset( + UaeNumbersAssetPath.share, + semanticsLabel: 'share', + width: 24, + height: 24, + ) + ], + ), + ), ], ), SizedBox( @@ -2353,7 +2454,8 @@ class _ChartScreen1State extends ConsumerState { // ), // nonChartData Cards Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.only( + top: 8.0, left: 1.0, right: 1.0), child: LayoutBuilder( builder: (context, constraints) { List rows = []; @@ -2431,23 +2533,32 @@ class _ChartScreen1State extends ConsumerState { children: [ // ConstrainedBox Container( - height: (chartsData[index] - ['chart_type'] == - 'pie_chart') - ? 500 - : (chartsData[index][ - 'chart_type'] == - 'bar_chart_horizontal') - ? 370 - : (chartsData[index]['chart_type'] == 'fl_stacked_bar') - ? 390 - : 350, + height: (chartsData[index] + ['chart_type'] == + 'pie_chart') + ? 500 + : (chartsData[index][ + 'chart_type'] == + 'bar_chart_horizontal') + ? 370 + : (chartsData[index][ + 'chart_type'] == + 'horizontal_rotate') + ? 385 + : (chartsData[index] + [ + 'chart_type'] == + 'fl_stacked_bar') + ? 390 + : 350, - // child: buildChart(chartsData[index]), - child: ChartWidget( - chartData: - chartsData[index], - bodyColor: bodyColor)) + // child: buildChart(chartsData[index]), + child: ChartWidget( + chartData: + chartsData[index], + bodyColor: bodyColor, + chartHeader: chartHeader), + ) ], ), ), @@ -2595,16 +2706,14 @@ class CardWidget extends StatelessWidget { final bodyColor = Color( int.parse( - (chartScreenData['body_color'] ?? '#898C81').replaceFirst('#', '0xff'), + (chartScreenData['border_color'] ?? '#898C81') + .replaceFirst('#', '0xff'), ), ); - - if(response.isEmpty) - { - return SizedBox.shrink(); - } - else if (response.length == 1) { + if (response.isEmpty) { + return SizedBox.shrink(); + } else if (response.length == 1) { return Container( margin: const EdgeInsets.all(10), // width: card_full_length ? double.infinity : null, @@ -2626,7 +2735,7 @@ class CardWidget extends StatelessWidget { elevation: 2, child: Container( // height: cardHeight, - height: 160, + height: 175, padding: const EdgeInsets.only( left: 16.0, right: 16.0, bottom: 2.0, top: 1.0), decoration: BoxDecoration( @@ -2757,7 +2866,7 @@ class CardWidget extends StatelessWidget { elevation: 0, // Remove shadow to keep only the outli child: Container( - height: 160, + height: 175, padding: const EdgeInsets.all(10.0), // padding: const EdgeInsets.only( // left: 10.0, right: 10.0, bottom: 5.0, top: 1.0), @@ -2767,7 +2876,8 @@ class CardWidget extends StatelessWidget { BorderRadius.circular(12), // Ensure border radius is applied ), child: Column( - mainAxisSize: MainAxisSize.min, // Adjust card height based on content + mainAxisSize: + MainAxisSize.min, // Adjust card height based on content mainAxisAlignment: MainAxisAlignment.center, children: [ // const Icon(Icons.public, @@ -2840,8 +2950,6 @@ class CardWidget extends StatelessWidget { ), const SizedBox(height: 1), - - Row( mainAxisAlignment: MainAxisAlignment .center, // Center-aligns the row contents @@ -2870,19 +2978,15 @@ class CardWidget extends StatelessWidget { ], ), - SizedBox( height: 2, // Height of the divider - width: 100, + width: 100, child: Divider( color: Color(0xFFBBBCBD), thickness: 1, // Divider line thickness ), ), - - - Row( mainAxisAlignment: MainAxisAlignment .center, // Center-aligns the row contents @@ -2897,7 +3001,8 @@ class CardWidget extends StatelessWidget { : _getColorFromHex(response[1]['font_color']), ), textAlign: TextAlign.center, // Ensure proper alignment - overflow: TextOverflow.ellipsis, // Truncate if text overflows + overflow: + TextOverflow.ellipsis, // Truncate if text overflows ), const SizedBox(width: 1), // Space between text and icon if (response[1]['font_color'] != '') ...[ @@ -2911,7 +3016,6 @@ class CardWidget extends StatelessWidget { ], ), - Text( // '(${response[1]['display_value'] ?? 'NA'})', RegExp(r'\d').hasMatch(response[1]['display_value'] ?? '') diff --git a/lib/presentation/Screens/charts/services/api_service.dart b/lib/presentation/Screens/charts/services/api_service.dart index dfc29379..634e0793 100644 --- a/lib/presentation/Screens/charts/services/api_service.dart +++ b/lib/presentation/Screens/charts/services/api_service.dart @@ -16,7 +16,7 @@ class ApiService { // static const String baseUrl = 'https://pb.venbait.in/api/getDataSet'; static const String baseUrl = '$apiUrl/api/getDataSet'; - Future> fetchChartData(String dataSets, locale,kpi, + Future> fetchChartData(String dataSets, locale, kpi, List> filterData) async { List isChartData = []; List nonChartData = []; @@ -28,14 +28,14 @@ class ApiService { // '$baseUrl?dataset=$dataSets&language=$locale&kpi=$kpi&filter_data=${Uri.encodeComponent(jsonEncode(filterData))}' // ); - final url = Uri.parse(baseUrl); // No query parameters in URL for POST final Map requestBody = { 'dataset': dataSets, 'language': locale, 'kpi': kpi.isNotEmpty ? kpi : null, // Avoid sending empty kpi - 'filter_data': filterData.isNotEmpty ? filterData : null, // Avoid empty list + 'filter_data': + filterData.isNotEmpty ? filterData : [], // Avoid empty list }; // final url = Uri.https('$baseUrl?dataset=$dataSets&language=$locale'); @@ -66,8 +66,9 @@ class ApiService { nonChartData.add(item); } } - final Map chartScreenData = jsonData['screen_heading_and_color']; - + final Map chartScreenData = + jsonData['screen_heading_and_color']; + // Save original data for reset // originalChartsData = List.from(isChartData); // Store original data // originalCardData = List.from(nonChartData); // Store original data @@ -77,7 +78,7 @@ class ApiService { 'isChartData': isChartData, 'nonChartData': nonChartData, 'filterData': jsonData['new_filter_data'], - 'chartScreenData' :chartScreenData + 'chartScreenData': chartScreenData // 'filterData': jsonData['filter_data'] // 'originalChartsData': originalChartsData, // 'originalCardData': originalCardData, @@ -195,4 +196,4 @@ class ApiService { return '$amount'; } } -} \ No newline at end of file +} diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index 8c95e730..8926cf5a 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -7,10 +7,114 @@ import 'package:flutter/material.dart'; // import 'package:syncfusion_flutter_charts/charts.dart'; class ChartWidget extends StatelessWidget { - ChartWidget({Key? key, required this.chartData, required this.bodyColor}) + ChartWidget( + {Key? key, + required this.chartData, + required this.bodyColor, + required this.chartHeader}) : super(key: key); final dynamic chartData; final Color bodyColor; + final String chartHeader; + + List showingTooltipOnSpots = []; // To store indices of active tooltips + late LineChartBarData tooltipsOnBar; // Needs proper initialization + + // Define color lists + final Map> colorMap = { + "ECONOMY": [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ], + "SOCIAL": [ + Color(0xFFD9BB99), + Color(0xFF87766E), + Color(0xFFC6A885), + Color(0xFFA28565), + Color(0xFFBFB29B), + Color(0xFFE8D8BB), + ], + "ENVIRONMENT": [ + Color(0xFF376C79), + Color(0xFF578D9C), + Color(0xFF7DAFBC), + Color(0xFF86C7D9), + Color(0xFF989898), + ], + }; + + final Map> colorMapForThree = { + "ECONOMY": [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + ], + "SOCIAL": [ + Color(0xFFD9BB99), + Color(0xFF87766E), + Color(0xFFC6A885), + ], + "ENVIRONMENT": [ + Color(0xFF376C79), + Color(0xFF578D9C), + Color(0xFF7DAFBC), + ], + }; + + // Define color lists + final Map> colorMapForTwo = { + "ECONOMY": [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + ], + "SOCIAL": [ + Color(0xFFD9BB99), + Color(0xFF87766E), + ], + "ENVIRONMENT": [ + Color(0xFF376C79), + Color(0xFF578D9C), + ], + }; + + // List get uniqueColors => colorMap[chartHeader] ?? [Colors.grey]; // Default to grey if header is unknown +// Function to get colors for both English & Arabic keys + List get uniqueColors { + final Map translations = { + 'اقتصاد': 'ECONOMY', + 'اجتماعي': 'SOCIAL', + 'بيئة': 'ENVIRONMENT', + }; + + String key = translations[chartHeader] ?? chartHeader; + return colorMap[key] ?? [Colors.grey]; + } + + List get uniqueColorsForTwo { + final Map translations = { + 'اقتصاد': 'ECONOMY', + 'اجتماعي': 'SOCIAL', + 'بيئة': 'ENVIRONMENT', + }; + + String key = translations[chartHeader] ?? chartHeader; + return colorMapForTwo[key] ?? [Colors.grey]; + } + + List get uniqueColorsForThree { + final Map translations = { + 'اقتصاد': 'ECONOMY', + 'اجتماعي': 'SOCIAL', + 'بيئة': 'ENVIRONMENT', + }; + + String key = translations[chartHeader] ?? chartHeader; + return colorMapForThree[key] ?? [Colors.grey]; + } String capitalizeAndSplit(String input) { return input @@ -19,6 +123,18 @@ class ChartWidget extends StatelessWidget { .join(' '); } + Color _hexToColor(String hexColor) { + hexColor = hexColor.toUpperCase().replaceAll('#', ''); // Remove # + if (hexColor.length == 6) { + hexColor = 'FF$hexColor'; // Add alpha if missing + } + return Color(int.parse(hexColor, radix: 16)); + } + + String formatVerticalText(String text) { + return text.split('').join('\n'); // Inserts a newline after each character + } + String formatNumber(double value) { if (value >= 1e12) { return '${(value / 1e12).toStringAsFixed(2)}T'; @@ -34,7 +150,10 @@ class ChartWidget extends StatelessWidget { } String formatNumberConversion( - double value, String chartConversion, String numberFormat,) { + double value, + String chartConversion, + String numberFormat, + ) { // Convert the input value to base unit (actual value) double baseValue = value; @@ -91,11 +210,20 @@ class ChartWidget extends StatelessWidget { }).toList(); } + Color hexToColor(String hex) { + hex = hex.replaceFirst('#', ''); // Remove # + if (hex.length == 6) { + hex = 'FF$hex'; // Add full opacity + } + return Color(int.parse(hex, radix: 16)); + } + Color adjustColor(Color baseColor, int index) { final HSLColor hsl = HSLColor.fromColor(baseColor); // Lightness variation (keeping it light, between 0.6 and 0.9) - double lightnessFactor = 0.6 + (index % 4) * 0.1; // Varies between 0.6 to 0.8 + double lightnessFactor = + 0.6 + (index % 4) * 0.1; // Varies between 0.6 to 0.8 final HSLColor adjustedHSL = hsl.withLightness( lightnessFactor.clamp(0.6, 0.9), // Ensures light shades only ); @@ -103,6 +231,23 @@ class ChartWidget extends StatelessWidget { return adjustedHSL.toColor(); } + Color adjustColorlineTrend(Color baseColor, int index) { + final HSLColor hsl = HSLColor.fromColor(baseColor); + + // Darken the color slightly by reducing lightness + double lightnessFactor = hsl.lightness - ((index % 4) * 0.05); + lightnessFactor = lightnessFactor.clamp(0.3, 0.7); // Keeps colors darker + + // Adjust opacity: Making it darker with alpha between 160-230 + int alphaFactor = + (230 - (index % 4) * 20).clamp(160, 230); // Darker opacity + + return hsl + .withLightness(lightnessFactor) + .toColor() + .withAlpha(alphaFactor); // Use withAlpha() instead of withOpacity() + } + List parsePieChartData( dynamic chartData, double totalValue, @@ -138,7 +283,8 @@ class ChartWidget extends StatelessWidget { return PieChartSectionData( value: value, // color: Colors.primaries[index % Colors.primaries.length], - color: adjustColor(bodyColor, index), // Adjust alpha dynamically + // color: adjustColor(bodyColor, index), // Adjust alpha dynamically + color: uniqueColors[index % uniqueColors.length], title: '${percentage.toStringAsFixed(1)}%', radius: isTouched ? 60 : 50, @@ -170,7 +316,7 @@ class ChartWidget extends StatelessWidget { width: 10, height: 10, decoration: BoxDecoration( - color: adjustColor(bodyColor, index), + color: uniqueColors[index % uniqueColors.length], shape: BoxShape.circle, ), ), @@ -194,9 +340,9 @@ class ChartWidget extends StatelessWidget { style: TextStyle( fontSize: 12, ), - // softWrap: true, - // maxLines: 2, - overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow + softWrap: true, + maxLines: 2, + // overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow ), ), ), @@ -207,6 +353,38 @@ class ChartWidget extends StatelessWidget { }).toList(); } + List generateIndicatorsBar( + Map chartData, List groupByValues) { + List indicators = []; + + for (int i = 0; i < groupByValues.length; i++) { + String groupLabel = groupByValues[i]; + + indicators.add( + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, // Replace with dynamic color if needed + ), + ), + SizedBox(width: 5), + Text( + groupLabel, // Displaying groupLabel + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + ], + ), + ); + } + + return indicators; + } + Set extractGroupByValues(dynamic chartData, String groupByKey) { return (chartData['response'] as List).map((entry) { var value = entry['ObsKey'][groupByKey]; @@ -216,6 +394,12 @@ class ChartWidget extends StatelessWidget { }).toSet(); } + // @override + // void initState() { + // super.initState(); + // tooltipsOnBar = lineBars.first; // Example: Assign the first line bar + // } + Widget buildChart(dynamic chartData, BuildContext context) { // print('bodyColor- $bodyColor'); print('chartDataccccccc $chartData'); @@ -225,13 +409,23 @@ class ChartWidget extends StatelessWidget { } print(chartData['chart_type_json']['TIME_PERIOD']); + print(chartData['chart_type_json']['x_group']); - String groupByKey = chartData['group_by'] ?? ''; + String groupByKey = ''; + + if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || + chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { + groupByKey = chartData['chart_type_json']['y_group'] ?? ''; + } else { + groupByKey = chartData['chart_type_json']['x_group'] ?? ''; + } + + // String groupByKey = chartData['group_by'] ?? ''; print('groupByKey $groupByKey'); Set groupByValues = extractGroupByValues(chartData, groupByKey); print('groupByValues $groupByValues'); - switch (chartData['chart_type']) { + switch (chartData['chart_type_json']['chart_type']) { // case 'stacked_bar': // String chartTitle = // capitalizeAndSplit(chartData['chart_heading'] ?? ''); @@ -385,30 +579,31 @@ class ChartWidget extends StatelessWidget { // child: SingleChildScrollView( // scrollDirection: Axis.vertical, - child: Center( - child: Container( - width: double.infinity, - constraints: - BoxConstraints(minHeight: 3), // Allow dynamic height - child: Padding( - padding: const EdgeInsets.only( - left: 2.0, right: 2.0, bottom: 8.0, top: 1.0), - child: Column( // Change Row to Column - crossAxisAlignment: CrossAxisAlignment.start, - children: generateIndicators(chartData, chartData['group_by']), - ), - - - // child: Wrap( - // spacing: 2, - // runSpacing: 5, - // children: - // generateIndicators(chartData, chartData['group_by']), - // ), + child: Center( + child: Container( + width: double.infinity, + constraints: + BoxConstraints(minHeight: 3), // Allow dynamic height + child: Padding( + padding: const EdgeInsets.only( + left: 2.0, right: 2.0, bottom: 8.0, top: 1.0), + child: Column( + // Change Row to Column + crossAxisAlignment: CrossAxisAlignment.start, + children: + generateIndicators(chartData, chartData['group_by']), ), + + // child: Wrap( + // spacing: 2, + // runSpacing: 5, + // children: + // generateIndicators(chartData, chartData['group_by']), + // ), ), ), ), + ), // ), ], ); @@ -462,12 +657,11 @@ class ChartWidget extends StatelessWidget { horizontal: 4, vertical: 8), // Optional tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipMargin: 16, - + getTooltipColor: (colorMap) => Colors.black, getTooltipItem: (groupData, groupIndex, rodData, rodIndex) { // Get the list of group names dynamically - List groupNames = []; for (var entry in chartData['response']) { String groupValue = entry['ObsKey'][groupByKey]; @@ -501,8 +695,6 @@ class ChartWidget extends StatelessWidget { Color groupColor = _getColorForGroup( i); // Replace with your color logic - - // Add a TextSpan for the colored circle and the group name with value tooltipTextSpans.addAll([ TextSpan( @@ -587,7 +779,8 @@ class ChartWidget extends StatelessWidget { waitDuration: Duration(milliseconds: 500), showDuration: Duration(seconds: 2), decoration: BoxDecoration( - color: Colors.blueGrey[900], + color: Colors.black, + // color: Colors.blueGrey[900], borderRadius: BorderRadius.circular(4), ), textStyle: TextStyle(color: Colors.white), @@ -619,11 +812,14 @@ class ChartWidget extends StatelessWidget { ]; Map groupColorMap = {}; int colorIndex = 0; - for (String group in groupByValues) { - groupColorMap[group] = uniqueColorsLine_trend_2[ - colorIndex % uniqueColorsLine_trend_2.length]; - colorIndex++; - } + + // for (String group in groupByValues) { + // // groupColorMap[group] = uniqueColorsLine_trend_2[ + // // colorIndex % uniqueColorsLine_trend_2.length]; + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // colorIndex++; + // } print('LnTrnd1'); // Extract all years from the chart data @@ -699,8 +895,18 @@ class ChartWidget extends StatelessWidget { } else if (RegExp(r'^\d{4}-[A-Za-z]{3}$').hasMatch(timePeriod)) { // Year-MonthAbbr (2019-Nov) Map monthMap = { - 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, - 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 + 'Jan': 1, + 'Feb': 2, + 'Mar': 3, + 'Apr': 4, + 'May': 5, + 'Jun': 6, + 'Jul': 7, + 'Aug': 8, + 'Sep': 9, + 'Oct': 10, + 'Nov': 11, + 'Dec': 12 }; List parts = timePeriod.split('-'); @@ -721,9 +927,41 @@ class ChartWidget extends StatelessWidget { print('LnTrnd2.1'); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + print('parsedChartBarColors1 - $parsedChartBarColors'); + + for (String group in groupByValues) { + if (hasBarColors && chartData['dataset'] == 'population') { + groupColorMap[group] = + parsedChartBarColors[group]!; // Directly assign as Color + } else { + groupColorMap[group] = + uniqueColors[colorIndex % uniqueColors.length]; + } + colorIndex++; + } + // Generate line bars for the chart - List lineBars = - lineBarsData(filteredData, groupByValues, groupByKey); + List lineBars = lineBarsData( + filteredData, groupByValues, groupByKey, parsedChartBarColors); print('LnTrnd3'); @@ -749,6 +987,16 @@ class ChartWidget extends StatelessWidget { // Chart Expanded( child: LineChart(LineChartData( + // showingTooltipIndicators: showingTooltipOnSpots.map((index) { + // return ShowingTooltipIndicators([ + // LineBarSpot( + // tooltipsOnBar, + // lineBarsData.indexOf(tooltipsOnBar), + // tooltipsOnBar.spots[index], + // ), + // ]); + // }).toList(), + lineTouchData: lineTouchData1(), gridData: gridData(), // titlesData: titlesData1(uniqueXValues), @@ -797,11 +1045,13 @@ class ChartWidget extends StatelessWidget { Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; Map groupColorMap = {}; int colorIndex = 0; - for (String group in groupByValues) { - groupColorMap[group] = uniqueColorsLine_trend_2[ - colorIndex % uniqueColorsLine_trend_2.length]; - colorIndex++; - } + // for (String group in groupByValues) { + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = uniqueColorsLine_trend_2[ + // // colorIndex % uniqueColorsLine_trend_2.length]; + // colorIndex++; + // } // Extract all years from the chart data List years = (chartData['response'] as List) @@ -825,7 +1075,7 @@ class ChartWidget extends StatelessWidget { selectedYears.add(latestYear); // Include the latest year in selection } -// Filter chart data to include only the selected years + // Filter chart data to include only the selected years List filteredData = (chartData['response'] as List).where((entry) { int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; @@ -837,9 +1087,39 @@ class ChartWidget extends StatelessWidget { (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) .toSet(); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + for (String group in groupByValues) { + if (parsedChartBarColors.containsKey(group)) { + groupColorMap[group] = + parsedChartBarColors[group]!; // Directly assign as Color + } else { + groupColorMap[group] = + uniqueColors[colorIndex % uniqueColors.length]; + } + colorIndex++; + } + // Generate line bars for the chart - List lineBars = - lineBarsData(filteredData, groupByValues, groupByKey); + List lineBars = lineBarsData( + filteredData, groupByValues, groupByKey, parsedChartBarColors); return Column(children: [ Text( @@ -864,7 +1144,7 @@ class ChartWidget extends StatelessWidget { Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, // Enable horizontal scrolling - padding: const EdgeInsets.only(right: 40), + padding: const EdgeInsets.only(right: 40, top: 10), child: SizedBox( width: (uniqueXValues.length * 50) + 50, // Adjust width dynamically @@ -907,22 +1187,24 @@ class ChartWidget extends StatelessWidget { // Chart ]); case 'line_trend_2': - final List uniqueColorsLine_trend_2 = [ - Color(0xFF6097CD), - Color(0xFFD086A7), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; + // final List uniqueColorsLine_trend_2 = [ + // Color(0xFF6097CD), + // Color(0xFFD086A7), + // Color(0xFF98BCE5), + // Color(0xFFA7B5C5), + // Color(0xFFBED3EC), + // Color(0xFFD4E3F4), + // ]; Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; Map groupColorMap = {}; int colorIndex = 0; - for (String group in groupByValues) { - groupColorMap[group] = uniqueColorsLine_trend_2[ - colorIndex % uniqueColorsLine_trend_2.length]; - colorIndex++; - } + // for (String group in groupByValues) { + // // groupColorMap[group] = uniqueColorsLine_trend_2[ + // // colorIndex % uniqueColorsLine_trend_2.length]; + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // colorIndex++; + // } // Extract all years from the chart data List years = (chartData['response'] as List) @@ -960,11 +1242,39 @@ class ChartWidget extends StatelessWidget { print('uniqueXValuesLineTrend2- $uniqueXValues'); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + for (String group in groupByValues) { + if (parsedChartBarColors.containsKey(group)) { + groupColorMap[group] = + parsedChartBarColors[group]!; // Directly assign as Color + } else { + groupColorMap[group] = + uniqueColors[colorIndex % uniqueColors.length]; + } + colorIndex++; + } + // Generate line bars for the chart List lineBars = lineBarsData2(filteredData); - - return Column(children: [ Text( chartData['chart_heading'] ?? '', @@ -990,7 +1300,8 @@ class ChartWidget extends StatelessWidget { scrollDirection: Axis.horizontal, // Enable horizontal scrolling padding: const EdgeInsets.only(right: 40, top: 20), child: SizedBox( - width: (uniqueXValues.length * 50) + 50, // Adjust width dynamically + width: (uniqueXValues.length * 50) + + 50, // Adjust width dynamically child: LineChart( LineChartData( lineTouchData: lineTouchData1(), @@ -1037,6 +1348,7 @@ class ChartWidget extends StatelessWidget { print('yValue-$yValue'); print('unitMsr-$unitMsr'); + //Handled For Quarter Chart Case (sort by Year and Quarter) if (xadditionalgrp == 'TIME_PERIOD') { chartData['response'].sort((a, b) { // Convert TIME_PERIOD to int for proper sorting @@ -1074,7 +1386,6 @@ class ChartWidget extends StatelessWidget { } xAxisData.add(xLabel); - yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); // yAxisLabels.add(unitMsr ?? ''); } @@ -1099,179 +1410,226 @@ class ChartWidget extends StatelessWidget { ), SizedBox(height: 10), // Chart - Expanded( - child: BarChart( - BarChartData( - alignment: BarChartAlignment.spaceAround, - maxY: yAxisData.isNotEmpty - ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 - : 10, - // barTouchData: BarTouchData( - // enabled: true, - // touchCallback: (FlTouchEvent event, barTouchResponse) { - // if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { - // return; - // } - // }, - // touchTooltipData: BarTouchTooltipData( - // getTooltipColor: (group) => Colors.transparent, - // getTooltipItem: (group, groupIndex, rod, rodIndex) { - // if (rod.toY == 0) return null; // Hide for zero values - // return BarTooltipItem( - // formatNumber(rod.toY), - // TextStyle(color: Colors.black), - // ); - // }, - // ), - // ), + Expanded(child: LayoutBuilder( + builder: (context, constraints) { + double chartWidth = + xAxisData.length * (40 + 10); // Compute chart width + double screenWidth = constraints.maxWidth; - // barTouchData: BarTouchData(enabled: true), - barTouchData: BarTouchData( - enabled: false, - handleBuiltInTouches: false, - touchTooltipData: BarTouchTooltipData( - getTooltipColor: (group) => Colors.transparent, - // fitInsideHorizontally: true, - // fitInsideVertically: true, - // tooltipPadding: const EdgeInsets.all(8), - tooltipMargin: 1, - getTooltipItem: (group, groupIndex, rod, rodIndex) { - // String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length - // ? yAxisLabels[groupIndex] - // : ''; - if (rod.toY == 0) return null; - - return BarTooltipItem( - (chartConversion != null && - chartConversion.isNotEmpty && - number_format != null && - chartConversion.isNotEmpty) - ? formatNumberConversion(rod.toY, chartConversion, - number_format) // If condition is true - : formatNumber(rod.toY), - const TextStyle( - color: Colors.black, - fontSize: 12, - fontWeight: FontWeight.w400, + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.only(top: 10, left: 5), + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: + screenWidth, // Ensure it at least fills available width + maxWidth: chartWidth > screenWidth + ? chartWidth + : screenWidth, // Prevent non-normalized constraints ), - ); - }, - ), - ), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: false, - interval: (yAxisData.isNotEmpty - ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 - : 1), - getTitlesWidget: (value, meta) { - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Text('${value.toInt()}'), - ); - }, - reservedSize: 60, - ), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: 1, - getTitlesWidget: (value, meta) { - if (value.toInt() < xAxisData.length) { - String title = xAxisData[value.toInt()]; - String displayTitle = title.length > 10 - ? title.substring(0, 10) + '...' - : title; + child: Center( + child: SizedBox( + width: xAxisData.length * + (40 + 10), // Bar width + manual spacing + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData + .reduce((a, b) => a > b ? a : b) * + 1.2 + : 10, + // barTouchData: BarTouchData( + // enabled: true, + // touchCallback: (FlTouchEvent event, barTouchResponse) { + // if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { + // return; + // } + // }, + // touchTooltipData: BarTouchTooltipData( + // getTooltipColor: (group) => Colors.transparent, + // getTooltipItem: (group, groupIndex, rod, rodIndex) { + // if (rod.toY == 0) return null; // Hide for zero values + // return BarTooltipItem( + // formatNumber(rod.toY), + // TextStyle(color: Colors.black), + // ); + // }, + // ), + // ), - return Padding( - padding: - const EdgeInsets.only(top: 8.0, left: 55.0), - child: SizedBox( - width: 60, // Limit width to force wrapping - child: Transform.rotate( - // angle: -0.5, - angle: -1.5, + // barTouchData: BarTouchData(enabled: true), + barTouchData: BarTouchData( + enabled: false, + handleBuiltInTouches: false, + touchTooltipData: BarTouchTooltipData( + getTooltipColor: (group) => + Colors.transparent, + // fitInsideHorizontally: true, + // fitInsideVertically: true, + // tooltipPadding: const EdgeInsets.all(8), + tooltipMargin: 1, + getTooltipItem: + (group, groupIndex, rod, rodIndex) { + // String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length + // ? yAxisLabels[groupIndex] + // : ''; + if (rod.toY == 0) return null; - child: TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[ - 800], // Change background color - borderRadius: BorderRadius.circular( - 8), // Optional: rounded corners - ), - textStyle: TextStyle( - color: Colors - .white), // Change text color - ), - child: TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[ - 800], // Change background color - borderRadius: BorderRadius.circular( - 8), // Optional: rounded corners + return BarTooltipItem( + (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null && + chartConversion.isNotEmpty) + ? formatNumberConversion( + rod.toY, + chartConversion, + number_format) // If condition is true + : formatNumber(rod.toY), + const TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, ), - textStyle: TextStyle( - color: Colors - .white), // Change text color - ), - child: Tooltip( - message: title, - child: Text( - // title, - displayTitle, - softWrap: true, - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 10, - ), - overflow: TextOverflow.ellipsis, - ), - ), + ); + }, + ), + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + interval: (yAxisData.isNotEmpty + ? yAxisData.reduce( + (a, b) => a > b ? a : b) / + 5 + : 1), + getTitlesWidget: (value, meta) { + return Padding( + padding: + const EdgeInsets.only(right: 8.0), + child: Text('${value.toInt()}'), + ); + }, + reservedSize: 60, ), ), - ))); - } - return Container(); - }, - reservedSize: 110, - ), - ), - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), // Hide top titles - ), - rightTitles: AxisTitles( - sideTitles: - SideTitles(showTitles: false), // Hide right titles - ), - ), - gridData: FlGridData(show: false), - borderData: FlBorderData( - show: true, - border: const Border( - // left: BorderSide(color: Colors.grey), - bottom: BorderSide(color: Colors.grey), - ), - ), - barGroups: List.generate( - xAxisData.length, - (index) => BarChartGroupData( - x: index, - barRods: [ - BarChartRodData( - toY: yAxisData[index], - color: bodyColor, - borderRadius: BorderRadius.circular(4), - width: 20, - ), - ], - showingTooltipIndicators: [0], - ), - ), - ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, + getTitlesWidget: (value, meta) { + if (value.toInt() < xAxisData.length) { + String title = + xAxisData[value.toInt()]; + String displayTitle = title.length > + 10 + ? title.substring(0, 10) + '...' + : title; + + return Padding( + padding: const EdgeInsets.only( + top: 8.0, left: 75.0), + child: SizedBox( + width: + 60, // Limit width to force wrapping + child: Transform.rotate( + // angle: -0.5, + angle: -1.5, + + child: TooltipTheme( + data: TooltipThemeData( + decoration: + BoxDecoration( + color: Colors + .blueGrey[ + 800], // Change background color + borderRadius: + BorderRadius.circular( + 8), // Optional: rounded corners + ), + textStyle: TextStyle( + color: Colors + .white), // Change text color + ), + child: TooltipTheme( + data: TooltipThemeData( + decoration: + BoxDecoration( + color: Colors + .black, // Change background color + // color: Colors.blueGrey[800], // Change background color + borderRadius: + BorderRadius + .circular( + 8), // Optional: rounded corners + ), + textStyle: TextStyle( + color: Colors + .white), // Change text color + ), + child: Tooltip( + message: title, + child: Text( + // title, + displayTitle, + softWrap: true, + textAlign: + TextAlign.end, + style: TextStyle( + fontSize: 10, + ), + overflow: + TextOverflow + .ellipsis, + ), + ), + ), + ), + ))); + } + return Container(); + }, + reservedSize: 110, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false), // Hide right titles + ), + ), + gridData: FlGridData(show: false), + borderData: FlBorderData( + show: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.grey), + ), + ), + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + barsSpace: 10, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + color: bodyColor, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + showingTooltipIndicators: [0], + ), + ), + ), + )), + ))); + }, )) ]); case 'bar_chart_horizontal': @@ -1279,6 +1637,8 @@ class ChartWidget extends StatelessWidget { List xAxisData = []; List yAxisData = []; List> sortedData = []; + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; for (var entry in chartData['response']) { String? timePeriod = entry['ObsKey']['TIME_PERIOD']; @@ -1299,15 +1659,16 @@ class ChartWidget extends StatelessWidget { String timeA = a['ObsKey']['TIME_PERIOD']; String timeB = b['ObsKey']['TIME_PERIOD']; - return timeA.compareTo(timeB); // Lexicographical sorting works for YYYY-MM + return timeA + .compareTo(timeB); // Lexicographical sorting works for YYYY-MM }); // If no valid TIME_PERIOD with a month is found, return original response - List> finalData = sortedData.isNotEmpty ? sortedData : List.from(chartData['response']); - + List> finalData = sortedData.isNotEmpty + ? sortedData + : List.from(chartData['response']); for (var entry in finalData) { - var xValue = entry['ObsKey'][groupByKey]; var yValue = entry['ObsValue']['Value']; if (xValue != null && yValue != null) { @@ -1345,9 +1706,10 @@ class ChartWidget extends StatelessWidget { child: BarChart( BarChartData( // alignment: BarChartAlignment.spaceAround, - maxY: yAxisData.isNotEmpty - ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 - : 10, + // maxY: yAxisData.isNotEmpty + // ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + // : 10, + maxY: _calculateMaxY(chartData), rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( enabled: false, @@ -1355,15 +1717,21 @@ class ChartWidget extends StatelessWidget { touchTooltipData: BarTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, - tooltipPadding: const EdgeInsets.all(8), - tooltipMargin: 16, + tooltipPadding: const EdgeInsets.all(5), + tooltipMargin: 5, getTooltipColor: (group) => Colors.transparent, getTooltipItem: (group, groupIndex, rod, rodIndex) { if (rod.toY == 0) return null; return BarTooltipItem( - formatNumber(rod.toY), - const TextStyle( + // formatNumber(rod.toY), + (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null) + ? formatNumberConversion( + rod.toY, chartConversion, number_format) + : formatNumber(rod.toY), + TextStyle( color: Colors.black, fontSize: 12, fontWeight: FontWeight.w400, @@ -1407,10 +1775,10 @@ class ChartWidget extends StatelessWidget { xAxisData[value.toInt()], textAlign: TextAlign.right, style: const TextStyle( - fontSize: 12, + fontSize: 10, ), softWrap: true, - maxLines: 3, + maxLines: 2, ), ), ), @@ -1442,12 +1810,12 @@ class ChartWidget extends StatelessWidget { xAxisData.length, (index) => BarChartGroupData( x: index, - // barsSpace: 2, + barsSpace: 20, barRods: [ BarChartRodData( toY: yAxisData[index], // color: Colors.blueAccent, - color: bodyColor, + color: uniqueColors[1], borderRadius: BorderRadius.circular(4), width: 20, ), @@ -1465,8 +1833,43 @@ class ChartWidget extends StatelessWidget { const double barWidth = 30; // Width for each bar, including spacing return totalBars * barWidth; // Calculate total chart width } + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + + if (kDebugMode) { + print('Converted barColorsMap: $barColorsMap'); + print('hasBarColors: $hasBarColors'); + } print('multi_bar'); + if (chartData.containsKey('chart_bar_color')) { + print('bar_colors - $chartBarColors'); + } + if (kDebugMode) { + print('chartBarColors: $chartBarColors'); + print('chartBarColors Type: ${chartBarColors.runtimeType}'); + print('chartBarColors keys: ${chartBarColors?.keys}'); + print('chartBarColors is null? ${chartBarColors == null}'); + print('chartBarColors is empty? ${chartBarColors?.isEmpty}'); + } String cropKey = chartData['chart_type_json']['x_sub_group']; // Group crops by CROP_TYPE @@ -1535,13 +1938,20 @@ class ChartWidget extends StatelessWidget { child: SizedBox( width: _calculateChartWidth( chartData), // Dynamically calculate the chart width + child: BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, maxY: _calculateMaxY( chartData), // Dynamically calculate max Y - barGroups: _buildHorizontalRotateBarGroups( - chartData, groupByValues), // Build bar groups + // barGroups: _buildHorizontalRotateBarGroups( + // chartData, groupByValues), // Build bar groups + barGroups: hasBarColors + ? _buildHorizontalRotateBarGroupsBarColors( + chartData, groupByValues, parsedChartBarColors) + : _buildHorizontalRotateBarGroups( + chartData, groupByValues), + titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), @@ -1549,7 +1959,7 @@ class ChartWidget extends StatelessWidget { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 80, // Added space for rotated titles + reservedSize: 130, // Added space for rotated titles getTitlesWidget: (value, meta) { if (value < groupByValues.length) { String title = @@ -1560,30 +1970,35 @@ class ChartWidget extends StatelessWidget { : title; return Padding( - padding: const EdgeInsets.only(top: 8.0,left: 30.0), + padding: const EdgeInsets.only( + top: 8.0, left: 95.0), child: SizedBox( - width: 60, // Limit width to force wrapping + width: + 80, // Limit width to force wrapping child: Transform.rotate( angle: -1.5, child: TooltipTheme( data: TooltipThemeData( decoration: BoxDecoration( - color: Colors.blueGrey[ - 800], // Change background color + color: Colors + .black, // Change background color + // color: Colors.blueGrey[800], // Change background color borderRadius: BorderRadius.circular( 8), // Optional: rounded corners ), textStyle: TextStyle( - color: Colors.white), // Change text color + color: Colors + .white), // Change text color ), child: Tooltip( message: title, child: Text( - displayTitle, + title, softWrap: true, - maxLines:2 , + maxLines: 2, + textAlign: TextAlign.end, style: TextStyle(fontSize: 10), - overflow: TextOverflow.ellipsis, + // overflow: TextOverflow.ellipsis, )), ), ))); @@ -1596,6 +2011,7 @@ class ChartWidget extends StatelessWidget { // ), // ); } + return const SizedBox.shrink(); }, ), @@ -1606,8 +2022,11 @@ class ChartWidget extends StatelessWidget { AxisTitles(sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: false), + barTouchData: BarTouchData( + enabled: true, touchTooltipData: BarTouchTooltipData( + getTooltipColor: (group) => Colors.black, tooltipHorizontalAlignment: FLHorizontalAlignment.center, tooltipRoundedRadius: 8, @@ -1616,11 +2035,16 @@ class ChartWidget extends StatelessWidget { fitInsideVertically: true, tooltipPadding: EdgeInsets.all(8), tooltipMargin: 16, + // Only show tooltip when touched getTooltipItem: (group, groupIndex, rod, rodIndex) { - if (rod.toY == 0 || touchedGroupIndex == -1) { - return null; // Don't show the tooltip if the value is 0 or there's no touch + // if (rod.toY == 0 || touchedGroupIndex == -1) { + // return null; // Don't show the tooltip if the value is 0 or there's no touch + // } + if (rod.toY == 0) { + return null; // Don't show for zero values } + if (groupIndex == touchedGroupIndex) { // Get the group label dynamically String groupLabel = @@ -1651,19 +2075,6 @@ class ChartWidget extends StatelessWidget { // 'Groupcrop: $crop'); double value = rod.toY; - String formattedValue; - if (value >= 1000000) { - formattedValue = - (value / 1000000).toStringAsFixed(1) + 'M'; - } else if (value >= 1000) { - formattedValue = - (value / 1000).toStringAsFixed(1) + 'K'; - } else { - formattedValue = value.toStringAsFixed( - 0); // for values smaller than 1000 - } - // print('Cropvaluevalue: $groupLabel\n$crop $value $formattedValue'); - return BarTooltipItem( '$groupLabel\n$crop', const TextStyle( @@ -1672,10 +2083,10 @@ class ChartWidget extends StatelessWidget { ), children: [ TextSpan( - // text: ' Value: ${rod.toY}', - text: ' Value: $formattedValue', + text: + '- ${(chartConversion != null && chartConversion.isNotEmpty && number_format != null) ? formatNumberConversion(rod.toY, chartConversion, number_format) : formatNumber(rod.toY)}', style: const TextStyle( - color: Colors.yellow, + color: Colors.white, fontWeight: FontWeight.w500, ), ), @@ -1744,6 +2155,25 @@ class ChartWidget extends StatelessWidget { ((numberOfBars * barHeight) + ((numberOfBars - 1) * barSpacing)) .clamp(minHeight, maxHeight); + var chartBarColors = chartData['chart_bar_color'] ?? {}; + Map barColorsMap = {}; + chartBarColors.forEach((key, value) { + barColorsMap[key] = value.toString(); // Ensure values are strings + }); + bool hasBarColors = barColorsMap.isNotEmpty; + + var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; + + // Convert LinkedMap to Map + Map chartBarColrs = + Map.from(chartBarColorsRaw); + + // Now map to Color + Map parsedChartBarColors = + chartBarColrs.map((key, value) { + return MapEntry(key, _hexToColor(value)); + }); + return Column(children: [ Text( chartData['chart_heading'] ?? '', @@ -1777,12 +2207,14 @@ class ChartWidget extends StatelessWidget { child: Stack(children: [ BarChart( BarChartData( - maxY: 400000, + // maxY: 400000, + maxY: _calculateMaxY(chartData), rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( // enabled: true, enabled: false, touchTooltipData: BarTouchTooltipData( + // getTooltipColor: (group) => Colors.black12, getTooltipColor: (group) => Colors.transparent, tooltipHorizontalAlignment: FLHorizontalAlignment.center, @@ -1790,7 +2222,8 @@ class ChartWidget extends StatelessWidget { fitInsideHorizontally: true, // Ensure it fits within the screen fitInsideVertically: true, - tooltipPadding: EdgeInsets.all(8), + tooltipPadding: EdgeInsets.only( + left: 3, right: 3, top: 0.5, bottom: 1), tooltipMargin: 16, // Only show tooltip when touched getTooltipItem: (group, groupIndex, rod, rodIndex) { @@ -1826,13 +2259,35 @@ class ChartWidget extends StatelessWidget { 0); // for values smaller than 1000 } + // return BarTooltipItem( + // // '$groupLabel\n$crop', + // '$formattedValue', + // const TextStyle( + // color: Colors.black, + // fontWeight: FontWeight.w400, + // fontSize: 12, + // ), + // ); + return BarTooltipItem( - '$formattedValue', + '$formattedValue', // This is your main tooltip text + const TextStyle( color: Colors.black, fontWeight: FontWeight.w400, fontSize: 12, ), + children: [ + TextSpan( + text: '', // Add extra information here + // text: '\n$crop', // Add extra information here + style: TextStyle( + color: Colors.grey[ + 700], // Optional: Different color for extra text + fontSize: 8, + ), + ), + ], ); // return BarTooltipItem( @@ -1950,16 +2405,26 @@ class ChartWidget extends StatelessWidget { ); }, ), - barGroups: _buildHorizontalRotateBarGroups( - chartData, groupByValues), + // barGroups: _buildHorizontalRotateBarGroups( + // chartData, groupByValues), + + barGroups: hasBarColors + ? _buildHorizontalRotateBarGroupsBarColors( + chartData, groupByValues, parsedChartBarColors) + : _buildHorizontalRotateBarGroups( + chartData, groupByValues), + alignment: BarChartAlignment.spaceAround, ), ), ])), - - // ), - // ) ), + + // ), + // ) + + const SizedBox(height: 10), // Space between chart and legend + _buildChartLegend(groupedCrops, parsedChartBarColors, uniqueColors), ]); default: @@ -2026,7 +2491,7 @@ class ChartWidget extends StatelessWidget { yearGenderMap[year] = {'M': 0.0, 'F': 0.0}; } - if (gender == 'Male'|| gender == 'ذكر') { + if (gender == 'Male' || gender == 'ذكر') { yearGenderMap[year]!['M'] = value; } else if (gender == 'Female' || gender == 'أنثى') { yearGenderMap[year]!['F'] = value; @@ -2042,16 +2507,21 @@ class ChartWidget extends StatelessWidget { } }); - // Add a line for the ratio data lineBars.add( LineChartBarData( spots: spots, isCurved: true, - color: Colors.blue, // Set color for the ratio line + // color: bodyColor?? Colors.grey, // Set color for the ratio line + color: uniqueColors[1] ?? Colors.grey, barWidth: 3, isStrokeCapRound: true, - belowBarData: BarAreaData(show: true), + belowBarData: BarAreaData( + show: true, + // color: bodyColor , + // color: (bodyColor ?? Colors.grey).withAlpha(100), + color: (uniqueColors[1] ?? Colors.grey).withAlpha(100), + ), ), ); @@ -2094,25 +2564,41 @@ class ChartWidget extends StatelessWidget { throw FormatException("Invalid TIME_PERIOD format: $timePeriod"); } - List lineBarsData(List filteredData, - Set groupByValues, String groupByKey) { + List lineBarsData( + List filteredData, + Set groupByValues, + String groupByKey, + Map chartBarColors) { List lineBars = []; + print('LineParsedChartBarColors $chartBarColors'); + print('filteredData $filteredData'); print('filteredData222 $groupByValues'); - final List uniqueColors = [ - Color(0xFF6097CD), - Color(0xFFD086A7), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; + // final List uniqueColors = [ + // Color(0xFF6097CD), + // Color(0xFFD086A7), + // Color(0xFF98BCE5), + // Color(0xFFA7B5C5), + // Color(0xFFBED3EC), + // Color(0xFFD4E3F4), + // ]; // Create a map to assign colors to each group in order Map groupColorMap = {}; int colorIndex = 0; + + // for (String group in groupByValues) { + // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); + // colorIndex++; + // } for (String group in groupByValues) { - groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + if (chartBarColors.isNotEmpty && chartBarColors.containsKey(group)) { + // Use predefined color from chartBarColors + groupColorMap[group] = chartBarColors[group]!; + } else { + groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; + } colorIndex++; } @@ -2163,7 +2649,8 @@ class ChartWidget extends StatelessWidget { } else if (value is double) { yValue = value; } else if (value is String) { - yValue = double.tryParse(value) ?? 0.0; // Handle invalid strings safely + yValue = + double.tryParse(value) ?? 0.0; // Handle invalid strings safely } else { throw Exception("Unexpected value type: ${value.runtimeType}"); } @@ -2192,6 +2679,7 @@ class ChartWidget extends StatelessWidget { // Add a line for this group lineBars.add( LineChartBarData( + show: true, spots: spots, isCurved: true, color: groupColorMap[group], @@ -2208,7 +2696,10 @@ class ChartWidget extends StatelessWidget { // Helper functions for chart styles LineTouchData lineTouchData1() { + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; return LineTouchData( + touchSpotThreshold: 10, touchTooltipData: LineTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.7), // Tooltip background tooltipRoundedRadius: 8, @@ -2216,6 +2707,7 @@ class ChartWidget extends StatelessWidget { fitInsideVertically: true, tooltipPadding: EdgeInsets.all(8), tooltipMargin: 16, // Adds margin to prevent clipping + getTooltipColor: (spot) => Colors.black, getTooltipItems: (List lineBarsSpot) { return lineBarsSpot.map((lineBarSpot) { Color lineColor = lineBarSpot.bar is LineChartBarData @@ -2231,7 +2723,13 @@ class ChartWidget extends StatelessWidget { color: lineColor), // Set circle color to match bar ), TextSpan( - text: formatNumber(lineBarSpot.y), + // text: formatNumber(lineBarSpot.y), + text: (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null) + ? formatNumberConversion( + lineBarSpot.y, chartConversion, number_format) + : formatNumber(lineBarSpot.y), // text: ' ${lineBarSpot.y}', // Keep the value white style: const TextStyle(color: Colors.white), ), @@ -2244,6 +2742,78 @@ class ChartWidget extends StatelessWidget { ); } + // + // LineTouchData lineTouchData1() { + // var chartConversion = chartData['chart_type_json']['conversion']; + // var number_format = chartData['chart_type_json']['number_format']; + // return LineTouchData( + // enabled: true, + // handleBuiltInTouches: true, + // touchCallback: + // (FlTouchEvent event, LineTouchResponse? response) { + // if (response == null || response.lineBarSpots == null) { + // return; + // } + // // if (event is FlTapUpEvent) { + // // final spotIndex = response.lineBarSpots!.first.spotIndex; + // // setState(() { + // // if (showingTooltipOnSpots.contains(spotIndex)) { + // // showingTooltipOnSpots.remove(spotIndex); + // // } else { + // // showingTooltipOnSpots.add(spotIndex); + // // } + // // }); + // // } + // }, + // mouseCursorResolver: + // (FlTouchEvent event, LineTouchResponse? response) { + // if (response == null || response.lineBarSpots == null) { + // return SystemMouseCursors.basic; + // } + // return SystemMouseCursors.click; + // }, + // touchSpotThreshold: 5, + // touchTooltipData: LineTouchTooltipData( + // tooltipRoundedRadius: 0, + // getTooltipColor: (spot) => Colors.transparent, + // fitInsideHorizontally: true, // Ensure it fits within the screen + // fitInsideVertically: true, + // tooltipPadding: EdgeInsets.all(8), + // tooltipMargin: 16, + // getTooltipItems: (List touchedSpots) { + // return touchedSpots.map((LineBarSpot touchedSpot) { + // return LineTooltipItem( + // // formatNumber(touchedSpot.y), + // (chartConversion != null && + // chartConversion.isNotEmpty && + // number_format != null) + // ? formatNumberConversion(touchedSpot.y, chartConversion, number_format) + // : formatNumber(touchedSpot.y), + // TextStyle( + // color: Colors.black87, + // fontWeight: FontWeight.w400, + // fontSize: 15, + // ), + // ); + // }).toList(); + // }, + // ), + // getTouchedSpotIndicator: ( + // _, + // indicators, + // ) { + // return indicators + // .map((int index) => const TouchedSpotIndicatorData( + // FlLine(color: Colors.transparent), + // FlDotData(show: true), + // )) + // .toList(); + // }, + // distanceCalculator: (Offset touchPoint, Offset spotPixelCoordinates) => + // (touchPoint - spotPixelCoordinates).distance, + // ); + // } + FlGridData gridData() { return FlGridData( show: false, @@ -2270,80 +2840,127 @@ class ChartWidget extends StatelessWidget { reservedSize: 40, getTitlesWidget: (value, meta) { // Format values as millions (M) - String formattedValue; + // String formattedValue; + String? formattedValue; print('LineTrend2val1 - $value '); - if (value % 10 == 0) { - // Check if the value is in the millions or thousands range - if (kDebugMode) { - print('LineTrend2valKmode - $value '); - } - if (value >= 1000000000000) { + // if (value % 10 == 0) { + // // Check if the value is in the millions or thousands range + // if (kDebugMode) { + // print('LineTrend2valKmode - $value '); + // } + // if (value >= 1000000000000) { + // formattedValue = + // '${(value / 1000000000000).toStringAsFixed(0)}T'; + // } else if (value >= 1000000000) { + // formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; + // } else if (value >= 1000000) { + // formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; + // } else if (value >= 1000) { + // formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; + // } else { + // formattedValue = value.toStringAsFixed( + // 0); // No decimals for values less than 1000 + // } + // + // // Skip rendering if the formatted value is the same as the last one + // if (_lastFormattedValue == formattedValue) { + // return const SizedBox.shrink(); // Empty widget for duplicates + // } + // // Update the last formatted value for the next comparison + // _lastFormattedValue = formattedValue; + // + // print('LINETREND_POP- $formattedValue'); + // // if (int.parse(formattedValue.replaceAll(RegExp(r'[^0-9]'), '')) % 10 != 0) { + // // return const SizedBox.shrink(); + // // } + // + // + // // print('formattedValueLine - $formattedValue'); + // return Text( + // formattedValue, + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 10, + // )); + // + // } + + // Ensure value is a multiple of 10k, 2M, 100B, or 1T before processing + if (value >= 1000000000000) { + // Trillions (T) + if (value % 1000000000000 == 0) { + // Only multiples of 1T formattedValue = '${(value / 1000000000000).toStringAsFixed(0)}T'; - } else if (value >= 1000000000) { + } + } else if (value >= 1000000000) { + // Billions (B) + if (value % 100000000000 == 0) { + // Only multiples of 100B formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; - } else if (value >= 1000000) { + } + } else if (value >= 1000000) { + // Millions (M) + if (value % 2000000 == 0) { + // Only multiples of 2M formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; - } else if (value >= 1000) { + } + } else if (value >= 10000) { + // Thousands (k) + if (value % 10000 == 0) { + // Only multiples of 10k formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; - } else { - formattedValue = value.toStringAsFixed( - 0); // No decimals for values less than 1000 } - - // Skip rendering if the formatted value is the same as the last one - if (_lastFormattedValue == formattedValue) { - return const SizedBox.shrink(); // Empty widget for duplicates - } - // Update the last formatted value for the next comparison - _lastFormattedValue = formattedValue; - - // print('formattedValueLine - $formattedValue'); - return Text(formattedValue, textAlign: TextAlign.center, - style: TextStyle( - fontSize: 10, - - )); } - return const SizedBox.shrink(); - // return Text( - // 'LINETRENTD2', - // style: TextStyle(color: Colors.black, fontSize: 12), - // ); + print( + 'Value: $value | Formatted: ${formattedValue ?? "Hidden"}'); // Debugging + + if (formattedValue != null) { + return Text( + formattedValue, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 10), + ); + } + + return const SizedBox.shrink(); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( - showTitles: true, - interval: null, - // interval: 10, // Ensure each year is shown only once - getTitlesWidget: (value, meta) { - // print('BtmTiles :-$value'); - // print('BtmTilesmeta :-$meta'); - // if (xValues.contains(value)) - double closestValue = findClosest(value, xValues); - if ((closestValue - value).abs() < 0.15) { - // print("Bottomtiles"); - // print(value); - return Padding( - padding: const EdgeInsets.only(top: 1.1), - child: Transform.rotate( - angle: 0.0, - // angle: -0.5, // Slight rotation to improve readability - child: Text( - value.toInt().toString(), - style: TextStyle(color: Colors.black, fontSize: 12), + showTitles: true, + interval: null, + // interval: 10, // Ensure each year is shown only once + getTitlesWidget: (value, meta) { + // print('BtmTiles :-$value'); + // print('BtmTilesmeta :-$meta'); + // if (xValues.contains(value)) + double closestValue = findClosest(value, xValues); + if ((closestValue - value).abs() < 0.15) { + // print("Bottomtiles"); + // print(value); + return Padding( + padding: const EdgeInsets.only(top: 3.1, left: 20.0), + child: SizedBox( + width: 40, + child: Transform.rotate( + // angle: 0.0, + angle: -1.5, // Slight rotation to improve readability + child: Text( + value.toInt().toString(), + style: TextStyle(color: Colors.black, fontSize: 12), + ), + ), ), - ), - ); - } else { - return SizedBox.shrink(); // Hide non-relevant labels - } - }, - ), + ); + } else { + return SizedBox.shrink(); // Hide non-relevant labels + } + }, + reservedSize: 40), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), // Hide top titles @@ -2369,14 +2986,31 @@ class ChartWidget extends StatelessWidget { // End Of line trend chart // Define the list of unique colors - final List uniqueColors = [ - Color(0xFF648CBA), - Color(0xFF90B0D5), - Color(0xFF98BCE5), - Color(0xFFA7B5C5), - Color(0xFFBED3EC), - Color(0xFFD4E3F4), - ]; + // final List uniqueColors1 = [ + // Color(0xFF648CBA), + // Color(0xFF90B0D5), + // Color(0xFF98BCE5), + // Color(0xFFA7B5C5), + // Color(0xFFBED3EC), + // Color(0xFFD4E3F4), + // ]; + // + // final List uniqueColors2 = [ + // Color(0xFFD9BB99), + // Color(0xFF87766E), + // Color(0xFFC6A885), + // Color(0xFFA28565), + // Color(0xFFBFB29B), + // Color(0xFFE8D8BB), + // ]; + // + // final List uniqueColors = [ + // Color(0xFF376C79), + // Color(0xFF578D9C), + // Color(0xFF7DAFBC), + // Color(0xFF86C7D9), + // Color(0xFF989898), + // ]; Color _getColorForGroup(int index) { // Use the index to get a color from the uniqueColors list @@ -2404,7 +3038,6 @@ class ChartWidget extends StatelessWidget { value = double.tryParse(rawValue.toString()) ?? 0.0; } - print('flStackedBar3.1'); // print('fl_groupValue $groupValue'); @@ -2427,7 +3060,6 @@ class ChartWidget extends StatelessWidget { List timePeriods = groupedData.values.first.keys.toList(); print('timePeriods $timePeriods'); - // double barWidth = calculateBarWidth(context, timePeriods.length); // For each time period, generate a BarChartGroupData @@ -2466,7 +3098,8 @@ class ChartWidget extends StatelessWidget { width: 20, // width: barWidth, borderRadius: BorderRadius.zero, - color: Colors.black // This will act as a container for stacked items + color: + Colors.black // This will act as a container for stacked items ), ], ); @@ -2586,13 +3219,14 @@ class ChartWidget extends StatelessWidget { // double value = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; double value = (entry['ObsValue']['Value'] as num?)?.toDouble() ?? 0; - if (value > maxY) { maxY = value; } } - return maxY * 1.1; // Add 10% buffer for better visualization + // Apply different buffers based on maxY value + return maxY < 500 ? maxY * 3.5 : maxY * 1.5; + // return maxY * 1.5; // Add 10% buffer for better visualization } List _buildHorizontalRotateBarGroups( @@ -2618,19 +3252,29 @@ class ChartWidget extends StatelessWidget { double value = double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; print('multi_bar1.1'); - final List uniqueColorsForTwo = [ - Color(0xFF648CBA), - Color(0xFF90B0D5), - ]; + // final List uniqueColorsForTwo = [ + // Color(0xFF648CBA), + // Color(0xFF90B0D5), + // ]; + + // Use bodyColor instead of hardcoded colors + final barColor; if (chartData['dataset'] == 'general_education' || chartData['dataset'] == 'higher_education' || chartData['dataset'] == 'air_transport' || chartData['dataset'] == 'labour_force' || chartData['dataset'] == 'gdp' || + chartData['dataset'] == 'hotels' || + chartData['dataset'] == 'hotel_guests' || + chartData['dataset'] == 'health_services' || chartData['dataset'] == 'clinics') { barColor = uniqueColorsForTwo[colorIndex % uniqueColorsForTwo.length]; colorIndex++; + } else if (chartData['dataset'] == 'cropsk') { + barColor = + uniqueColorsForThree[colorIndex % uniqueColorsForThree.length]; + colorIndex++; } else { barColor = uniqueColors[colorIndex % uniqueColors.length]; colorIndex++; @@ -2663,6 +3307,73 @@ class ChartWidget extends StatelessWidget { return barGroups; } + List _buildHorizontalRotateBarGroupsBarColors( + dynamic chartData, + Set groupByValues, + Map chartBarColors) { + List responseData = chartData['response']; + List barGroups = []; + // Define two shades for alternating colors + List uniqueColorsForTwo = [ + chartBarColors['default'] ?? Colors.blue, // Original color + (chartBarColors['default'] ?? Colors.blue) + .withAlpha(180) // Slightly modified shade + ]; + + int colorIndex = 0; + // Iterate through groupByValues and populate BarChartGroupData + for (int i = 0; i < groupByValues.length; i++) { + String groupValue = groupByValues.elementAt(i); + + // Filter data for the current group + List groupData = responseData.where((entry) { + return entry['ObsKey'][chartData['group_by']] == groupValue; + }).toList(); + + // Create BarChartRodData for each bar in the group + List barRods = groupData.map((data) { + double value = + double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; + + // Get gender-based color + String gender = data['ObsKey']['GENDER'] ?? ""; + + List barGroups = []; + + // Determine color dynamically + Color barColor; + if (chartData['dataset'] == 'health_services') { + barColor = uniqueColorsForTwo[ + colorIndex % 2]; // Alternate between two colors + colorIndex++; // Update index for next bar + } else { + barColor = chartBarColors[gender] ?? + Colors.grey; // Default gender-based color + } + + print('GEG- $gender'); + + return BarChartRodData( + toY: value, // Use the parsed value + color: barColor, // Gender-based dynamic color + width: 20, + ); + }).toList(); + + // Add BarChartGroupData for the group + barGroups.add( + BarChartGroupData( + x: i, + barRods: barRods, + showingTooltipIndicators: + List.generate(barRods.length, (index) => index), + ), + ); + } + + return barGroups; + } + /// End horizontal rotate and fl_multi_bar bar @override @@ -2671,6 +3382,47 @@ class ChartWidget extends StatelessWidget { } } +Widget _buildChartLegend( + Map> groupedCrops, + Map parsedChartBarColors, + List uniqueColors, +) { + // Extract unique crop types + Set uniqueCropTypes = + groupedCrops.values.expand((list) => list).toSet(); + + return Wrap( + alignment: WrapAlignment.center, + spacing: 12, + runSpacing: 6, + children: uniqueCropTypes.map((cropType) { + // Color cropColor = parsedChartBarColors[cropType] ?? Colors.grey; // Fetch color for each crop type + int index = uniqueCropTypes + .toList() + .indexOf(cropType); // Get index for cycling colors + Color cropColor = parsedChartBarColors.isNotEmpty && + parsedChartBarColors.containsKey(cropType) + ? parsedChartBarColors[cropType]! + : uniqueColors[index % uniqueColors.length]; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircleAvatar( + radius: 6, + backgroundColor: cropColor, + ), + const SizedBox(width: 4), + Text( + cropType, + style: const TextStyle(fontSize: 12), + ), + ], + ); + }).toList(), + ); +} + class BarValuePainter extends CustomPainter { BarValuePainter(this.barGroups, this.constraints); final List barGroups; diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index bbdc7403..e70c4ee6 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -75,7 +75,9 @@ class _ProfileScreenState extends ConsumerState { 'India', 'Canada' ]; + final List preferred_language = ['English', 'Arabic']; String? _selectedCountry; + String? _selectedLanguage; bool isChecked = false; bool showError = false; final _picker = ImagePicker(); @@ -86,6 +88,7 @@ class _ProfileScreenState extends ConsumerState { late Future userDetails; bool _isHoveringDate = false; bool _isHoveringDropdown = false; + bool _isHoveringPreferredLang = false; @override void initState() { @@ -187,10 +190,10 @@ class _ProfileScreenState extends ConsumerState { _isLoading = true; // Start loading }); final XFile? pickedFile = - await _picker.pickImage(source: ImageSource.gallery); + await _picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { final String fileExtension = - pickedFile.path.split('.').last.toLowerCase(); + pickedFile.path.split('.').last.toLowerCase(); if (fileExtension == 'jpg' || fileExtension == 'jpeg' || fileExtension == 'png' || @@ -227,7 +230,8 @@ class _ProfileScreenState extends ConsumerState { builder: (context, child) { return Theme( data: Theme.of(context).copyWith( - dialogBackgroundColor: Colors.white, // Background color of the date picker + dialogBackgroundColor: + Colors.white, // Background color of the date picker textButtonTheme: TextButtonThemeData( style: ButtonStyle( shape: WidgetStateProperty.all(RoundedRectangleBorder( @@ -237,8 +241,10 @@ class _ProfileScreenState extends ConsumerState { ), datePickerTheme: DatePickerThemeData( confirmButtonStyle: ButtonStyle( - backgroundColor: WidgetStateProperty.all(Color(0xFFAA8E83)), // OK button background color - foregroundColor: WidgetStateProperty.all(Colors.white), // OK button text color + backgroundColor: WidgetStateProperty.all( + Color(0xFFAA8E83)), // OK button background color + foregroundColor: WidgetStateProperty.all( + Colors.white), // OK button text color // minimumSize: WidgetStateProperty.all(Size(150, 50)), minimumSize: WidgetStateProperty.all(Size(100, 40)), shape: WidgetStateProperty.all(RoundedRectangleBorder( @@ -247,12 +253,14 @@ class _ProfileScreenState extends ConsumerState { ), cancelButtonStyle: ButtonStyle( // backgroundColor: WidgetStateProperty.all(Colors.white), // Cancel button background color - foregroundColor: WidgetStateProperty.all(Color(0xFFAA8E83)), // Cancel button text color + foregroundColor: WidgetStateProperty.all( + Color(0xFFAA8E83)), // Cancel button text color // minimumSize: WidgetStateProperty.all(Size(150, 50)), minimumSize: WidgetStateProperty.all(Size(100, 40)), shape: WidgetStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), // Rounded corners - side: const BorderSide(color: Color(0xFFAA8E83), width: 2), // Outline color + side: const BorderSide( + color: Color(0xFFAA8E83), width: 2), // Outline color )), ), ), @@ -262,7 +270,6 @@ class _ProfileScreenState extends ConsumerState { }, ); - if (pickedDate != null && pickedDate != _selectedDate) { setState(() { _selectedDate = pickedDate; @@ -282,7 +289,8 @@ class _ProfileScreenState extends ConsumerState { // Check if the selected date is in the future if (selectedDate.isAfter(today)) { - return context.translate('Date of birth cannot be in the future','لا يمكن أن يكون تاريخ الميلاد في المستقبل'); + return context.translate('Date of birth cannot be in the future', + 'لا يمكن أن يكون تاريخ الميلاد في المستقبل'); } return null; } @@ -315,6 +323,7 @@ class _ProfileScreenState extends ConsumerState { // String email = 'surendarsuri30@gmail.com'; String dateOfBirth = _dateController.text; // in DD/MM/YYYY format String countryRegion = _selectedCountry ?? ''; + String preferredLang = _selectedLanguage == 'English' ? 'en' : 'ar'; bool termsAccepted = isChecked; // Parse the date from dd/MM/yyyy format and convert to yyyy-MM-dd @@ -324,13 +333,14 @@ class _ProfileScreenState extends ConsumerState { // Create a multipart request final uri = - Uri.parse('${_pb.baseUrl}/api/collections/users/records/$userID'); + Uri.parse('${_pb.baseUrl}/api/collections/users/records/$userID'); final request = http.MultipartRequest('PATCH', uri); // Add other fields request.fields['full_name'] = fullName; request.fields['dob'] = formattedDob; request.fields['country_region'] = countryRegion; + request.fields['language'] = preferredLang; request.fields['is_profile_completed'] = 'True'; // Add the file, if available @@ -371,6 +381,7 @@ class _ProfileScreenState extends ConsumerState { _fullNameController.clear(); _dateController.clear(); _selectedCountry = null; + _selectedLanguage = null; isChecked = false; // Reset profile image @@ -399,14 +410,15 @@ class _ProfileScreenState extends ConsumerState { 'Logout', 'تسجيل الخروج', ), - style: TextStyle(color: Color(0xFF898C81),fontWeight: FontWeight.w700), + style: + TextStyle(color: Color(0xFF898C81), fontWeight: FontWeight.w700), ), content: Text( context.translate( 'Are you sure you want to logout?', 'هل أنت متأكد أنك تريد تسجيل الخروج؟', ), - style: TextStyle(color:Color(0xFF898C81)), + style: TextStyle(color: Color(0xFF898C81)), ), actions: [ TextButton( @@ -414,7 +426,7 @@ class _ProfileScreenState extends ConsumerState { style: TextButton.styleFrom( // backgroundColor: // Color(0xFFAA8E83), // Set background color - side: BorderSide(color: Color(0xFFAA8E83), width: 2), + side: BorderSide(color: Color(0xFFAA8E83), width: 2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), // Set text color ), @@ -432,12 +444,10 @@ class _ProfileScreenState extends ConsumerState { TextButton( onPressed: () => logout(context), // Exit the app style: TextButton.styleFrom( - backgroundColor: - Color(0xFFAA8E83), // Set background color + backgroundColor: Color(0xFFAA8E83), // Set background color // foregroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(10.0), // Set text color + borderRadius: BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -560,7 +570,7 @@ class _ProfileScreenState extends ConsumerState { backgroundImage: _profileImage != null ? FileImage(_profileImage!) : AssetImage("assets/edit_profile/profile.png") - as ImageProvider, + as ImageProvider, child: Align( alignment: Alignment.bottomRight, child: GestureDetector( @@ -584,7 +594,7 @@ class _ProfileScreenState extends ConsumerState { child: CircularProgressIndicator( strokeWidth: 2, valueColor: - AlwaysStoppedAnimation(Colors.grey), + AlwaysStoppedAnimation(Colors.grey), ), ), ], @@ -673,19 +683,18 @@ class _ProfileScreenState extends ConsumerState { r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$"); if (!nameRegex.hasMatch(value)) { - return AppLocalizations.of(context)!.invalid_characters; + return AppLocalizations.of(context)! + .invalid_characters; } return null; }, controller: _fullNameController, focusNode: _focusNodes[2], decoration: InputDecoration( - enabledBorder:OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: MyTheme.topicColor( - IndicatorTopic.economy) + color: MyTheme.topicColor(IndicatorTopic.economy) .shade400, width: 1), // Enabled border ), @@ -695,7 +704,8 @@ class _ProfileScreenState extends ConsumerState { fontSize: 14, ), border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), + borderSide: BorderSide( + color: Color(0xFF7296BE), ), borderRadius: BorderRadius.circular(8), ), @@ -723,17 +733,17 @@ class _ProfileScreenState extends ConsumerState { controller: _dateController, focusNode: _focusNodes[3], decoration: InputDecoration( - enabledBorder:OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: MyTheme.topicColor( - IndicatorTopic.economy) - .shade400, + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade400, width: 1), // Enabled border ), border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), + borderSide: BorderSide( + color: Color(0xFF7296BE), ), borderRadius: BorderRadius.circular(8), ), @@ -748,10 +758,11 @@ class _ProfileScreenState extends ConsumerState { width: 45, padding: EdgeInsets.only(right: 1), alignment: - Alignment.center, // Center the icon vertically + Alignment.center, // Center the icon vertically child: Icon( Icons.keyboard_arrow_down_sharp, - color: _isHoveringDate ? Colors.black : Colors.grey, + color: + _isHoveringDate ? Colors.black : Colors.grey, ), ), ), @@ -773,35 +784,37 @@ class _ProfileScreenState extends ConsumerState { ), SizedBox(height: 10), MouseRegion( - onEnter: (_) => setState(() => _isHoveringDropdown = true), - onExit: (_) => setState(() => _isHoveringDropdown = false), + onEnter: (_) => + setState(() => _isHoveringDropdown = true), + onExit: (_) => + setState(() => _isHoveringDropdown = false), child: DropdownButtonFormField( value: _selectedCountry, decoration: InputDecoration( - enabledBorder:OutlineInputBorder( - borderRadius: - BorderRadius.circular(10), - borderSide: BorderSide( - color: MyTheme.topicColor( - IndicatorTopic.economy) - .shade400, - width: 1), // Enabled border - ), - border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade400, + width: 1), // Enabled border ), - borderRadius: BorderRadius.circular(8), - ), - labelText: context.translate('Select',' اختيار') - ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF7296BE), + ), + borderRadius: BorderRadius.circular(8), + ), + labelText: context.translate('Select', ' اختيار')), icon: Icon(Icons.keyboard_arrow_down_sharp, - color: - _isHoveringDropdown ? Colors.black : Colors.grey), + color: _isHoveringDropdown + ? Colors.black + : Colors.grey), items: _countries .map((item) => DropdownMenuItem( - value: item, - child: Text(item), - )) + value: item, + child: Text(item), + )) .toList(), onChanged: (String? newValue) { setState(() { @@ -811,6 +824,59 @@ class _ProfileScreenState extends ConsumerState { validator: _validateDropdown, ), ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.preferredLang, + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox(height: 10), + MouseRegion( + onEnter: (_) => + setState(() => _isHoveringPreferredLang = true), + onExit: (_) => + setState(() => _isHoveringPreferredLang = false), + child: DropdownButtonFormField( + value: _selectedLanguage, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: + MyTheme.topicColor(IndicatorTopic.economy) + .shade400, + width: 1), // Enabled border + ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF7296BE), + ), + borderRadius: BorderRadius.circular(8), + ), + labelText: context.translate('Select', ' اختيار')), + icon: Icon(Icons.keyboard_arrow_down_sharp, + color: _isHoveringPreferredLang + ? Colors.black + : Colors.grey), + items: preferred_language + .map((item) => DropdownMenuItem( + value: item, + child: Text(item), + )) + .toList(), + onChanged: (String? newValue) { + setState(() { + _selectedLanguage = newValue; + }); + }, + validator: _validateDropdown, + ), + ), SizedBox(height: 20), Row( children: [ @@ -823,53 +889,48 @@ class _ProfileScreenState extends ConsumerState { }); }, side: BorderSide( - color: - showError ? Color(0xFFb22222) : Color(0xFF7296BE), + color: showError + ? Color(0xFFb22222) + : Color(0xFF7296BE), width: 1, ), ), Expanded( child: Padding( padding: const EdgeInsets.only( - top: 14.0,), + top: 14.0, + ), child: Text.rich( TextSpan( - text: AppLocalizations.of( - context)! - .agree, + text: AppLocalizations.of(context)!.agree, // text: 'I agree to ', style: TextStyle( fontWeight: FontWeight.w500, fontSize: 14, color: Color( - 0xFF898C81,), // Change to your desired color + 0xFF898C81, + ), // Change to your desired color ), children: [ TextSpan( - recognizer:TapGestureRecognizer() - ..onTap = () => context.push('/terms&conditions'), - text: AppLocalizations.of( - context)! + recognizer: TapGestureRecognizer() + ..onTap = () => + context.push('/terms&conditions'), + text: AppLocalizations.of(context)! .terms_conditions, // text: 'Terms & Conditions', style: TextStyle( fontSize: 14, - fontWeight: - FontWeight.bold, - color: - Color(0xFF985400), + fontWeight: FontWeight.bold, + color: Color(0xFF985400), // Makes the text bold // decoration: // TextDecoration.underline, - decorationColor: - Color(0xFF985400), - decorationThickness: - 1), + decorationColor: Color(0xFF985400), + decorationThickness: 1), ), TextSpan( - text: AppLocalizations.of( - context)! - .t_and, + text: AppLocalizations.of(context)!.t_and, style: TextStyle( color: Color( 0xFF898C81), // Change to your desired color @@ -877,31 +938,26 @@ class _ProfileScreenState extends ConsumerState { ), TextSpan( recognizer: TapGestureRecognizer() - ..onTap = () => context.push('/privacy_policy'), - text: AppLocalizations.of( - context)! + ..onTap = + () => context.push('/privacy_policy'), + text: AppLocalizations.of(context)! .privacy_policy, style: TextStyle( - fontWeight: - FontWeight.bold, + fontWeight: FontWeight.bold, fontSize: 14, fontFamily: 'Roboto', - color: - Color(0xFF985400), + color: Color(0xFF985400), // decoration: // TextDecoration.underline, - decorationColor: - Color(0xFF648CBA), - decorationThickness: - 1), + decorationColor: Color(0xFF648CBA), + decorationThickness: 1), ), TextSpan( - text: AppLocalizations.of( - context)! + text: AppLocalizations.of(context)! .conditions, style: TextStyle( fontWeight: FontWeight.w500, - fontSize: 14, + fontSize: 14, color: Color( 0xFF898C81), // Change to your desired color ), @@ -918,13 +974,14 @@ class _ProfileScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.only(left: 10.0, right: profileLocale - ?.languageCode == - 'ar' - ? 15 - : 0.0,), + padding: EdgeInsets.only( + left: 10.0, + right: profileLocale?.languageCode == 'ar' + ? 15 + : 0.0, + ), child: Text( - context.translate('Required', 'مطلوب'), + context.translate('Required', 'مطلوب'), style: TextStyle( color: Color(0xFFb22222), fontSize: 12, @@ -960,7 +1017,7 @@ class _ProfileScreenState extends ConsumerState { ElevatedButton( onPressed: () { setState(() { - hasValidated = true; + hasValidated = true; }); if ((_formKey.currentState?.validate() ?? false) && (isChecked)) { @@ -969,7 +1026,7 @@ class _ProfileScreenState extends ConsumerState { } else { setState(() { showError = - !isChecked; // Show error if the checkbox is not checked + !isChecked; // Show error if the checkbox is not checked }); } }, @@ -982,7 +1039,7 @@ class _ProfileScreenState extends ConsumerState { ), child: Row( mainAxisAlignment: - MainAxisAlignment.center, // Center the content + MainAxisAlignment.center, // Center the content children: [ Text( AppLocalizations.of(context)!.save, @@ -1041,10 +1098,10 @@ class ConfirmationDialog extends StatelessWidget { TextSpan( text: AppLocalizations.of(context)!.name, style: - TextStyle(fontWeight: FontWeight.w700), // Bold for "name" + TextStyle(fontWeight: FontWeight.w700), // Bold for "name" ), TextSpan( - text: context.translate(' or ','أو'), + text: context.translate(' or ', 'أو'), ), TextSpan( text: AppLocalizations.of(context)!.dob, @@ -1111,4 +1168,4 @@ class ConfirmationDialog extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/components/constant/constant.dart b/lib/presentation/components/constant/constant.dart index 4b65a141..55e3e055 100644 --- a/lib/presentation/components/constant/constant.dart +++ b/lib/presentation/components/constant/constant.dart @@ -7,12 +7,14 @@ const Color environmentboldColor = Color(0xFF90B0D5); const TextStyle subtitleStyle = TextStyle( fontFamily: 'Roboto', // Font family set to Roboto fontWeight: FontWeight.w400, // Regular weight (w400) - fontSize: 11, +// fontSize: 11, + fontSize: 11 * 1.2, // Increase by 20% color: Color(0xFF8E8E8E) // Font size 11 ); const TextStyle robotoRegular11 = TextStyle( fontFamily: 'Roboto', // Font family set to Roboto fontWeight: FontWeight.w400, // Regular weight (w400) - fontSize: 11, + fontSize: 11 * 1.1, color: Color(0xFF000000) // Font size 11 + ); diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 7ee43de6..557f0ae3 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:external_repos/external_repos.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -27,7 +28,6 @@ import 'package:pocketbase/pocketbase.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:uae_stat/config/api_config.dart'; - import '../../components/indicators/locale_provider.dart'; import '../../components/my_toggle.dart'; @@ -42,7 +42,8 @@ class LoginRoute extends HookConsumerWidget { dynamic userData; String? role; int? loginCount; - + final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; + // final FirebaseAuth _auth = FirebaseAuth.instance; static final formKey = GlobalKey(); Future profileStatus(String userId) async { @@ -97,6 +98,89 @@ class LoginRoute extends HookConsumerWidget { await prefs.setString('login_count', count.toString()); } + // Future getDeviceToken() async { + // String? token = await FirebaseMessaging.instance.getToken(); + // print("FCM Token: $token"); + // } + + Future getDeviceToken(BuildContext context, String userId) async { + try { + // await _firebaseMessaging.requestPermission(); + print("FCM Token Entry"); + String? token = await FirebaseMessaging.instance.getToken(); + print("FCM Token: $token"); + + if (token != null) { + await updateDeviceToken(userId, token); + + // Automatically copy to clipboard + // Clipboard.setData(ClipboardData(text: token)); + + // Show token in Alert Dialog + // if (context.mounted) { + // showDialog( + // context: context, + // builder: (context) { + // return AlertDialog( + // title: Text("FCM Token"), + // content: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // SelectableText(token), // Allows manual selection + // SizedBox(height: 10), + // Text( + // "Token copied to clipboard ✅", + // style: TextStyle(color: Colors.green), + // ), + // ], + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: Text("OK"), + // ), + // ], + // ); + // }, + // ); + // } + } + } catch (e) { + print("Error getting FCM Token: $e"); + } + } + + Future updateDeviceToken(String userId, String token) async { + final url = "${pb.baseUrl}/api/collections/users/records/$userId"; + + try { + // Authenticate admin (Persist session globally instead) + await pb.admins.authWithPassword( + 'pb@venbainfotech.com', + 'pb@venbainfotech.com', + ); + + final response = await http.patch( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', // Add this + 'Authorization': pb.authStore.token, // Correct way to send auth token + }, + body: jsonEncode({ + "device_token": token, + }), + ); + + if (response.statusCode == 200) { + print("✅ Device token updated successfully!"); + } else { + print("❌ Failed to update device token: ${response.body}"); + } + } catch (e) { + print("🚨 Error updating device token: $e"); + } + } + // Future authenticateAndStoreData() async { // try { // // Perform authentication @@ -244,15 +328,13 @@ class LoginRoute extends HookConsumerWidget { 'تم إرسال بريد إلكتروني إلى $email يتضمن المزيد من التفاصيل.', ), ); - }, style: TextButton.styleFrom( padding: EdgeInsets.zero, // maximumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.compact, - overlayColor: Colors.white - ), + overlayColor: Colors.white), child: Text( context.translate( 'Forgot Password?', @@ -273,7 +355,7 @@ class LoginRoute extends HookConsumerWidget { width: double.infinity, child: ElevatedButton( onPressed: () async { - hasValidated.value=true; + hasValidated.value = true; final isValid = formKey.currentState!.validate(); if (!isValid) return; final session = await context.loaderWithErrorDialog( @@ -367,6 +449,7 @@ class LoginRoute extends HookConsumerWidget { } await saveUserId(userId, loginCount); + await getDeviceToken(context, userId); } try { final bool isProfileComplete = await profileStatus(userId); @@ -451,14 +534,14 @@ class LoginRoute extends HookConsumerWidget { 'Login', 'تسجيل الدخول', ), - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'Roboto', - ), - fontSize: locale?.languageCode == 'ar' ? 12: 18, - fontWeight: FontWeight.w500, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'Roboto', ), + fontSize: locale?.languageCode == 'ar' ? 12 : 18, + fontWeight: FontWeight.w500, + ), ), 6.horizontalSpace, const Icon( @@ -478,16 +561,15 @@ class LoginRoute extends HookConsumerWidget { 'Email', 'بريد إلكتروني', ), - validator: (value) { final emailRegex = r'^[a-zA-Z0-9àèìòùÀÈÌÒÙéàç~!#$%^&*()_+=-{}|;,.?<>]+@[a-zA-Z0-9àèìòùÀÈÌÒÙéàç~!#$%^&*()_+=-{}|;,.?<>]+\.[a-zA-Z]{2,}$'; if (value == null || value.isEmpty) { return context.translate('Required', 'مطلوب'); - } - else if (!RegExp(emailRegex).hasMatch(value)) { + } else if (!RegExp(emailRegex).hasMatch(value)) { return AppLocalizations.of(context)!.invalid_email; - } return null; + } + return null; }, imgPath: MiscIconAssetPath.person, controller: emailCtl, @@ -662,48 +744,47 @@ class LoginRoute extends HookConsumerWidget { final listViewHorizontalPadding = screenWidth < 700 ? 30 : 30 + (screenWidth - 700) / 2; final scaffoldBody = SingleChildScrollView( - child: Column( - children:[ - Padding( - padding: EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - 3.verticalSpace, - Align( - alignment: AlignmentDirectional.topEnd, - child: MyToggle( - isOn: locale?.languageCode == 'en', - knobTextWhenOn: 'ع', - knobTextWhenOff: 'EN', - pathColorWhenOn: Colors.grey.shade300, - pathColorWhenOff: Colors.grey.shade300, - onTap: () { - final formState = formKey.currentState; - ref.read(localeProvider.notifier).toggleLocale(); - Future.delayed(Duration(milliseconds: 100), () { - if (hasValidated.value && formState?.validate() == false) { - formState?.validate(); - } - }); - }, - ), - ), - 34.verticalSpace, - helloAndPleaseLoginTexts, - 40.verticalSpace, - form, - 15.verticalSpace, - dontHaveAnAccountRegisterBtn, - // continueAsGuestBtn, - // Spacer(), - 15.verticalSpace, - fcscBanner, - ], + child: Column(children: [ + Padding( + padding: EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + 3.verticalSpace, + Align( + alignment: AlignmentDirectional.topEnd, + child: MyToggle( + isOn: locale?.languageCode == 'en', + knobTextWhenOn: 'ع', + knobTextWhenOff: 'EN', + pathColorWhenOn: Colors.grey.shade300, + pathColorWhenOff: Colors.grey.shade300, + onTap: () { + final formState = formKey.currentState; + ref.read(localeProvider.notifier).toggleLocale(); + Future.delayed(Duration(milliseconds: 100), () { + if (hasValidated.value && + formState?.validate() == false) { + formState?.validate(); + } + }); + }, + ), ), - ) - ] - ), + 34.verticalSpace, + helloAndPleaseLoginTexts, + 40.verticalSpace, + form, + 15.verticalSpace, + dontHaveAnAccountRegisterBtn, + // continueAsGuestBtn, + // Spacer(), + 15.verticalSpace, + fcscBanner, + ], + ), + ) + ]), ); // final bgScaffold = Scaffold( // backgroundColor: Colors.white, @@ -783,4 +864,4 @@ class LoginRoute extends HookConsumerWidget { // ), // ); // } -} \ No newline at end of file +} diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index 61b1714e..8e142b0a 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -1456,7 +1456,8 @@ class InfoCard extends StatelessWidget { child: Text( title, style: const TextStyle( - fontSize: 11, + // fontSize: 11, + fontSize: 11 * 1.1, fontWeight: FontWeight.w400, // color: Colors.black, fontFamily: 'Roboto', @@ -1478,7 +1479,8 @@ class InfoCard extends StatelessWidget { subtitle, textAlign: TextAlign.center, style: const TextStyle( - fontSize: 11, + // fontSize: 11, + fontSize: 11 * 1.1, fontFamily: 'Roboto', fontWeight: FontWeight.w400, color: Color(0xFF8E8E8E), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index fd90a183..776231d3 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -204,7 +204,7 @@ class _EditProfileState extends State { if (avatarFilename.isNotEmpty && recordId.isNotEmpty) { _avatarUrl = - '$apiUrl/$collectionId/$recordId/$avatarFilename'; + '$apiUrl/api/files/$collectionId/$recordId/$avatarFilename'; } else { _avatarUrl = ''; // Reset to default or empty } @@ -308,7 +308,8 @@ class _EditProfileState extends State { // Check if the selected date is in the future if (selectedDate.isAfter(today)) { - return context.translate('Date of birth cannot be in the future','لا يمكن أن يكون تاريخ الميلاد في المستقبل'); + return context.translate('Date of birth cannot be in the future', + 'لا يمكن أن يكون تاريخ الميلاد في المستقبل'); } return null; } @@ -691,7 +692,8 @@ class _EditProfileState extends State { enabled: false, validator: (value) { if (value == null || value.isEmpty) { - return context.translate('Required', 'مطلوب'); + return context.translate( + 'Required', 'مطلوب'); } //RegExp(r"^[a-zA-Z\s]+$"); @@ -699,7 +701,8 @@ class _EditProfileState extends State { r"^[a-zA-Z0-9'\-\u0627\u0639~\u00C0-\u00FF\s]+$", ); if (!nameRegex.hasMatch(value)) { - return AppLocalizations.of(context)!.invalid_characters; + return AppLocalizations.of(context)! + .invalid_characters; } return null; }, @@ -787,18 +790,19 @@ class _EditProfileState extends State { ), value: _selectedCountry, decoration: InputDecoration( - enabledBorder:OutlineInputBorder( + enabledBorder: OutlineInputBorder( borderRadius: - BorderRadius.circular(10), + BorderRadius.circular(10), borderSide: BorderSide( color: MyTheme.topicColor( - IndicatorTopic.economy) + IndicatorTopic.economy) .shade400, width: 1), // Enabled border ), border: OutlineInputBorder( - borderSide:BorderSide(color: Color(0xFF7296BE), - ), + borderSide: BorderSide( + color: Color(0xFF7296BE), + ), borderRadius: BorderRadius.circular(8), ), @@ -845,8 +849,9 @@ class _EditProfileState extends State { color: showError ? Color(0xFFb22222) : MyTheme.topicColor( - IndicatorTopic.economy) - .shade400, + IndicatorTopic + .economy) + .shade400, width: 1.5, ), ), @@ -880,8 +885,11 @@ class _EditProfileState extends State { TextDecoration .underline, ), - recognizer:TapGestureRecognizer() - ..onTap = () => context.push('/terms&conditions'), + recognizer: + TapGestureRecognizer() + ..onTap = () => + context.push( + '/terms&conditions'), ), TextSpan( text: AppLocalizations @@ -907,9 +915,11 @@ class _EditProfileState extends State { TextDecoration .underline, ), - recognizer: TapGestureRecognizer() - ..onTap = () => context.push('/privacy_policy'), - + recognizer: + TapGestureRecognizer() + ..onTap = () => + context.push( + '/privacy_policy'), ), TextSpan( text: AppLocalizations @@ -1151,4 +1161,4 @@ class ConfirmationDialog extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart index f8d5d5cf..1b10dbc7 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart @@ -342,14 +342,18 @@ class _ManageUserRouterState extends State { padding: EdgeInsets.only(left: 12.0), child: TextField( decoration: InputDecoration( - hintText: "Search", - hintStyle: TextStyle(color: Color(0xFF898C81)), - prefixIconConstraints: BoxConstraints(maxWidth: 42, maxHeight: 42), + hintText: AppLocalizations.of(context)!.search, + hintStyle: TextStyle(color: Color(0xFFAA8E83)), + // hintStyle: TextStyle(color: Color(0xFFAA8E83)), + prefixIconConstraints: + BoxConstraints(maxWidth: 42, maxHeight: 42), prefixIcon: Container( padding: EdgeInsets.only(right: 5), child: SvgPicture.asset( MiscIconAssetPath.Search, semanticsLabel: 'Search', + colorFilter: ColorFilter.mode( + Color(0xFFAA8E83), BlendMode.srcIn), ), ), @@ -359,7 +363,7 @@ class _ManageUserRouterState extends State { // ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric( - vertical: 8.0, horizontal: 18.0), + vertical: 0.5, horizontal: 18.0), ), onChanged: filterUsers, ), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart index c1b276cb..889200a7 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification.dart @@ -1,22 +1,29 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:uae_stat/config/api_config.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import '../../custom_drawer_routes.dart'; +import 'package:http/http.dart' as http; -class NotificationPage extends StatefulWidget { +class NotificationPage extends ConsumerStatefulWidget { @override _NotificationPageState createState() => _NotificationPageState(); } -class _NotificationPageState extends State { +class _NotificationPageState extends ConsumerState { // Example notification data final _pb = PocketBase(apiUrl); List> notifications = []; // Current selected tab index int selectedTabIndex = 0; + late final Locale locale; // Tab categories final List tabs = [ @@ -28,6 +35,13 @@ class _NotificationPageState extends State { "Favourites" ]; + @override + void initState() { + super.initState(); + locale = ref.read(localeProvider) ?? const Locale('en'); + fetchNotifications(locale?.languageCode ?? 'en'); + } + String formatDate(String dateString) { try { DateTime dateTime = @@ -39,36 +53,61 @@ class _NotificationPageState extends State { } } - Future fetchNotifications() async { + Future fetchNotifications(locale) async { + final baseUrl = apiUrl + '/api/getNotification'; try { - await _pb.admins - .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final response = await http.get(Uri.parse('$baseUrl?language=$locale')); - final result = await _pb.collection('notification').getFullList(); + if (response.statusCode == 200) { + final Map jsonResponse = + jsonDecode(response.body); // Decode JSON - setState(() { - notifications = result.map((record) => record.toJson()).toList(); - }); + if (jsonResponse['message'] == 'Success') { + setState(() { + notifications = List>.from( + jsonResponse['data']); // Assign decoded data + }); + } else { + throw Exception('Failed to load data'); + } + } else { + throw Exception( + 'Failed to load data with status code ${response.statusCode}'); + } } catch (e) { - print('Error fetching notifications: $e'); + print('Error fetching data: $e'); } } - @override - void initState() { - super.initState(); - fetchNotifications(); - } + // Future fetchNotifications() async {/api/getNotification?language=ar + // try { + // await _pb.admins + // .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + // + // final result = await _pb.collection('notification').getFullList(); + // + // setState(() { + // notifications = result.map((record) => record.toJson()).toList(); + // }); + // } catch (e) { + // print('Error fetching notifications: $e'); + // } + // } @override Widget build(BuildContext context) { + ref.listen(localeProvider, (previous, next) { + final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null + fetchNotifications(localeCode); + }); // Filter notifications based on the selected tab List> filteredNotifications = selectedTabIndex == 0 ? notifications - : notifications - .where((notification) => - notification["category"] == tabs[selectedTabIndex]) - .toList(); + : notifications.where((notification) { + print( + "Comparing: '${notification["category"]}' with '${tabs[selectedTabIndex]}'"); + return notification["category"] == tabs[selectedTabIndex]; + }).toList(); return PopScope( canPop: false, @@ -77,7 +116,12 @@ class _NotificationPageState extends State { context.go('/myhomepage'); }, child: BaseScaffold( - title: Text('Notification'), + title: Text( + context.translate( + 'Notification', + 'إشعار', + ), + ), body: Column( children: [ TabBarHeader( @@ -90,53 +134,41 @@ class _NotificationPageState extends State { }, ), Expanded( - child: selectedTabIndex == 1 + child: filteredNotifications.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Image.asset( - 'assets/backgrounds/Notification/Update_notific.png', - height: 200, - ), + Icon(Icons.info_outline, + size: 100, color: Colors.grey[400]), SizedBox(height: 16), Text( - filteredNotifications.isNotEmpty - ? filteredNotifications[0]['title'] - : 'No updates available.', + 'No notifications available.', style: TextStyle(fontSize: 16, color: Colors.grey), - textAlign: TextAlign.center, ), ], ), ) - : filteredNotifications.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.info_outline, - size: 100, color: Colors.grey[400]), - SizedBox(height: 16), - Text( - 'No notifications available.', - style: - TextStyle(fontSize: 16, color: Colors.grey), - ), - ], + : ListView.builder( + itemCount: filteredNotifications.length, + itemBuilder: (context, index) { + final notification = filteredNotifications[index]; + return Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Color(0xFFDEDEDE), + width: 1), // Bottom border only + ), ), - ) - : ListView.builder( - itemCount: filteredNotifications.length, - itemBuilder: (context, index) { - final notification = filteredNotifications[index]; - return NotificationTile( + child: NotificationTile( title: notification['title'] ?? 'No Title', date: formatDate(notification['created'] ?? ''), category: notification['category'] ?? 'Unknown', - ); - }, - ), + message: notification['message'] ?? ''), + ); + }, + ), ), ], ), @@ -205,22 +237,43 @@ class TabBarHeader extends StatelessWidget { class NotificationTile extends StatelessWidget { final String title; + final String message; final String date; final String category; const NotificationTile({ required this.title, + required this.message, required this.date, required this.category, }); @override Widget build(BuildContext context) { - return ListTile( - leading: Icon(Icons.circle, - size: 12, color: category == "Social" ? Colors.red : Colors.grey), - title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text(date), + return GestureDetector( + onTap: () { + context.go('/notification_details', extra: { + 'title': title, + 'message': message, + 'date': date, + 'category': category, + }); + }, + child: ListTile( + // leading: Icon(Icons.circle, + // size: 12, color: category == "Social" ? Colors.red : Colors.grey), + leading: Icon(Icons.circle, size: 12, color: Colors.red), + title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text( + date, + style: const TextStyle( + color: Color(0xFF8E8E8E), + fontSize: 12, + fontWeight: FontWeight.w400), + ), + trailing: const Icon(Icons.arrow_forward_ios, + size: 16, color: Color(0xFF898C81)), + ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart new file mode 100644 index 00000000..83f5c21c --- /dev/null +++ b/lib/presentation/routes/drawer_routes/Drawer Items/notification/notification_details.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; + +class NotificationDetails extends ConsumerWidget { + final String title; + final String message; + final String date; + final String category; + + const NotificationDetails({ + super.key, + required this.title, + required this.message, + required this.date, + required this.category, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + context.go('/notification'); + }, + child: BaseScaffold( + title: Text( + context.translate( + 'Notification', + 'إشعار', + ), + ), + showBackButton: true, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Color(0xFF414042)), + ), + const SizedBox(height: 20), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + category == 'App updates' + ? 'assets/backgrounds/Notification/App-Update.png' + : 'assets/backgrounds/Notification/Update_notific.png', + height: 200, + ), + ], + ), + ), + const SizedBox(height: 20), + Text( + message.isNotEmpty + ? message + : "No additional details available.", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFF414042)), + ), + const SizedBox(height: 10), + Text( + "Date: $date", + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Color(0xFF8E8E8E)), + ), + ], + ), + ), + )); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0c4642aa..ec843094 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,8 @@ import Foundation import connectivity_plus import file_selector_macos import firebase_core -import flutter_secure_storage_macos +import firebase_messaging +import flutter_secure_storage_darwin import package_info_plus import path_provider_foundation import share_plus @@ -21,12 +22,13 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) - FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 0b5097c2..db2b6d12 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,31 +5,34 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: + version: "80.0.0" + _flutterfire_internals: dependency: transitive - description: dart - source: sdk - version: "0.3.3" + description: + name: _flutterfire_internals + sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5" + url: "https://pub.dev" + source: hosted + version: "1.3.53" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.3.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + sha256: "1d460d14e3c2ae36dc2b32cef847c4479198cf87704f63c3c3c8150ee50c3916" url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.12.0" ansicolor: dependency: transitive description: @@ -58,18 +61,18 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" boxy: dependency: "direct main" description: @@ -82,50 +85,50 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" built_collection: dependency: transitive description: @@ -138,18 +141,18 @@ packages: dependency: transitive description: name: built_value - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" url: "https://pub.dev" source: hosted - version: "8.9.3" + version: "8.9.4" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -178,10 +181,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -194,18 +197,18 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412" + sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.3" connectivity_plus_platform_interface: dependency: transitive description: @@ -266,42 +269,42 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "3486c470bb93313a9417f926c7dd694a2e349220992d7b9d14534dc49c15bba9" + sha256: "021897cce2b6c783b2521543e362e7fe1a2eaab17bf80514d8de37f99942ed9e" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "42cdc41994eeeddab0d7a722c7093ec52bd0761921eeb2cbdbf33d192a234759" + sha256: e4235b9d8cef59afe621eba086d245205c8a0a6c70cd470be7cb17494d6df32d url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5" + sha256: "6dcee8a017181941c51a110da7e267c1d104dc74bec8862eeb8c85b5c8759a9e" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" custom_lint_visitor: dependency: transitive description: name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 + sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" url: "https://pub.dev" source: hosted - version: "1.0.0+6.11.0" + version: "1.0.0+7.3.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.0.1" dartz: dependency: transitive description: @@ -330,10 +333,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" equatable: dependency: transitive description: @@ -369,26 +372,26 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" fast_immutable_collections: dependency: "direct main" description: name: fast_immutable_collections - sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c + sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" url: "https://pub.dev" source: hosted - version: "10.2.4" + version: "11.0.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -425,18 +428,18 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.3+4" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b url: "https://pub.dev" source: hosted - version: "2.32.0" + version: "3.12.1" firebase_core_platform_interface: dependency: transitive description: @@ -449,10 +452,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41 + sha256: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493 url: "https://pub.dev" source: hosted - version: "2.21.0" + version: "2.21.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "5fc345c6341f9dc69fd0ffcbf508c784fd6d1b9e9f249587f30434dd8b6aa281" + url: "https://pub.dev" + source: hosted + version: "15.2.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: a935924cf40925985c8049df4968b1dde5c704f570f3ce380b31d3de6990dd94 + url: "https://pub.dev" + source: hosted + version: "4.6.4" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: fafebf6a1921931334f3f10edb5037a5712288efdd022881e2d093e5654a2fd4 + url: "https://pub.dev" + source: hosted + version: "3.10.4" fixnum: dependency: transitive description: @@ -486,10 +513,10 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "0.21.2" flutter_launcher_icons: dependency: "direct dev" description: @@ -523,10 +550,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.27" flutter_rating_bar: dependency: "direct main" description: @@ -547,50 +574,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: f7eceb0bc6f4fd0441e29d43cab9ac2a1c5ffd7ea7b64075136b718c46954874 url: "https://pub.dev" source: hosted - version: "9.2.4" + version: "10.0.0-beta.4" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: f226f2a572bed96bc6542198ebaec227150786e34311d455a7e2d3d06d951845 + url: "https://pub.dev" + source: hosted + version: "0.1.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 + sha256: "9b4b73127e857cd3117d43a70fa3dddadb6e0b253be62e6a6ab85caa0742182c" url: "https://pub.dev" source: hosted - version: "1.2.2" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" - source: hosted - version: "3.1.3" + version: "2.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "4c3f233e739545c6cb09286eeec1cc4744138372b985113acc904f7263bef517" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.0.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: ff32af20f70a8d0e59b2938fc92de35b54a74671041c814275afd80e27df9f21 url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.0.0" flutter_staggered_grid_view: dependency: "direct main" description: @@ -621,18 +648,18 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "8971efe7e59585e9149052e33718d84bca51e806f063d1467622b3dcb2878b6c" + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" url: "https://pub.dev" source: hosted - version: "8.2.11" + version: "8.2.12" freezed: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -669,10 +696,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: daf3ff5570f55396b2d2c9bf8136d7db3a8acf208ac0cef92a3ae2beb9a81550 + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 url: "https://pub.dev" source: hosted - version: "14.7.1" + version: "14.8.1" graphs: dependency: transitive description: @@ -725,10 +752,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" iconify_design: dependency: "direct main" description: @@ -757,10 +784,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c + sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" url: "https://pub.dev" source: hosted - version: "0.8.12+20" + version: "0.8.12+21" image_picker_for_web: dependency: transitive description: @@ -821,10 +848,10 @@ packages: dependency: "direct dev" description: name: injectable_generator - sha256: af403d76c7b18b4217335e0075e950cd0579fd7f8d7bd47ee7c85ada31680ba1 + sha256: b04673a4c88b3a848c0c77bf58b8309f9b9e064d9fe1df5450c8ee1675eaea1a url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.7.0" intl: dependency: "direct main" description: @@ -845,10 +872,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -861,10 +888,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" url: "https://pub.dev" source: hosted - version: "6.9.0" + version: "6.9.4" jwt_decoder: dependency: "direct main" description: @@ -877,18 +904,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -901,10 +928,10 @@ packages: dependency: transitive description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" logging: dependency: transitive description: @@ -913,14 +940,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" mailer: dependency: "direct main" description: @@ -941,10 +960,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_charts: dependency: "direct main" description: @@ -965,10 +984,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1005,26 +1024,26 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: c447a3c3e7be4addf129b8f9ab6a4bd5d166b78918223e223b61fddf4d07e254 + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.2.0" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1085,10 +1104,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" platform: dependency: transitive description: @@ -1141,10 +1160,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" recase: dependency: transitive description: @@ -1173,10 +1192,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8 + sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.5.9" riverpod_annotation: dependency: "direct main" description: @@ -1189,18 +1208,18 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188" + sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "83e4caa337a9840469b7b9bd8c2351ce85abad80f570d84146911b32086fbd99" + sha256: b05408412b0f75dec954e032c855bc28349eeed2d2187f94519e1ddfdf8b3693 url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" rxdart: dependency: transitive description: @@ -1244,18 +1263,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" + sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.6" shared_preferences_foundation: dependency: transitive description: @@ -1284,10 +1303,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: @@ -1300,18 +1319,18 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -1328,10 +1347,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: @@ -1344,10 +1363,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -1360,10 +1379,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -1376,10 +1395,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -1392,42 +1411,42 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" syncfusion_flutter_charts: dependency: "direct main" description: name: syncfusion_flutter_charts - sha256: "672c17a7f4cb3020c06bb6a28d2d033623007b8e2a19704ad34b412e9bdd2dab" + sha256: c53e8616368c1a88397b7b180d0ded7d9e960564cdba01509cc5fa2d503ed727 url: "https://pub.dev" source: hosted - version: "28.2.4+1" + version: "28.2.9" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: "3c1876b0a245de23de3b17a19e3106fed57d88f4fd2c8dc9bc1976705b1c31d5" + sha256: "5a1a76eef118e511443b299925d4a802eb8259388d6a580e0ad6413d1a70f987" url: "https://pub.dev" source: hosted - version: "28.2.4" + version: "28.2.9" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" the_validator: dependency: "direct main" description: @@ -1448,10 +1467,10 @@ packages: dependency: "direct main" description: name: tutorial_coach_mark - sha256: df450c88d4c812bc221afd3ff948da3dc0f44c0b4fa5dbc046d6d86f2cfc9e71 + sha256: "2c77c0b00bbe7d5b8a6d31cb9e03d44bf77dfe7ba6514cc2b546886d024a9945" url: "https://pub.dev" source: hosted - version: "1.2.12" + version: "1.2.13" typed_data: dependency: transitive description: @@ -1520,10 +1539,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: @@ -1576,26 +1595,26 @@ packages: dependency: "direct main" description: name: video_player - sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + sha256: "48941c8b05732f9582116b1c01850b74dbee1d8520cd7e34ad4609d6df666845" url: "https://pub.dev" source: hosted - version: "2.9.2" + version: "2.9.3" video_player_android: dependency: transitive description: name: video_player_android - sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + sha256: "7018dbcb395e2bca0b9a898e73989e67c0c4a5db269528e1b036ca38bcca0d0b" url: "https://pub.dev" source: hosted - version: "2.7.16" + version: "2.7.17" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "8a4e73a3faf2b13512978a43cf1cdda66feeeb900a0527f1fbfd7b19cf3458d3" + sha256: "84b4752745eeccb6e75865c9aab39b3d28eb27ba5726d352d45db8297fbd75bc" url: "https://pub.dev" source: hosted - version: "2.6.7" + version: "2.7.0" video_player_platform_interface: dependency: transitive description: @@ -1616,10 +1635,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -1632,10 +1651,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -1664,10 +1683,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f + sha256: "512c26ccc5b8a571fd5d13ec994b7509f142ff6faf85835e243dde3538fdc713" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.3.2" webview_flutter_platform_interface: dependency: transitive description: @@ -1680,18 +1699,18 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" + sha256: c49a98510080378b1525132f407a92c3dcd3b7145bef04fb8137724aadcf1cf0 url: "https://pub.dev" source: hosted - version: "3.17.0" + version: "3.18.4" win32: dependency: transitive description: name: win32 - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.11.0" xdg_directories: dependency: transitive description: @@ -1717,5 +1736,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.2 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4248f8ff..d8c68214 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" -version: 1.0.27+28 +version: 1.0.30+31 environment: sdk: ">=3.2.3 <4.0.0" @@ -9,9 +9,9 @@ environment: dependencies: flutter: sdk: flutter - fast_immutable_collections: ^10.1.0 + fast_immutable_collections: ^11.0.3 # flutter_cache_manager: ^3.3.1 - flutter_hooks: ^0.20.4 + flutter_hooks: ^0.21.2 flutter_riverpod: ^2.4.0 get_it: ^8.0.1 go_router: ^14.1.3 @@ -33,10 +33,10 @@ dependencies: csv: ^6.0.0 excel: ^4.0.2 intl: ^0.19.0 - firebase_core: ^2.27.0 + firebase_core: ^3.12.1 the_validator: ^2.0.0 pocketbase: ^0.18.1 - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^10.0.0-beta.4 jwt_decoder: ^2.0.1 fluttertoast: ^8.2.11 webview_flutter: ^4.5.0 @@ -66,6 +66,7 @@ dependencies: package_info_plus: ^8.2.0 flutter_svg: ^2.0.17 boxy: ^2.2.1 + firebase_messaging: ^15.2.4 dependency_overrides: fading_edge_scrollview: ^4.1.1 @@ -139,6 +140,7 @@ flutter: - assets/icons/misc/group.png - assets/icons/misc/search.png - assets/icons/bookmark/ + - assets/FCSC-GIF.gif fonts: - family: Segoe