From 03a0c7d02b50e99cf16e81a379427b622e212c50 Mon Sep 17 00:00:00 2001 From: jjjh02 Date: Sat, 24 Feb 2024 02:41:01 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20mypage,=20recommend=20api=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes babymeal/assets/images/allergy_bean.png | Bin 0 -> 1789 bytes babymeal/assets/images/allergy_beef.png | Bin 0 -> 2586 bytes babymeal/assets/images/allergy_buckwheat.png | Bin 0 -> 1646 bytes babymeal/assets/images/allergy_chicken.png | Bin 0 -> 1258 bytes babymeal/assets/images/allergy_crab.png | Bin 0 -> 1917 bytes babymeal/assets/images/allergy_egg.png | Bin 0 -> 1064 bytes babymeal/assets/images/allergy_mackerel.png | Bin 0 -> 1201 bytes babymeal/assets/images/allergy_milk.png | Bin 0 -> 1124 bytes babymeal/assets/images/allergy_peach.png | Bin 0 -> 1520 bytes babymeal/assets/images/allergy_peanut.png | Bin 0 -> 2118 bytes babymeal/assets/images/allergy_pinenut.png | Bin 0 -> 1312 bytes babymeal/assets/images/allergy_pork.png | Bin 0 -> 1873 bytes babymeal/assets/images/allergy_seaweed.png | Bin 0 -> 1809 bytes babymeal/assets/images/allergy_shrimp.png | Bin 0 -> 2157 bytes babymeal/assets/images/allergy_squid.png | Bin 0 -> 1306 bytes babymeal/assets/images/allergy_sulfurous.png | Bin 0 -> 1431 bytes babymeal/assets/images/allergy_tomato.png | Bin 0 -> 1535 bytes babymeal/assets/images/allergy_walnut.png | Bin 0 -> 1283 bytes babymeal/assets/images/allergy_wheat.png | Bin 0 -> 1704 bytes babymeal/assets/images/level2_none.png | Bin 0 -> 1172 bytes babymeal/assets/images/progress2.png | Bin 0 -> 1424 bytes babymeal/ios/Flutter/Debug.xcconfig | 1 + babymeal/ios/Flutter/Release.xcconfig | 1 + babymeal/ios/Podfile | 44 + babymeal/lib/model/CustomerModel.dart | 25 + babymeal/lib/model/FridgeRecipe.dart | 41 + babymeal/lib/model/RecipeDetailModel.dart | 48 + babymeal/lib/model/RecipeModel.dart | 44 + .../lib/pages/mypage/ChangeChildAllergy.dart | 133 +- .../pages/mypage/ChangeChildBirthPage.dart | 91 +- .../lib/pages/mypage/ChangeChildNamePage.dart | 93 +- .../lib/pages/mypage/ChangeNicknamePage.dart | 45 +- .../lib/pages/mypage/ChangePasswordPage.dart | 147 +- babymeal/lib/pages/mypage/ManageMyPage.dart | 141 +- babymeal/lib/pages/mypage/MyPageComments.dart | 50 +- babymeal/lib/pages/mypage/MyPagePost.dart | 50 +- .../lib/pages/mypage/ViewChildInfoPage.dart | 95 +- babymeal/lib/pages/mypage/ViewMyPage.dart | 1274 +++++++++++------ .../recommend/RecomChooseMaterialPage.dart | 103 +- .../pages/recommend/RecomChooseMealPage.dart | 4 +- .../pages/recommend/SelectKeywordPage.dart | 34 +- .../recommend/ShowDetailFridgeRecipePage.dart | 459 ++++++ .../recommend/ShowDetailMainFridgeRecipe.dart | 442 ++++++ .../pages/recommend/ShowDetailRecipePage.dart | 380 +++-- .../lib/pages/recommend/ShowRecipesPage.dart | 566 +++++--- .../pages/recommend/ViewRecommendPage.dart | 360 +++-- .../lib/pages/recommend/WaitRecipesPage.dart | 174 ++- babymeal/lib/services/DietService.dart | 45 + .../lib/services/FridgeContentService.dart | 13 + babymeal/lib/services/MyPageService.dart | 70 + babymeal/macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + babymeal/macos/Podfile | 43 + babymeal/macos/Podfile.lock | 35 + .../macos/Runner.xcodeproj/project.pbxproj | 98 +- .../contents.xcworkspacedata | 3 + babymeal/pubspec.lock | 38 +- babymeal/pubspec.yaml | 4 + 59 files changed, 4182 insertions(+), 1014 deletions(-) create mode 100644 .DS_Store create mode 100644 babymeal/assets/images/allergy_bean.png create mode 100644 babymeal/assets/images/allergy_beef.png create mode 100644 babymeal/assets/images/allergy_buckwheat.png create mode 100644 babymeal/assets/images/allergy_chicken.png create mode 100644 babymeal/assets/images/allergy_crab.png create mode 100644 babymeal/assets/images/allergy_egg.png create mode 100644 babymeal/assets/images/allergy_mackerel.png create mode 100644 babymeal/assets/images/allergy_milk.png create mode 100644 babymeal/assets/images/allergy_peach.png create mode 100644 babymeal/assets/images/allergy_peanut.png create mode 100644 babymeal/assets/images/allergy_pinenut.png create mode 100644 babymeal/assets/images/allergy_pork.png create mode 100644 babymeal/assets/images/allergy_seaweed.png create mode 100644 babymeal/assets/images/allergy_shrimp.png create mode 100644 babymeal/assets/images/allergy_squid.png create mode 100644 babymeal/assets/images/allergy_sulfurous.png create mode 100644 babymeal/assets/images/allergy_tomato.png create mode 100644 babymeal/assets/images/allergy_walnut.png create mode 100644 babymeal/assets/images/allergy_wheat.png create mode 100644 babymeal/assets/images/level2_none.png create mode 100644 babymeal/assets/images/progress2.png create mode 100644 babymeal/ios/Podfile create mode 100644 babymeal/lib/model/CustomerModel.dart create mode 100644 babymeal/lib/model/FridgeRecipe.dart create mode 100644 babymeal/lib/model/RecipeDetailModel.dart create mode 100644 babymeal/lib/model/RecipeModel.dart create mode 100644 babymeal/lib/pages/recommend/ShowDetailFridgeRecipePage.dart create mode 100644 babymeal/lib/pages/recommend/ShowDetailMainFridgeRecipe.dart create mode 100644 babymeal/lib/services/DietService.dart create mode 100644 babymeal/lib/services/FridgeContentService.dart create mode 100644 babymeal/lib/services/MyPageService.dart create mode 100644 babymeal/macos/Podfile create mode 100644 babymeal/macos/Podfile.lock diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1b8bfd55cd66857a2087d78aab703ff673d54b7c GIT binary patch literal 6148 zcmeHK%}T>S5Z<-bZYe?!3Oz1(Em+%P6)z#y7cim+m70*E!8BW%)FP#jv%Zi|;`2DO zy8%mk@FZeqVDrt+&+g6#*&oIjcc)Nuxh+K~`VngP$IGCm5 zH3R)c6Rusb09GBaSN3iFL97DseFWn;O0!n$lW&#E+dCCe5jAn|J*kBk@A0Gx}0BDd& z#54OI`M1Jx0C?9V*P!nL_6b305po>%lkq368=X#0MK+B ztOy4J04n)pBHlYuX|33k0`-H7(hg++Y(aSFu-<{1Sz(=c^e|?bFCHzNZMlS7tD9Y! zp(S4(Fpevc7GAfic_%e+>aV-X1_qhm3DYB}^$hRYgdF&w4cIlOF@ zw<#75a#PEmn71M}r57BXXm9ezzz%-%FSWdFd#?)Dl6-_`h8MfBR)r>lbdJN;hwU-7 zC88j>Jndt=NzQ{%@wb<9T!#BOjA{U)#uW>I?R}6WIOd`$i5n3TDo1ZYOkhCS+~o>5Vc+p{pcQi*nz>t8akxn8y;rO0&0mkz+7^rc35|FHDaR>eA$tU$vv(&MJjvS04L_e;rwVI`xft#9BqehrQ)0V* z>()i&9mC3@q@?jAv20cQrnwonw8C`bXir7Tm#FZqM|n_=^HHpP*O7~X1fC73xi0Xigyeb} zYqjG%jhYr8{a{qh*-eONMN~W38U`0(NnV^4A-?7;8KOjXxb+C5S zd8>@#dG~S0RLT3Ef?=dExMeFPNByE~q&R?}BYbLtgjG{5w6KUp*Z8@W)B}@YjWNJ{ z1qogNJ|lO1cISJds~3)fBg#YU27t~g3#iZ215IR)5VOKno{#ohB**t>8+Y z&|2KKJESn^p8C$P>y%yRR&RjdGOaDSp|65q;UdgEdotRM?zES`%*BFP-;1iw*QVa;E+W^<%=07R@{F=OpVRa%QfmZIL|4d zVFo*IoL%Ilp-rdx#bZm|ffdje>UMO6?1+u^ryV*1D#N4|tt0D$CVcR<%BMgeS*Yu9;+UoxZ?^7IV&A{p{z`-%N&9u?VJg%L zuWGlx=HDtjeuK3g2d<7pL{PHaw8u!13l6kBh{Xv@a#r6b`x`6@sQFD$%c+vn`@r-$ z(OE4dJ34W7iXIiWaJp7Gw#Zr9nb;-&6T{TVo!M%pBG61q+9{wJHpO8(8$EGK^Jd9Z zTv!*y`4Qyy;m4`u5;tSJiE(;VqlA0!EBYcw#uT6@UI%C2&4W*DK(6S3dEq~5#`voXLr|Q@hg9uP0?EALsaOsK zY7NxL9R#_Dn>eM{n1Yg%I`$zrhQnWSg2qzsPYxQRV}SFYT(x*XXJr-T(jq literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_beef.png b/babymeal/assets/images/allergy_beef.png new file mode 100644 index 0000000000000000000000000000000000000000..09087476fe1d94c0521cfba30093d0ac22d73a42 GIT binary patch literal 2586 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 G=>G#u=pGpW literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_chicken.png b/babymeal/assets/images/allergy_chicken.png new file mode 100644 index 0000000000000000000000000000000000000000..ee04e96cf04f43eeb459d327e6b9292a02687366 GIT binary patch literal 1258 zcmeAS@N?(olHy`uVBq!ia0vp^86eET1|(%=wk`xxoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBZoQ|AV@L(#+dH=T!k#k6Ki+?LtC5@iq+?OT z$%A(%-Z|(g9wo)4vGA$wg-<~%G#4&yHR5G8QD9{4VDDVcpu~T~LFUtj>}U1|k1RM* zm~;M3{O?=~zFbq>7j@We#GlGnwLc_PWookG7HYPYskeKp-+ z(7VQrdyQW0|LqwC@{1lCx;Q6)d!NH4t5IFefAg}YaQZE`TU{<*ee)bmtQ>{4wH}?k z^yX>q68`mz<7PWAR|&DHv-%#;%BA9X$vS+(SvyC4k2xuyY#ueMPndp)%xyj_F~+A*|C)J_ZPLmu?=7Z9K3P_NCq78JiC5+IoINLc z0{7K$7zN}lUjDPXqJr73Me>Bt>f--$XE=&BKKm6}(RjvoLAW*R-BtT{@?US_o)Q}0 z*xLU)prG*;_xgi}><$&^?D3Atl{1+&PkX1(J&sB2c`7w$R7zPUa%)Za(|>lA&36U) zLtHLKd-Eo^KUv2r+L73JOGoMs_w^>uE&Kkvnl-uoYs`Mga_V8?&9Fs*yeTK0jdq

|qO+y;a*IX! z_xNAA*%&oGdBW=Q)bUB9allXUC+Ao+|JL7o-I8EDrSVDc_B(dB-|!s~H4e6}vf3Va zxyL9V$4v3*rAIZzS6DjEWNZygd0hPC!ms%*8u9j#aZyf{A4A`l3;Im>@jxYM^GV6x z4||^7;EG+qZ~WdMf8Vix|HPL*58i9}DBJ18EBjSGK{4C5HERVZ_jOEWnX3HwuB(yZ zq{r!RPlqpD#Cb(#`NGv6mg{1xI+ZS@6-rrkrR4oG^Gle%`Ro(97k|Ha*R0xR=XJFs zq^_PnTWIA`wN-np^!5LWDfXQ(4X`ep75Y!+xS!7L%eF-gOa8s>nd37xF)g+1ualYY zyJ_1UuI`i!d7i#u*RH0ZN`_8J(bMs_?5%#^e!=+0H2&`~?*%){FGm;@T3uaV*z)=A zo29qXdoQy-Zxpi+ySTEhUwIDusZAntFIc>ulRu5`&5eJHo9}D4Z>+xYcjGMq3wg)t zGyJ=9(sJKUGLTAd{_IdTG4q=KTeqm_$i`a_p87Pcspj+l)6QJ~r&ZQEI{uT@-gMEa z*PWGjm+?QmzVO=g^_LuePd{Au;BUzJu80GeaYWvLEnp)Sopnpvo_?K z;1!+gXD=P~58W-VzOn4B9qu338))k+l= zilUdM6)S{TZBZ`Q{rY};-w)@U=bZBg{L<}gEO@yjxc~qFuO$-UaFK!kii7RK!Jzhv zi{K1Lx&n(`AK0UoFDj8=1^;#0e{NHr370; zRtt2h2NlpZ8=ZwEi7zSVV#MT^JSz`o)QJ-!jH z{0cOAYCNrfa{Wbnj#pdaAIWB$K$bEJ<@W+c?MhkL!Et-JdAUS{l~z%PR!gaUJ*%0s zABXMTuUjzQKX<9SyWS+GaIuVgo$h-R%VeJjiXvD_nJ+PQwtpk6lfX8_s`OqT>vB1G z;H2DbhPqVYmO_1bVh@C9Qymvq-og;-f&T3%R5TwQLC_Ujv1l6rSa1Ggqdws5R{w5;X6XR95g(sP`;CnGgnl3rGMYJiU?KPx<N75@qY$kIrYX%Z|nR;{2A!ILpWNy{rbB@9$EX)_^9oDdvg$djOZ4XY~<`3ui9@h zXl>$(*~M*OR-}0SR_}-$m=4ljlU*x< zndju4$2D|dG|M#v{_3a_6-zOF1QqY;&@Hvtea%oCq9)wB>C{JF;8AWnBejzI({3X- zWkI&0zMOewjS~%e_xG%9nTGJ0X7ja48&8&-GZqHy3=^M{N8zsOYJ^L{h&QwkFt2vVqzzuX}<39lM+_-{w(~TQi9TrX>rc$rR+* za@Nqs&0}4Pq3r7D`^y2)n_8GVV0RGq?k*Ci6fIWi4YE$W^}AI&TF&K~6g_kFsG7Z* zE}L~j&Q425-?56}? zQ|eIPE*&v0hD5C>g^sZ|ZF&0G;&LUQa$&AkKTNSoiIpB;6>8|BMk3JI^Y^ zAm6OE*;)OlpQRfdi!oN=>(HCM{=Qn9qqy%B1!bPY`|ArH>F<=mg0qN_`3iU``P~1o zj+8f}%Yo{n@ec^sXD4MPEizI*uc>+l^nVEft)fecw=igwT1jXSOsCWFV>gsbYu}rB zkQqm$Z>st%De3PmO4b&mywG!`G3JT>UJJ#cSx5(=Q2dkG4<=Q|2aTT4YGT8LK{XZe zsx=xMCc)`N7hJ@DN)?tC$X>Cl5+!!I&nx{3_on=E`}sT~>@T4ZX7~8UC7#8(4y8?s nOYjGuopGoC8zuh*pLNylARV!$D4)s;S^_N1Y!Hnmcar`AaJ!Ak literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_egg.png b/babymeal/assets/images/allergy_egg.png new file mode 100644 index 0000000000000000000000000000000000000000..5f235662e8c418b569986c37ffcf558308510e92 GIT binary patch literal 1064 zcmeAS@N?(olHy`uVBq!ia0vp^86eET1|(%=wk`xxoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di49t5yT^vIy7~js>?;ji}(r~^yS2(pXL(-t3 z*{Q%$SS%)1a&GC<{G_s}g6^_wo0}~X9NN1cC770JI=`&GSG={wpkdm!=euA26uQ*H za_dX=^W(eU-U-q=e7?V8&8bsuPqtrIHJv0FQdZ}Y z|9z*({d*@DxCFkQJ<0#Px0Z_Hik$k1laGZ>G2tt9A}+Nijr=eW2fago6ji1v8t4R^|P2CcKP#r zzbNg{@^C!*-0hDfdzGL_wSEg%(1jh@&hIaK=d3!+DHWb&&2QQj(3G@GGC0fHKV~6= zo1j;~#he)~=U@J2Ud*9$(z~T;N4V}6uH-GskkwlijcdtyFJifbfIDYhIbn&16 z=&|;~0|HmJthIfzH=$*A(|=dkGn}XHBOT$0|B? zW>q`BbygHIU(B$}(xEH)>ipo6=>iM*W^ygE*U_%`-Jx(uyg*P`-tp2;%?!1@Y*Xh> zo1l3+{;1S#4zAW2JP-e!v1odr>33w$)or3I{R=uirknT`-g)aM@}~wGora=Io|k^O zvaVZI&T;yLtILiEJ?7Y@n8G2LC++w_#QbB^n#;$RURl>^x~(bgMtkMvm#iC)g$2}l zp4hQqKePIZl+EQwr9PWk$EWa3Kk+ znEuJGSGR;?Zudu>J?C^dAhK3REQ95F@t!^*^S7%u{V>?~t!(@8ZRw6rLmYY!MsE20 zsN{C;_Km7Wr}kz>6+0dIrq8AHqOq><)S|xqG6yHRyuG$Xae@6+-=s&2xU4P)KRDd6 zOkv5a3x6&xX*>05Pmpba&51jgD(Z!UBD0!PC(r)1`pCpHYne6oz3+Y@IPu*^kzKtJ zv%3PqH(l72?XK}~VwK%y1<90$#R8K=Ogw+Ale5s{=;LmB-1K0F0_n`p(!mM2Y@ZK= R>H{-2gQu&X%Q~loCIDq(*S`P& literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_mackerel.png b/babymeal/assets/images/allergy_mackerel.png new file mode 100644 index 0000000000000000000000000000000000000000..0159dbc31e3f6cb91699e3169fa125f375c567a8 GIT binary patch literal 1201 zcmeAS@N?(olHy`uVBq!ia0vp^86eET1|(%=wk`xxoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBuA8TeV@L(#+u6SU!hs@f`fQFY2St=sBn*NA zJzO3w%88H&|HDwfOuN33`_2Xf7GaJkUX!G*iEehfOI?1)70oX5TDYay`u&vep0n!I zS}LE-sXjN;I`+SpgyU&lA^Ce)*}eKP&C|_Q>0RGB@qCUl!^n{C(|)j}sGr z|62CY^}?-Z+alxsm%Yrd$}6A!x>>7s|bd)V~zw?7EFb}Um^`O?$)bH$oh zo0neYoqX?};aP@@#V0%adbHNNGPx*dHL=${$jd*Q&wbSRvQEc=rRSx?`np&735rI| z>^-FLcd;3hf@b0R{jWCPxfgh~I{fWcE*HhEO0i0N&33Ib6prdTe6Zqs;>~$G7k4Te ze=k{7Bc|L{aN!Q4bK`_6yUg*<_g-Z5>#q=U2~A7Co>Kn(jnx4Lspu(>5BeG;3Tgw@ z&Ju_fNGo6GroeNtY3s4)85-M~nnLo7x4rye@jf(a_pil$f+74K*3-*Vv=84o_;a)F zVa{D@dDosfoPR3JnZ>l*xb#g;`l{EPYZM)QgDRta)n4nL xUCE!tD8hMTZOfMDs}rXx6iswzT``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di49q`0T^vIy7~jshn|0ekpfz39y6m#Zr0##t z`M>_157Af}kdc|$?fFMJbhGEsMYTWkq=6!4gF zBIU?fIN5Cf{u95Rr)SPyJH^E``oy1CuHP$Er_BhTz!nl^;k9{hH~TcxXN*%N_=G+; zIPIMfwDQ^Xq~4!WA!p}%Y>woV{=9nn$BS#~kA^MN)m0DK8j&o2p=W(liC_2Y^8WL; z)6~oD?Y`Y-U!m|>%v~}d{A&C9_U09O^SVqM8s79Ds-MVibgjvG!~Yu@`kXzYQ$mkj z_$7Sb+`gIXQmfd;8*<9)dlqszos}+H)$Y6X^Qi#O7yH>S&r)Q3+HEvx;VFqV3w}8X z%*fsrRBl_p``5Q0yJqR8R{oVru29U6?v1gl*WZ5MUUrSE#_uCrcD(6`y|;(;Rk=mY zO|zh@pWii97Jo=?NjhH0>agzCFZRZXtBb`aOp-ScU2!x~OXK&`*>~LvzC_3TnYGf) zD-7*|i5a{)(0V&&@A7(a*L+_G(IDTb|>Wb9{f+F8J%%9Q4LwTZ2dF?wgCE zVkYcUxpQAqeXg+AM$rg|(j?bhX0D7SRga9Bj`k&UcWhby_}YSpU)Ak=kFYMiQJBLx zah2JbM{Xj~p3g((CU-v$c$?W(J8hD~!duE+cXO`y+dJhOIO)gu+?H|K>aLc+GS(uZ0otsc{4dyM9oPOzO*6f`J_jpUmA9T zlyZ4G99v~uweUoofP_f&LJp14w|7r!zvxJs%-Q60{ZY_jD^7Qj-v=IWh(teJp%8pk zE<9&i_QN0xEsfx}+bk1BWdq-o#IPL=`w=zg#iR|BF9-G|X^Svwrmc#dkfiOxD7muC zv1{!GmXyV}QZ=_Z-F)P?>{hGhwnM>=YO7+WC28A;PFeZw##xnr-GWE&tqq?7RC`7x z{O#52{7r|nGg#WBbq#^q!~_oh^FFK0!4>MzcwpX(ykKRQD=cqY6Zte_79$?XlroKls}z>^v%BM;70G_qC1UafSST=fy85}Sb4q9e0L=UJ AT>t<8 literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_peach.png b/babymeal/assets/images/allergy_peach.png new file mode 100644 index 0000000000000000000000000000000000000000..23f582a5901c4e6b53935e45eff4448899ca2103 GIT binary patch literal 1520 zcmbu9`#aMM0LH)7!X_fRu}KL#wWdOkODo@H*j&agq>^q9i+SW}OD4G6J~0Vo^D@f+Fo|nAp};^_dvSIu{#!KB$tE zObNeP5s?Oi`mZ3+EswFEHXX3pwWlCjA(%pp?d|PQ>^$-Fb>~snnUL{U>^7mbwT`1r z8r;M^6S}1xl6{iXu?tieS~7f$oc|k+h<-*}`feN<&>_CJd%zWGZ?&mYUA{}BoW=D^ z4x+o{w|KR+@@)$xHL6i08#9Z$Ti;eM6kO`L$X-Aj> zh-lQL#pw8D@v~;k%sF+{xKsMbilTX5(#VDg1J#DaLb)|6&PQKHpI?&EZQ~3!-Vui# z+}#`6XcV~_C$Yg*5aL@~7rd#O`N8=Ri)nAsx|y03P|d+rP*rc$eWSkQ`U!h>(q~M%D7tUTYjh1Ak<%~ zq17I^nX}QUn7}(qwYZRYW_&EKUfBY@E-h58x&HuF`wkAq&ylwzgp02FVZBB4Xvr84m^GmledCYgzn5Bev=eg-B>?!HmQjW zHeg1FUG-Ud_}T?kr%Xz^)!9o} zQuIgpc?u+ZC@d332(yEkzpzC&KycWo$SzvK+~T~vStz($YNcnDTNZ(S|IUt6GlH-e zCHbY3T{cp`qPaRQ9KN4NUS_(`&(y(To7pEll6KHx2JZW$ht1xpO8m+5-NSmMZ)b0U zVXQy~cyvNM`tzwt52dii8 zFE?B6RXtCPk88y*YrilomOb>P#ZJybZ9x*Uy@#O7dTfBtQXe#=yIQoNxNe5@{m0so zTz5lN@N(`KQR?6;#aAL^t&KWsRW`jQ;0fCvRmgC}ZNNZ~8oN3)?1VyM@xT#U9L7h1n~hSfccfL_dyUwf zcRUR+&V8r}?KGtjWr4aYaetrNl9o@maBm$|TZj5zJvn&I_y7Ky1Zd$79(ZeZPpLg< N0FU!?zk@x-`4>g%w-^8b literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_peanut.png b/babymeal/assets/images/allergy_peanut.png new file mode 100644 index 0000000000000000000000000000000000000000..3bb67f04becf61e2e26cf16aa77e9ab034ee0cc8 GIT binary patch literal 2118 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 literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_pork.png b/babymeal/assets/images/allergy_pork.png new file mode 100644 index 0000000000000000000000000000000000000000..36f74c2b7ff602d2c21f50c7b24b2048362d66e8 GIT binary patch literal 1873 zcmb7_=RX^W1I81gA&S_fp{Q6zt#Bz<)e?J!&{H+eiqY1HTIKB38kLw0LerGCNR1jn zRjt&F?KE{CqXgBus#)js{qM!^UOdn9d7kGF_-5GInDRkj5C8zcXKrSUIZfP8ai2ML zDR@)GY48M_{T2oQ@CyGF5KuyrI30mu7}INj+7USWw1E7Ktc?JGS2?^tJh=dXGoQ?j zjT|F^pG)RvWhN2<5F2=Z2)sY%L-0R(TCYlHI|UYEuB?`o<4w9d(0y-6 zVVz&}b8zQ4Ff_zf$h4BtkbPp&6Og7U&+pknJ#a)i$^sfjl=<}k9wewa)vab66IbR` z$m{q5s|@AFsFsl@d*oA6QwP4_v}Srm3&s3Bw@~cE!|Xi=wmD3=u>8?dXX_J-Q%qV$1#_Y18qVekLHW z6L;poNL{2{6-M1*Yb9)g<)Z%x*SORXEo-@vFT@T+EFA^QU6&2{Eaoacd6Br)i_Op{=-(c7Fve_|s!kYL=7M-n=L?bnDSA85M*) zpUZ<#?AXv!TET_a_wtw;`Ee&q%nSCJwvI{mDA<>DdY%ZJ$eO-X7i1ln|0sSI$AKb< zcWU=3Z?%+wxk$I`7|kFw(*SrVghvRf@G++OKwuEdF^6Vu%tlKNG>^wqNl1ry|*1*zzeFD)Sky>20qodvkhB_FVl+nk(lCgw9OSZ9+_YOa`WvDV_$RlV25w2Q#l50YI9JhU zB-YyXYvYXoyw8RC6X_%*7zLPuOnz%|l%`yQy#*&e5Z+Hv{}PAFkSM zZgXa$$!i&wGt=4XRE>JQy1XC?WN|zTUuh7N1BtksYej*mjDA$y@-WDon_}F&hxlj0 zwPDZKJ0g4>G$k+4RZ?r;LbVUVJ~1#|vpZ9bK(pi!3KZM1;2V%_%CVpJnhuO5~q);tKTxXj$IL@yo0<2-Bj#xlL}jB!xP zuVu`K8BPrmZu9m-yMBz^$&pMm-gb0}%uqlymY7W1Zuey)FZkFO-t>>PMjjE{(E;M( z=5)iAAXA&9p3ser9c0YLAxQ>1Ho)8)uqF)v*Z9){ZZ6Cet}+5Ckj(Rj8!mb?);6&^ z=F#3oL-BU53Yd4BSLM+e5u zWAf6_dti#gc}Zuz9jdJoac$v9=KP|6=H+CQ=o`< z`66Gf@#kc;`~WZrgkrBl`TGy;Qv3^u=jH=`6_KV6WQ6st%hO{>o0TC)-*s-7;li5~ z`=p5>mpv6EaK3AAZ$H1%p(fNM&zu)GVXfK?#;OXx69mrEmvuKmz?Wc}79m5KQv^l# z#0m``)0GuIpxpmngo6vFHZ!!{T{4UC!mX4kQkufTx(fI&IJlW^@OpxeAU=FopgLtZ zoTC6yOs1{fIY_IK0r4HF-_k|!Bt;>usB6uXP{uPgZXNmJxz{(~NJHYTgHX0uu<)+- z^W0>3^$f<<%Ars#$TX2mOP?lo1hHm^tAvC>2Ed`B*$kjdjHKSD5b zRyvfB;6jM6-Rcr|)^~z`eLI@7L9|37if=p08PD;sl$Io-Z14{^6IqhVh3|q2qH-l^ zWZH~QuO0RYSwfEE+~bN!L_HHzN+pP=@4mTyFP@aaP@W-7C5VD(`^?!455EYI%V_ak z2LCKtTYY)=gySu_fwdtvCz=TJ zV8Nziy!al!YHY!1CCktxEY9nOK1X9=DYh5WxE6baFS%Ih(=BpEgU_%U6mzT$|A;(n zVMe$2IbXDVY!s+5kCi;~PYNA*`R#k_obPBz(X75n{nsWkMPi?BEly7H*_Fa~xQy{F zWE%~87<8lfmF_PF(P>aExi%y>Q5pOb1aIgG{}BuS10<`WLzT6Owf7}bPYDPxH?c9U Iz2=elFEc=8@c;k- literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_seaweed.png b/babymeal/assets/images/allergy_seaweed.png new file mode 100644 index 0000000000000000000000000000000000000000..062ee594ded3978346b14024b826649eb700a0bc GIT binary patch literal 1809 zcmbVNSvVUC8jVmQmWf)b8q1)OCbc$Il#rAgswEm!Y9~#o8%x#FSZXOZs;IWcGSt2k z+-Xv4Tep_6gchNcP_AgBmWvTBZEo)Cyv;nE|9t2Fzw@29|775uaB^TZFaQ9MbFjC) zCgPyK0Fo3omLqfI ze+&SStZ}f#xWB1Bn0-to(xW(RM1eHn`Pf}O1*uno3` zXd6%0e0L165#wOntbz>=haAk_Y?@gqWG9Y}zI5c4l7=F>_i4`(tSRIZKF)ZcCzk*W zvDMnWgp2bDE7c6CNin&^l{F_wPkL5chL| zK(?rev-+Ta7{?xXWId}E?}}}zw$RIN4C|yQHYUNXkM2-^2gK)2Ya}OEmm_nRSEjHq}X(vG7Be!5q%e?v>_50^^``X9QLdE zKcr@rnzWJs8Hjss1{hbsdATPEURW1T5mv<4`pciFRL#d)^Q$Z-w(5YZ^^tJYYJVHt ziJJOMa6Sq1l6!dT#y0nh@G&XWG-!F^l~C&pDXI;c8DtC+e>i=`CjuaOX+}r&>1Hhl znV?ijOCpS1%s=!1r7uFxm1n^4g_Xo)P3Lh!qQ3phJ0pSrSoz}f3Sa^D5M8?K)i3pVU&34qv@HDws}rET&rlc{34 zsJl1JUC)B8Oh;%t#glWli-Y&z(cl>%vO-+W$nV0-o+8U}gqp%HcWP~j=bn*$?TqO6H9VOV{Zp`k}ke-h!s^?{ocYKX?TYy)xS z37AC9zXKKQv}Eb}C7cj>RJ%#AbC$_`bqI*J@|K&KuoIuS9h|b%;QuofI!SvRSy=Uc z%U1n9Qn=q^UECP9t;5!Qfcf<_@Ok--ni0zo%x@ z19gPowKBbqTPSY!t9HNCEd3V9S>-Ox!OirFJyqyg-xoFO9!V&IHcGk)gQ_64J#R*K zw651@R#xE`_U+yRhTPii0<*o{;b@&52)-;}L%kh?c+BSX3PSP&YCaH&(5r~iFYG&1 z8iu6M;3Q90xKdVO=T}4?kM6!IiuK|S1b^eYYI!ivXhcyv3En71JMT$6^-3TvuGdLj&LnkOcr~j&BFz=W|I$cokeMPC zm0a<~&yW)-F4(bFNyWO$LN?Th&;O*BZjPjN4J22@k;z&!jOGS2*6y~0QE~$d`{{Kd z!^ty+`0fppJy?oze^+JKWvaRSXhtEt5 zbxYG7-jwvJzS{{V>W)+mNcot+WJB}DGdhkC{C!1V^ecmFFM3p>`h)ckQ{&rv3i@tS zOmBU7%~1-eK)S4#O{+)K#anfq4kO_7^F5hsAq&*O02y6e{>$6rSdjqnc#vK_-I;UV*WamWIa#n;65pGkNM zxI}7Nz?a?-85!P00juS_#Fwy|s#7&DDf%4?t-l>R^yUVae~Qr}jCAz*K;P1-2deg^ z#@hSnNd$_AmPXHDDb(uU2Xebllbd?1&_NGv(C0c}S7V?q$@^{uk$i8os?)lDYRok4 or$yI&xR!uT{=fhHe>KCJ+$*_Nxd;qnT-0Cy2RkQQ2G%d-Pvpf*pa1{> literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_shrimp.png b/babymeal/assets/images/allergy_shrimp.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec7b5d92b64a926ed986354447e4b72c6555861 GIT binary patch literal 2157 zcmb`J`9Bkk1IM>9W=gI`qD{FoNA7!bQ_VGJCZf!hW3GAPYb>nDD9N!f6mrzFka&m` zGD|XQg%~-KB{_OrkLUIM6TaUcKA+F){rLwzABr=^?l4pa3IG5OJJ?&h9wh!>K?Dw5 zPPVz?AcUgqy$Jw-u*AOt0*Xte4@w}x)y@*o$dX+=0RCX~c{BjC5 zX#1svw`TL7#l(Sp7u2;LzlQY6o)RTwqnO$t?Y{pB26`%A>=L6`G$1kg&@5Bo#gMk1 z((1H4#}imTRda&g3h#Y^@G98`;!S&2m&;O|r-n=!@O*3Ch~U~k@@K2p5SnF~HCYkq zj_Qii=0{@BB|V#&zjf`1!$>U1NF{?Xvjugz6IZV1Ws-r8jl{lPT!JZ%RbW)SrOZo@ z2fAN(FH`RB?^#I50zd6tbfDr3PT!)G4KACN@14*8Dh6>1-y1yj>h>&+$z*I7AWo(| za-~mG5^Uc}@}C;_62$dL=JxrOpJ}zU^jg>io6(tYy&qpSF9+cD$P1{mvGWZzQkJJ4 z!|dC1{wzMW8Pw)csT7~UEL0b$!65<1InZu&qg?&16SAKU(HT9)7~y)Qa^t?{U}Pujx5AyUia9 zl<|e(rJr@%BIa1bp=swv^AMIJsU* zvQGl5T?h}YM&1O04c~Ev5qm|rlpOiw4cg1=an8^MvHrMVNgDf7j6)A^P5;nS?u_NQR@Oe3Hbm&r|G@Jrf?A2nzlY^W*OfSzUg=#TdL9 zS->j%#!Q_hp{TvTd0!5QTL_c!hQL&QHG2)K$uJN`!^b9@T@sV$8uugb`fx{*qps$p ziTr}hdT7KU3<7AuZYE@#(vsEY6BNqWHl?>rbu~Q2&UY+y< zJjU)Z^%;s)Bo>vzCNvL_n%~h|p2##t;Ayk)&iB?yw303Nss-J0anM(l|U{ws&dt= z$^8T#4>#miQsKJ-y|2Om66OTRkcp(w7@9Rfu5$Qm-E$rOrNAjn@ln-{W8;Q4xf_dl zN2_Cno30so2ToS}a!zeKXGf*hp|H=^e2-o}-f^Gt;kEW{%m7D=>|-^Zq!KdnX#%wd zhVe$>B+qNw7wvL>L}$N@Nww6_+#bt(6?=P=BQwdEadHi-@%w9zoK$rMy+OHLh~<={ z1BkL*vYr-1WDYbzR7+2D9Jji@T$ORHkTuS<=WNy8TkXnB`o=?n z*KBEM06P(v4OLCBTf_7#G~g*zp0GnGsCG#KWuw|t!W?W(Bg-8jfBT8Ok6Z>x*_X#r zb!&yH8f>wOH%?oYbl^$H<0gwRowW2R5fje1M4usl)(n2~quh5(F@vv9d^+UvGjNgd z(aKhB--L}Yvlc`rZxNdaNiF9=Des)kq(zV|u}Dx<#!+lHUhoX>KRzF9ixA7|{cqQ= z%PEK2L~qRHEzFeCKu=nfqocFFPE>LL8*DfK{bLnY^Q=o1wPG*Be5@|}8Sy);;g^4k zWRQ%S#_hhL%&8?{n zZQ_t><4Q_0gQ)kP14B5|=XG;XPxVIjR`BZvYe2578pR1|%c^j{b4GVqa&xJo{%Mn; z^{@oSsI_z9OYHjxj)RlO8O6KTZw&bZ*&9EmBV;* z0nsWdWim?sa5Q{BuMu3!TP3Ob$!n+vr3%=wujSJX`9G;>#9GMN!~{UEO2${((;eT_ zpTk9mjD)6yH6ZHX8$o801DCB}6D^HE2gS^M++mOX+f?FW&fNXycLGjR_3`F(J3E7* zvP7|Zq!XX=mJs=FC84B~t&>^=WQv&XNk)-rsQ8{StArMD1;=1`7V_nC``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB?s`ub$B+ufw{tfdt2m0Z&tL7$!8uV;#ieaR zh{vmX0RayVmX;(ZO+}-Y6CRwL6Yu`?^1Ui?Ihp(5X=EnmE((DBg@g~FH#7J42s;wpXILdRJwTuAzP>YlerM$5 zRmiKm7xFW*N#a<=;YtC?xd+alJ3F(qe72IkbHL3%LEm?J2&v>c-uzQk85!>*w`tc* z$&(sh!2!!ZdoSm34-7Xt?!W2h(?^efA9AZ!C-E7XwBYwKa3)~F#@vv*TAZ}vw=T}E-gvipsvwd^_ZQ3TJ$m_Uq zaYxC#HGT~x-UR)Xx^?lb%gGl{h@H@#+^4W<_3!F#uD~ls zFFCYs3+B#R^Y6M+CP!68#{1qPN7)O{4sCqenl1I)G579K71hg^*P2az!JSl*o%DY> zm)OxIUpHQlQ+>g0Ww9Ycp>0#pnN>&GUvPH`+Sb;ld3#U#TX@Tp$$F0AHM`B5H=XUi z)%3!?qw&IoFqiOvHCy>R8Jy>6>}pC{YkTat?@S9%r?848veS1@&3$w4-mB+FRy2Ry z{o`eu__gcz{l5A9JAL}$TV|KCThV_-k62Zl%Bbr|meWj2s;RA#s`Fc;WfaO(I7?jQ z)R9$Yhm+RSp19L8xw+>V>%`uvf?m1FYin6fSxzK+pcXj^=^H3-7#{T$gCg9Jeie3U2+}4uOeeiB!y1iGkU~v%IwfBwTWB( z=T#gH`WExUrpavc@*|7bAI%R;)jP$;9e-{Ud7Ij2SCQg(|% z+vO5z>9ZVODTz@_?#^pT+PQqy-suk~+AcbuRvUU#x>s|LUZC2gvy&HIH4(by=&CX` zU5>*=<#5W}S$97d|11?d{_~aPdfgDNOE&}8zG^;q|02)CnzKop{eH!Tg)W~IxNWQA z1(W#;g&jkAl@^{VpPS41WOKK*<H>ne?&NjBA2&NbZGS z21|1UPg`XaT*%2;8m zIc_`tC6HOg{NSOcCrM^!&v$c&N_FKM9O}?eYQ8daNz;qRKY5aK)Qnn;&L6c|5Yud; z+;KwV+bzcI#Ya|Mm7lmw!QtGyciABm1O!w&uiakd5q?4c;Qz8_a!Lhw8S<0gylZBy UwCjlwU{S{4>FVdQ&MBb@00^d97XSbN literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_sulfurous.png b/babymeal/assets/images/allergy_sulfurous.png new file mode 100644 index 0000000000000000000000000000000000000000..1f1504a7fea8f56b4af42cbb831c3870e8ecd48c GIT binary patch literal 1431 zcmb7^{Xf$Q0LQ;#wXM`PPgzq-W0I%UM3HXuG!r&VQC4}#LpbJao_g4FqC9sVrj?L~ zn{Y)QQc51z9a?iFkMod+7?qpKaQfwby}KXYpU>;_`2*gG6h~V{C=v<)fTA7A%6X&x zzXRU9ks7GS`5OfZCAo(Kz*eR2$N{;$og1fIxU(%0sOUn?YzzfIf&&2n9;R-Uod*E` zNYBoS;2J46&$CYzxhc0hq1v@YQ9wa8g>Ds)s9Mc+r9Rc7b9s7aX^sRt$RU86@?V&CR-4@ zkn@5{7%fGIy24C#!Ki<^#}PW?I`>DL$0sh6Xd?GDwoH@_fxiVqYe#xCudB~DmPFP$ zB>Vs!n@*HkP`IWQdBZorwS5qHhjP2nMVr@Xnmm6+8ZDV>Z`MGjN;_us;D{W!vV`JZ zy_zY1B&jw!-NVt0H6WZ~6xM7TRR{|_P&LmzF;?Gq^!MY=LY7k-Zm%4+bgt$cO46wU zpr}#Q7KTm30vMu02r#1%CRG(JI?er#NZ!=Tf|o522nvTEDF4Yhd*Ilu2K_x~l9ox* zJzereOZx2C82iU4bmpKD=aoTuRj42SX6-i@NewTMS^y@(_K1D6daXy&hh|U&Yb{pI zP{t-A1+{chy;orGIjv<9ckc<~GJTe4SlFnreDqVTr*kPAG?abggSdyb$jtp;7S>W zU5aj~X68NBTz^00>Z~v0-tT=J5IBA?KW2fMA#aPPpDu1nUJqcrvL6{|+LO~x-55U} zthQyiJmxRKdHV6ZEAualmb&`iX)TMZu4R`Y#imdWr#LwEb7QvT7mxngg^?fw88423 zz0?5@CYikv_}o?S;%I-wN589#`?l*@a{p9F#>Z|+_Mq(cT*BIWh5jpb(rH|fwu*;x zXQQfv{bH|liS=jy$*T05=rTj+iv!-xjJX>BG7}}*AvgvDW_q3pet-?`Y#Z=#!bF^b z!H`~@$b?p_^!3>-znC#6#u2K&SQ4*fqWUCPrKDf^KeBDH2DJz9!3d z4L>>^gJ*wzf0Qo;wDy$uYiK(7Xy9k~uKA#3FV2@80|EPFQ@Z&tERPA#KHqK5da9B^ ztWYFRyshG*2!+qr(F*bFp0}gA%BV%KJ0Tri7vlRWift<~C;{`yq`>@FXbN1Vg)x_D zJg5rlTUWW&!uW#5{kBsuecl6na#iu0;r>Ue`ivdJPByjk@JU^<{Iymem!Rd9k*(NI zvzv%8)~PMGQ>Qj#+|yW7my5h3&TI>v@G0!I@U=#O3ES+EQaZ+E^z#QHA4bCzPu5LT zdTK5=gvS9%XiFi)?Sy*4jTWPnTB{Prvvv#At4b}49k`pixdIuIdOcBGD)s{Q7WuN3 zeRM2)G;w-GRk0SAlEBsIW~x=hZgaC7?j%2{HI&xtii@`aAJjuUPS8%o;mK*@x`JGt zw$~&=ymdjYUfXMb!e&+Dw~_d&F7^s;FfV|=vpKOY2a(G4?WQcc862i>5BI8IX5Puj zLa_~$o}twLg8@WI#t`vU&alX`R$PBi5E)HeED#2jh_u%KF!aJ+(8#nok)yDx)^3r= z5=v!_1M_P18R*Y8_f1PYJC8i@KgsPb#JLR5L=0=gGeLR#4m>kj)8Qo;L$K=QWB literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_tomato.png b/babymeal/assets/images/allergy_tomato.png new file mode 100644 index 0000000000000000000000000000000000000000..47caba160599fa30ddc5f636e996a4b6301b2522 GIT binary patch literal 1535 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 b{9(O1b;HN}=j^$_GK;~})z4*}Q$iB}8Ba_w literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/allergy_wheat.png b/babymeal/assets/images/allergy_wheat.png new file mode 100644 index 0000000000000000000000000000000000000000..2128667b6cf907f21f4aab2e30e598fb472ce254 GIT binary patch literal 1704 zcmbW2iB}Q`0>;4wFC-nsFhWPmE9{{uW?Cwc2Y8|8XkI&*rDmRExaHA?2Zn8GIWr~- zd8NMaCFZIDqiKmpi-P8v3MranHB#ON<~ncwg5CFi-}k-m`v*R{zn>>m6QKzJ0H8#I zTi`*){V8?T12;srRUd>#3L!KN0D$QIDIkDRX>c$C(*iy5fTzRAm4gC`#rfg@fR8iveO4E}J2bRKGs4z#NFOE0^G>&u>N;_40!$~NC-+r! zRqQH%phjskjVQ5oMM7cd)qUO`b8!P4mE&c6+Yd3zMSbZLJ=j1KG3$k`iS4@$`}}V_ z>k+oIymNFWWGHKs&v|L4!`hlhsD4B!aTqS^gHleBPcDTQGbaOEIYX;+voB&kbXg|& zYrx76p{#B1Mm4wov_!^~n+$skC_j$nX%)Pop@?^oBpWA6=4P5=%oVb;*Yg2BY%=6f zX`~-I?$hPSZNm&9l3~Qm|bZ#ZvV74p5a!#;$6RcBkDg$t( z1>;C@X&SQwE(p@nPYE>>?fL7&OXuMlQmLb!OPvw;J4JqBj~GKL1GF#kSxek1eru4VN0o4Ky{!>YvwPSozF7MGoxD^+H+ zL_tU!>->{=X22VOwR*}lv|ex9Ww1Sr?8LEU_%y`Ur#oO(0yZ~hPJe%ddT3%B)oDE6 z6tljIdo0Qt$y@VT6X~5j`72S__Uuf$wC8QdYNH9}6C&DS%4fBpxsPSFHbiNLgKEml z@}cNg!^>)e7i8$8snM6de(pC;t>tF$qu-`zTu-R(x_@d_HRd6{q2PA<3iBFH(N=p? ze*p4c>vE(?w;?5wi{g|szOzub>kza8wFiE#W(x!sBAR_3*yVa~B>5ub@%gZ=dA)&{ zAEN^D=+d^s&rW2J@l=H`JHrK4nVuUeBfMkun&DI0)%G>EkLL#(H&J+2Avx}diuvq| zLr&O_YKA-lR7Xv$z!e18rHwBeR2Ld2+((HF-SsmAE1Wn|pfu!%bAqph&?jL?+vbK6d3PA2VX@8d-Wxj)hdEXpe&%q*gXar71BBCOelzaxD?wTR zp0C|?*gUTI(Ui$@LvlK5;tDCwz?Z@*nZ()GOR3pu8OmPL_?Oa1(tyw~vqKfm5@YKE z>vqN*SAr69HXIu>ly$U$R9@A?ZZ^$6#5{O>`po=0c04;O7}sGuaI?68cN7!vJ$QK*Cu zS!*7>-cQoC;}(#url)Q#0Vi{N?=xG83{z0aJO5y^=d?q(BMlvXOw;*Rj(wSts|F}w zjBhYIyXnUg=Y|7fm`1KgfS)Q@6MFX)90uj~MAGNZ?n{@p+?a#%H zEKUZy}=-6KqWOcHs=N53^JJPfcRTQfjfsi&hv@j%e5YISGM2 vB;Jq6Me5)Ai%O5!RMFoMxWz{7|ASJy2({M=ZpJqs91DQx?&tOtPtN%-eMKub literal 0 HcmV?d00001 diff --git a/babymeal/assets/images/level2_none.png b/babymeal/assets/images/level2_none.png new file mode 100644 index 0000000000000000000000000000000000000000..32b1081cc79e84ccdc84c1b21710c7661b6786c7 GIT binary patch literal 1172 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 eUA>ztz5f6oHy3m`Q&3R=0000 :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/babymeal/lib/model/CustomerModel.dart b/babymeal/lib/model/CustomerModel.dart new file mode 100644 index 0000000..b583b0f --- /dev/null +++ b/babymeal/lib/model/CustomerModel.dart @@ -0,0 +1,25 @@ +class Customer { + final String customerName; + final String rank; + final int myLikes; + final int myComments; + final int myPosts; + + Customer({ + required this.customerName, + required this.rank, + required this.myLikes, + required this.myComments, + required this.myPosts, + }); + + factory Customer.fromJson(Map json) { + return Customer( + customerName: json['customerName'], + rank: json['rank'], + myLikes: json['myLikes'], + myComments: json['myComments'], + myPosts: json['myPosts'], + ); + } +} diff --git a/babymeal/lib/model/FridgeRecipe.dart b/babymeal/lib/model/FridgeRecipe.dart new file mode 100644 index 0000000..4bc38a9 --- /dev/null +++ b/babymeal/lib/model/FridgeRecipe.dart @@ -0,0 +1,41 @@ +class FridgeRecipe { + int? simpleDietId; + String? dietName; + int? time; + String? difficulty; + bool? heart; + + FridgeRecipe({ + this.simpleDietId, + this.dietName, + this.time, + this.difficulty, + this.heart, + }); + + factory FridgeRecipe.fromJson(Map json) { + return FridgeRecipe( + simpleDietId: json['simpleDietId'], + dietName: json['dietName'], + time: json['time'], + difficulty: json['difficulty'], + heart: json['heart'], + ); + } + + Map toJson() { + return { + 'simpleDietId': simpleDietId, + 'dietName': dietName, + 'time': time, + 'difficulty': difficulty, + 'heart': heart, + }; + } + + @override + String toString() { + return 'FridgeRecipe(simpleDietId: $simpleDietId, dietName: $dietName, time: $time, difficulty: $difficulty, heart: $heart)'; + } + +} \ No newline at end of file diff --git a/babymeal/lib/model/RecipeDetailModel.dart b/babymeal/lib/model/RecipeDetailModel.dart new file mode 100644 index 0000000..f7b9183 --- /dev/null +++ b/babymeal/lib/model/RecipeDetailModel.dart @@ -0,0 +1,48 @@ +class RecipeDetail { + String? dietName; + String? description; + String? ingredients; + String? recipe; + int? time; + String? difficulty; + bool? heart; + + RecipeDetail({ + this.dietName, + this.description, + this.ingredients, + this.recipe, + this.time, + this.difficulty, + this.heart + }); + + factory RecipeDetail.fromJson(Map json) { + return RecipeDetail( + dietName: json['dietName'], + description: json['description'], + ingredients: json['ingredients'], + recipe: json['recipe'], + time: json['time'], + difficulty: json['difficulty'], + heart: json['heart'] + ); + } + + Map toJson() { + return { + 'dietName': dietName, + 'description': description, + 'ingredients': ingredients, + 'recipe': recipe, + 'time': time, + 'difficulty': difficulty, + 'heart': heart + }; + } + + @override + String toString() { + return 'RecipeDetail(dietName: $dietName, description: $description, ingredients: $ingredients, time: $time, difficulty: $difficulty, recipe: $recipe)'; + } +} \ No newline at end of file diff --git a/babymeal/lib/model/RecipeModel.dart b/babymeal/lib/model/RecipeModel.dart new file mode 100644 index 0000000..1577d08 --- /dev/null +++ b/babymeal/lib/model/RecipeModel.dart @@ -0,0 +1,44 @@ +class GetRecipe { + int? simpleDietId; + String? dietName; + String? description; + int? time; + String? difficulty; + bool? heart; + + GetRecipe({ + this.simpleDietId, + this.dietName, + this.description, + this.time, + this.difficulty, + this.heart, + }); + + factory GetRecipe.fromJson(Map json) { + return GetRecipe( + simpleDietId: json['simpleDietId'], + dietName: json['dietName'], + description: json['description'], + time: json['time'], + difficulty: json['difficulty'], + heart: json['heart'], + ); + } + + Map toJson() { + return { + 'simpleDietId': simpleDietId, + 'dietName': dietName, + 'description': description, + 'time': time, + 'difficulty': difficulty, + 'heart': heart, + }; + } + + @override + String toString() { + return 'GetRecipe(simpleDietId: $simpleDietId, dietName: $dietName, description: $description, time: $time, difficulty: $difficulty, heart: $heart)'; + } +} \ No newline at end of file diff --git a/babymeal/lib/pages/mypage/ChangeChildAllergy.dart b/babymeal/lib/pages/mypage/ChangeChildAllergy.dart index 50e1142..a782f71 100644 --- a/babymeal/lib/pages/mypage/ChangeChildAllergy.dart +++ b/babymeal/lib/pages/mypage/ChangeChildAllergy.dart @@ -1,5 +1,10 @@ +import 'package:babymeal/pages/mypage/ViewChildInfoPage.dart'; +import 'package:babymeal/pages/mypage/ViewMyPage.dart'; +import 'package:babymeal/services/MyPageService.dart'; import 'package:flutter/material.dart'; - +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class ChangeChildAllergyPageWidget extends StatefulWidget { const ChangeChildAllergyPageWidget({Key? key}) : super(key: key); @@ -32,6 +37,29 @@ class _ChangeChildAllergyPageWidgetState "조개류", "잣" ]; + + List imageList = [ + 'assets/images/allergy_egg.png', + 'assets/images/allergy_milk.png', + 'assets/images/allergy_buckwheat.png', + 'assets/images/allergy_peanut.png', + 'assets/images/allergy_bean.png', + 'assets/images/allergy_wheat.png', + 'assets/images/allergy_mackerel.png', + 'assets/images/allergy_crab.png', + 'assets/images/allergy_shrimp.png', + 'assets/images/allergy_pork.png', + 'assets/images/allergy_peach.png', + 'assets/images/allergy_tomato.png', + 'assets/images/allergy_sulfurous.png', + 'assets/images/allergy_pinenut.png', + 'assets/images/allergy_chicken.png', + 'assets/images/allergy_beef.png', + 'assets/images/allergy_squid.png', + 'assets/images/allergy_seaweed.png', + 'assets/images/allergy_pork.png', +]; + List isSelected = List.generate(19, (index) => false); @override void initState() { @@ -43,6 +71,71 @@ class _ChangeChildAllergyPageWidgetState super.dispose(); } + + Future updateBabyData(String newAllergy) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + MyPageService myPageService = MyPageService(); + String babyId = await myPageService.getBabyId(userToken!); + + final currentData = await myPageService.fetchCurrentBabyData(babyId); + + if (currentData == null) { + print('Failed to fetch current data'); + return; + } + + // 변경하고자 하는 필드만 새로운 값으로 업데이트합니다. + currentData['data'][0]['allergy'] = newAllergy; + print('newBabyName: $newAllergy'); + print('Current data: $currentData'); + + final babyData = currentData['data'][0]; + babyData['babyId'] = babyId; + + print('babyData: $babyData'); + + + + if (userToken == null) { + print('No token found'); + return; + } + + final response = await http.put( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }, + body: jsonEncode(babyData), // 변경된 전체 데이터를 서버에 보냅니다. + + ); + + if (response.statusCode == 200) { + print('Baby data updated successfully'); + } else { + print('Failed to update baby data'); + } +} + +Future navigateFromChangeChildAllergyPage() async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => ViewChildInfoPageWidget()), + ); + + if (result == true) { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + MyPageService myPageService = MyPageService(); + String babyId = await myPageService.getBabyId(userToken!); + myPageService.fetchCurrentBabyData(babyId); + + } +} + @override Widget build(BuildContext context) { return Scaffold( @@ -52,9 +145,23 @@ class _ChangeChildAllergyPageWidgetState height: 55, child: FloatingActionButton.extended( elevation: 0, - backgroundColor: Color( - 0xFFFF5C39), ////////////////////////////////////////////////////// - onPressed: () {}, + backgroundColor: Color(0xFFFF5C39), + onPressed: () async{ + List selectedAllergies = []; + for (int i = 0; i < isSelected.length; i++) { + if (isSelected[i]) { + selectedAllergies.add(allergyList[i]); + } + } + String newAllergies = selectedAllergies.join(', '); + await updateBabyData(newAllergies); + await navigateFromChangeChildAllergyPage(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ViewMyPageWidget())); + }, label: Container( width: MediaQuery.of(context).size.width * 0.88, child: Row( @@ -158,6 +265,24 @@ class _ChangeChildAllergyPageWidgetState borderRadius: BorderRadius.circular(12), ), ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Stack( + children: [ + Image.asset( + imageList[index], + fit: BoxFit.cover, + ), + if (isSelected[index]) // isSelected가 true일 때만 오버레이 표시 + Container( + decoration: BoxDecoration( + color: Color(0xFFFF582C).withOpacity(0.2), // 투명한 주황색 + borderRadius: BorderRadius.circular(12), + ), + ), + ], + ), + ), ), Text( allergyList[index], diff --git a/babymeal/lib/pages/mypage/ChangeChildBirthPage.dart b/babymeal/lib/pages/mypage/ChangeChildBirthPage.dart index 0f605f3..16309b3 100644 --- a/babymeal/lib/pages/mypage/ChangeChildBirthPage.dart +++ b/babymeal/lib/pages/mypage/ChangeChildBirthPage.dart @@ -1,6 +1,11 @@ import 'package:babymeal/pages/auth/SigninEnterAllergyPage.dart'; +import 'package:babymeal/pages/mypage/ViewChildInfoPage.dart'; +import 'package:babymeal/pages/mypage/ViewMyPage.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class ChangeChildBirthPageWidget extends StatefulWidget { const ChangeChildBirthPageWidget({Key? key}) : super(key: key); @@ -26,6 +31,85 @@ class _ChangeChildBirthPageWidgetState super.dispose(); } + Future?> fetchCurrentBabyData(int babyId) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print('No token found'); + return null; + } + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final Map data = json.decode(utf8.decode(response.bodyBytes)); + return data; + } else { + print('Failed to fetch baby data'); + return null; + } +} + Future updateBabyData(int babyId, String newBirthday) async { + // 기존 데이터를 불러옵니다. + final currentData = await fetchCurrentBabyData(babyId); + + if (currentData == null) { + print('Failed to fetch current data'); + return; + } + + // 변경하고자 하는 필드만 새로운 값으로 업데이트합니다. + currentData['data'][0]['birth'] = newBirthday; + print('newBabyName: $newBirthday'); + print('Current data: $currentData'); + + final babyData = currentData['data'][0]; + babyData['babyId'] = babyId; + + print('babyData: $babyData'); + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No token found'); + return; + } + + final response = await http.put( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }, + body: jsonEncode(babyData), // 변경된 전체 데이터를 서버에 보냅니다. + + ); + + if (response.statusCode == 200) { + print('Baby data updated successfully'); + } else { + print('Failed to update baby data'); + } +} + +Future navigateFromChangeChildBirthPage() async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => ViewChildInfoPageWidget()), + ); + + if (result == true) { + fetchCurrentBabyData(7); + } +} + @override Widget build(BuildContext context) { return Scaffold( @@ -43,12 +127,15 @@ class _ChangeChildBirthPageWidgetState onPressed: change_yearController!.text.length == 4 && change_monthController!.text.length == 2 && change_dayController!.text.length == 2 - ? () { + ? () async{ + final birthday = '${change_yearController!.text}-${change_monthController!.text}-${change_dayController!.text}'; + await updateBabyData(7, birthday); + await navigateFromChangeChildBirthPage(); Navigator.push( context, MaterialPageRoute( builder: (context) => - SigninEnterAllergyPageWidget())); + ViewMyPageWidget())); } : () {}, label: Container( diff --git a/babymeal/lib/pages/mypage/ChangeChildNamePage.dart b/babymeal/lib/pages/mypage/ChangeChildNamePage.dart index 094083b..68401b4 100644 --- a/babymeal/lib/pages/mypage/ChangeChildNamePage.dart +++ b/babymeal/lib/pages/mypage/ChangeChildNamePage.dart @@ -1,6 +1,10 @@ import 'package:babymeal/pages/mypage/ViewChildInfoPage.dart'; +import 'package:babymeal/pages/mypage/ViewMyPage.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class ChangeChildNamePageWidget extends StatefulWidget { const ChangeChildNamePageWidget({Key? key}) : super(key: key); @@ -32,6 +36,87 @@ class _ChangeChildNamePageWidgetState extends State { super.dispose(); } + Future?> fetchCurrentBabyData(int babyId) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print('No token found'); + return null; + } + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final Map data = json.decode(utf8.decode(response.bodyBytes)); + return data; + } else { + print('Failed to fetch baby data'); + return null; + } +} + Future updateBabyData(int babyId, String newBabyName) async { + // 기존 데이터를 불러옵니다. + final currentData = await fetchCurrentBabyData(babyId); + + if (currentData == null) { + print('Failed to fetch current data'); + return; + } + + // 변경하고자 하는 필드만 새로운 값으로 업데이트합니다. + currentData['data'][0]['babyName'] = newBabyName; + print('newBabyName: $newBabyName'); + print('Current data: $currentData'); + + final babyData = currentData['data'][0]; + babyData['babyId'] = babyId; + + print('babyData: $babyData'); + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No token found'); + return; + } + + final response = await http.put( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }, + body: jsonEncode(babyData), // 변경된 전체 데이터를 서버에 보냅니다. + + ); + + if (response.statusCode == 200) { + print('Baby data updated successfully'); + } else { + print('Failed to update baby data'); + } +} + +// ViewChildInfoPageWidget에서 +Future navigateFromChangeChildNamePage() async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => ViewChildInfoPageWidget()), + ); + + if (result == true) { + fetchCurrentBabyData(7); + } +} + + @override Widget build(BuildContext context) { return Scaffold( @@ -45,11 +130,13 @@ class _ChangeChildNamePageWidgetState extends State { ? Color(0xFFFF5C39) : Color(0xFFBDBDBD), onPressed: change_nameController!.text.length > 0 - ? () { - Navigator.push( + ? () async{ + await updateBabyData(7, change_nameController!.text); + await navigateFromChangeChildNamePage(); + Navigator.push( context, MaterialPageRoute( - builder: (context) => ViewChildInfoPageWidget())); + builder: (context) => ViewMyPageWidget())); } : () {}, label: Container( diff --git a/babymeal/lib/pages/mypage/ChangeNicknamePage.dart b/babymeal/lib/pages/mypage/ChangeNicknamePage.dart index cd601fd..2d53bc4 100644 --- a/babymeal/lib/pages/mypage/ChangeNicknamePage.dart +++ b/babymeal/lib/pages/mypage/ChangeNicknamePage.dart @@ -2,6 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:babymeal/pages/mypage/ManageMyPage.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; + class ChangeNicknamePageWidget extends StatefulWidget { const ChangeNicknamePageWidget({super.key}); @@ -14,6 +18,7 @@ class _ChangeNicknamePageWidgetState extends State { TextEditingController? nickNameController = TextEditingController(); int _charCount = 0; bool isExist = false; + @override void initState() { super.initState(); @@ -32,6 +37,43 @@ class _ChangeNicknamePageWidgetState extends State { super.dispose(); } + Future updateNickname() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/nickname'; + + if (token != null && nickNameController!.text.isNotEmpty) { + final response = await http.put( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + body: jsonEncode({"customerName": nickNameController!.text}), + ); + + print(nickNameController!.text); + print(response.statusCode); + + if (response.statusCode == 200) { + final responseBody = json.decode(response.body); + if (responseBody["success"] && responseBody["data"] == true) { + // 닉네임 변경 성공 + Navigator.pop(context); + } else { + // 닉네임이 이미 존재하는 경우 + setState(() { + isExist = true; + }); + } + } else { + // 서버 에러 처리 + print("Error: ${response.body}"); + } + } + } + @override Widget build(BuildContext context) { @@ -46,7 +88,8 @@ class _ChangeNicknamePageWidgetState extends State { ? Color(0xFFFF5C39) : Color(0xFFBDBDBD), onPressed: !isExist && nickNameController!.text.length > 0 - ? () { + ? () async{ + await updateNickname(); Navigator.push( context, MaterialPageRoute( diff --git a/babymeal/lib/pages/mypage/ChangePasswordPage.dart b/babymeal/lib/pages/mypage/ChangePasswordPage.dart index 706b88f..57fb0bd 100644 --- a/babymeal/lib/pages/mypage/ChangePasswordPage.dart +++ b/babymeal/lib/pages/mypage/ChangePasswordPage.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:babymeal/pages/mypage/ManageMyPage.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class ChagePasswordPageWidget extends StatefulWidget { const ChagePasswordPageWidget({super.key}); @@ -12,7 +15,6 @@ class ChagePasswordPageWidget extends StatefulWidget { class _ChagePasswordPageWidgetState extends State { final scaffoldKey = GlobalKey(); - String? currentPassword = 'dddd'; //TO DO //현재 비밀번호 가져오기 TextEditingController? passwordController = TextEditingController(); @@ -20,7 +22,8 @@ class _ChagePasswordPageWidgetState extends State { TextEditingController? checkpasswordController = TextEditingController(); int _charCount = 0; - bool isTrue = false; + bool isTrue = true; + bool isValidPassword = false; bool isMatch = true; @override @@ -35,16 +38,16 @@ class _ChagePasswordPageWidgetState extends State { }); } - void _truePassword(){ - if(passwordController!.text == currentPassword) - setState(() { - isTrue = true; - }); - else - setState(() { - isTrue = false; - }); - } + // void _truePassword(){ + // if(passwordController!.text == currentPassword) + // setState(() { + // isTrue = true; + // }); + // else + // setState(() { + // isTrue = false; + // }); + // } void _matchPassword(){ if(newpasswordController!.text == checkpasswordController!.text) @@ -65,6 +68,86 @@ class _ChagePasswordPageWidgetState extends State { super.dispose(); } + Future validateCurrentPassword(String inputPassword) async { + final String apiUrl = "http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/password/validate"; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + // 사용자에게 로그인 필요 알림 처리 + return; + } + + final response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }, + body: jsonEncode({ + "password": inputPassword, + }), + ); + + if (response.statusCode == 200) { + final responseBody = json.decode(response.body); + if (responseBody['success'] && responseBody['data'] == true) { + // 비밀번호 검증 성공 처리 + setState(() { + isValidPassword = true; + }); + } else { + // 비밀번호 검증 실패 처리 + setState(() { + isValidPassword = false; + }); + } + } else { + // HTTP 오류 처리 + print('Failed to validate password. Server responded with status code: ${response.statusCode}'); + } +} + + Future updatePassword() async { + final String apiUrl = "http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/password"; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + // 사용자에게 로그인 필요 알림 처리 + return; + } + + final response = await http.put( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }, + body: jsonEncode({ + "password": newpasswordController!.text, + }), + ); + + if (response.statusCode == 200) { + final responseBody = json.decode(response.body); + if (responseBody['success']) { + // 비밀번호 변경 성공 처리 + print('Password updated successfully'); + Navigator.pop(context); // 성공적으로 비밀번호를 변경하면 이전 화면으로 돌아갑니다. + } else { + // 서버에서 정의된 오류 메시지 표시 + print('Failed to update password: ${responseBody['message']}'); + } + } else { + // HTTP 오류 처리 + print('Failed to update password. Server responded with status code: ${response.statusCode}'); + } + } + + @override Widget build(BuildContext context) { return Scaffold( @@ -74,18 +157,23 @@ class _ChagePasswordPageWidgetState extends State { height: 55, child: FloatingActionButton.extended( elevation: 0, - backgroundColor: !isMatch && newpasswordController!.text.length > 9 && newpasswordController!.text.length < 16 + backgroundColor: isValidPassword && isMatch && newpasswordController!.text.length >= 9 && newpasswordController!.text.length < 16 ? Color(0xFFFF5C39) : Color(0xFFBDBDBD), - onPressed: !isMatch && newpasswordController!.text.length > 9 && newpasswordController!.text.length < 16 - ? () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ManageMyPageWidget())); - } - : () {}, + onPressed: () async { + // isMatch와 newpasswordController!.text.length 값 출력 + print('isMatch: $isMatch'); + print('newpasswordController text length: ${newpasswordController!.text.length}'); + + if (isValidPassword && isMatch && newpasswordController!.text.length >= 9 && newpasswordController!.text.length < 16) { + await updatePassword(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ManageMyPageWidget())); + } + }, label: Container( width: MediaQuery.of(context).size.width * 0.88, child: Row( @@ -151,7 +239,7 @@ class _ChagePasswordPageWidgetState extends State { LengthLimitingTextInputFormatter(16), ], onChanged: (value){ - _truePassword(); + validateCurrentPassword(value); }, decoration: InputDecoration( enabledBorder: UnderlineInputBorder( @@ -366,6 +454,19 @@ class _ChagePasswordPageWidgetState extends State { height: 0, letterSpacing: -0.24, ), + ) + else if(isValidPassword == false) + Text( + "현재 비밀번호가 틀렸습니다.", + textAlign: TextAlign.right, + style: TextStyle( + color: Color(0xFFFF5C39), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w400, + height: 0, + letterSpacing: -0.24, + ), ), ], ), diff --git a/babymeal/lib/pages/mypage/ManageMyPage.dart b/babymeal/lib/pages/mypage/ManageMyPage.dart index c0513a5..faf65c4 100644 --- a/babymeal/lib/pages/mypage/ManageMyPage.dart +++ b/babymeal/lib/pages/mypage/ManageMyPage.dart @@ -1,8 +1,14 @@ +import 'package:babymeal/pages/auth/SigninEnterEmailPage.dart'; import 'package:flutter/material.dart'; import 'package:babymeal/pages/mypage/ChangeNicknamePage.dart'; import 'package:babymeal/pages/mypage/ChangePasswordPage.dart'; import 'package:babymeal/pages/auth/SigninSelectEmailpage.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + class ManageMyPageWidget extends StatefulWidget { const ManageMyPageWidget({Key? key}) : super(key: key); @@ -11,11 +17,94 @@ class ManageMyPageWidget extends StatefulWidget { } class _ManageMyPageWidgetState extends State { + String email = ''; + String nickname = ''; + String rank = ''; + + @override + void initState() { + super.initState(); + fetchUserInfo(); + } + +String getRankText(String? rank) { + switch (rank) { + case '초보': + return 'Lv.1 ${rank}'; + case '요리사': + return 'Lv.2 ${rank}'; + case '주방장': + return 'Lv.3 ${rank}'; + default: + return '알 수 없음'; // 기본값 + } +} + + Future fetchUserInfo() async { + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/manage'; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token != null) { + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final data = json.decode(utf8.decode(response.bodyBytes))['data']; + setState(() { + email = data['email']; + nickname = data['customerName']; + rank = data['rank']; // 서버에서 받은 등급 정보에 따라 변환하여 저장 + }); + } else { + print('Failed to load user info'); + } + } + } + + Future deleteAccount() async { + final String apiUrl = "http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer"; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print("No user token found"); + // 사용자에게 로그인이 필요함을 알림 + return; + } + + final response = await http.delete( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final responseBody = json.decode(response.body); + if (responseBody['success'] && responseBody['data'] == true) { + // 탈퇴 처리 성공 + print("Account deleted successfully"); + // 로그인 화면으로 이동하거나 로그아웃 처리 + Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (BuildContext context) => SigninEnterEmail()), (route) => false); + } else { + // 서버에서 정의된 오류 메시지 표시 + print("Failed to delete account: ${responseBody['message']}"); + } + } else { + // HTTP 오류 처리 + print("Failed to delete account. Server responded with status code: ${response.statusCode}"); + } +} + @override Widget build(BuildContext context) { - final email = 'mamma11@naver.com'; - final nickname = '서준맘'; - final level = 'Lv2.요리사등급'; return Scaffold( backgroundColor: Colors.white, @@ -157,7 +246,7 @@ class _ManageMyPageWidgetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), + padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), child: Text( '회원 등급', style: TextStyle( @@ -170,26 +259,29 @@ class _ManageMyPageWidgetState extends State { ), Row( children: [ - Text( - '$level', - style: TextStyle( - fontWeight: FontWeight.w400, - fontFamily: 'Pretendard', - fontSize: 15, - color: Color(0xFF212121), + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 15, 0), + child: Text( + getRankText(rank), + style: TextStyle( + fontWeight: FontWeight.w400, + fontFamily: 'Pretendard', + fontSize: 15, + color: Color(0xFF212121), + ), ), ), - IconButton( - onPressed: () { - //TO DO - //회원등급 페이지로 넘기기 - }, - padding: EdgeInsets.fromLTRB(0, 0, 0, 0), - icon: Icon( - Icons.arrow_forward_ios, - color: Color(0xFF616161), - size: 24, - )) + // IconButton( + // onPressed: () { + // //TO DO + // //회원등급 페이지로 넘기기 + // }, + // padding: EdgeInsets.fromLTRB(0, 0, 0, 0), + // icon: Icon( + // Icons.arrow_forward_ios, + // color: Color(0xFF616161), + // size: 24, + // )) ], ) ], @@ -199,7 +291,7 @@ class _ManageMyPageWidgetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: EdgeInsets.fromLTRB(10, 0, 0, 0), + padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: TextButton( child: Text( '계정 탈퇴', @@ -242,8 +334,7 @@ class _ManageMyPageWidgetState extends State { padding: EdgeInsets.fromLTRB(0, 0, 3, 0), child: ElevatedButton( onPressed: (){ - Navigator.pushAndRemoveUntil(context, MaterialPageRoute( - builder: (BuildContext context)=> SigninSelectEmail()),(route) => false); + deleteAccount(); }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFFBDBDBD), diff --git a/babymeal/lib/pages/mypage/MyPageComments.dart b/babymeal/lib/pages/mypage/MyPageComments.dart index e73d71f..ac5f9be 100644 --- a/babymeal/lib/pages/mypage/MyPageComments.dart +++ b/babymeal/lib/pages/mypage/MyPageComments.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class MypageMyCommentsPageWidget extends StatefulWidget { const MypageMyCommentsPageWidget({super.key}); @@ -10,13 +13,48 @@ class MypageMyCommentsPageWidget extends StatefulWidget { class _MypageMyCommentsPageWidgetState extends State { - List CommentContext = [ - '좋은 글 잘 보고 가요!', - '생각해본 적 없는데 정말 좋은 방법이네요. 시도해볼게요!', - '감사합니다~' - ]; + List CommentContext = []; + List Comment_TimeContext = []; + + @override + void initState() { + super.initState(); + fetchMyComments(); + } + + Future fetchMyComments() async { + final prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print('Token not found'); + return; + } + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/myComments'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final data = jsonDecode(utf8.decode(response.bodyBytes)); + final comments = data['data'] as List; + + setState(() { + CommentContext = comments.map((comment) => comment['contents'] as String).toList(); + Comment_TimeContext = comments.map((comment) { + final time = DateTime.parse(comment['time']); + return '${time.toLocal()}'; // 여기서 원하는 형식으로 변환해야 할 수 있습니다. + }).toList(); + }); + } else { + print('Failed to fetch comments'); + } + } - List Comment_TimeContext = ['12시간전', '10일전', '15일전']; @override Widget build(BuildContext context) { return Scaffold( diff --git a/babymeal/lib/pages/mypage/MyPagePost.dart b/babymeal/lib/pages/mypage/MyPagePost.dart index 80bec5b..7269eaa 100644 --- a/babymeal/lib/pages/mypage/MyPagePost.dart +++ b/babymeal/lib/pages/mypage/MyPagePost.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class MyPageMyPostsPageWidget extends StatefulWidget { const MyPageMyPostsPageWidget({super.key}); @@ -9,13 +12,48 @@ class MyPageMyPostsPageWidget extends StatefulWidget { } class _MyPageMyPostsPageWidgetState extends State { - List TitleContext = [ - '아이 훈육법에 대한 내 생각', - '[건대입구] 아이와 함께 가기 좋은 카페 \'웰컴베이비\' 추천합니다-메뉴,가격,주차정보', - '아이 밥 먹이는 꿀팁!!' - ]; + List TitleContext = []; + List Title_TimeContext = []; + + @override + void initState() { + super.initState(); + fetchMyPosts(); + } + + Future fetchMyPosts() async { + final prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print('Token not found'); + return; + } + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/myPosts'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final data = jsonDecode(utf8.decode(response.bodyBytes)); + final posts = data['data'] as List; + + setState(() { + TitleContext = posts.map((post) => post['title'] as String).toList(); + Title_TimeContext = posts.map((post) { + final updateTime = DateTime.parse(post['updateTime']); + return '${updateTime.toLocal()}'; + }).toList(); + }); + } else { + print('Failed to fetch posts'); + } + } - List Title_TimeContext = ['9시간전', '13일전', '20일전']; @override Widget build(BuildContext context) { return Scaffold( diff --git a/babymeal/lib/pages/mypage/ViewChildInfoPage.dart b/babymeal/lib/pages/mypage/ViewChildInfoPage.dart index fd9a808..a3b33ed 100644 --- a/babymeal/lib/pages/mypage/ViewChildInfoPage.dart +++ b/babymeal/lib/pages/mypage/ViewChildInfoPage.dart @@ -1,8 +1,13 @@ +import 'package:babymeal/model/BabyModel.dart'; import 'package:babymeal/pages/mypage/ChangeChildAllergy.dart'; import 'package:babymeal/pages/mypage/ChangeChildBirthPage.dart'; import 'package:babymeal/pages/mypage/ChangeChildNamePage.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; + class ViewChildInfoPageWidget extends StatefulWidget { const ViewChildInfoPageWidget({super.key}); @@ -12,6 +17,46 @@ class ViewChildInfoPageWidget extends StatefulWidget { } class _ViewChildInfoPageWidgetState extends State { + PostBaby? babyInfo; + + @override + void initState() { + super.initState(); + fetchBabyInfo(); + } + + Future fetchBabyInfo() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print('No token found'); + return; + } + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final data = jsonDecode(utf8.decode(response.bodyBytes)); + if (data['success'] && data['data'] != null) { + setState(() { + babyInfo = PostBaby.fromJson(data['data'][0]); + }); + } + } else { + print('Failed to fetch baby info'); + } + + print(babyInfo?.babyName); + print(babyInfo?.birth); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -70,7 +115,7 @@ class _ViewChildInfoPageWidgetState extends State { Padding( padding: EdgeInsets.fromLTRB(0, 0, 0, 0), child: Text( - '박서준', + babyInfo?.babyName ?? '', style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w500, @@ -117,7 +162,7 @@ class _ViewChildInfoPageWidgetState extends State { Padding( padding: EdgeInsets.fromLTRB(0, 0, 0, 0), child: Text( - '2020.12.01', + babyInfo?.birth ?? '', style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w500, @@ -158,20 +203,38 @@ class _ViewChildInfoPageWidgetState extends State { ), ), ), - IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ChangeChildAllergyPageWidget()), - ); - }, - icon: Icon( - Icons.arrow_forward_ios, - color: Color(0xFF949494), - size: 24, - )) + Padding( + padding: EdgeInsets.fromLTRB(0, 0, 0, 0), + child: Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 0, 0, 0), + child: Text( + babyInfo?.allergy ?? '', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + color: Color(0xFF212121), + ), + ), + ), + IconButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ChangeChildAllergyPageWidget()), + ); + }, + icon: Icon( + Icons.arrow_forward_ios, + color: Color(0xFF949494), + size: 24, + )), + ], + ), + ) ], )), ]), diff --git a/babymeal/lib/pages/mypage/ViewMyPage.dart b/babymeal/lib/pages/mypage/ViewMyPage.dart index 405e582..68b5f96 100644 --- a/babymeal/lib/pages/mypage/ViewMyPage.dart +++ b/babymeal/lib/pages/mypage/ViewMyPage.dart @@ -1,9 +1,16 @@ +import 'package:babymeal/model/CustomerModel.dart'; import 'package:babymeal/pages/mypage/ManageMyPage.dart'; import 'package:babymeal/pages/mypage/MyPageComments.dart'; import 'package:babymeal/pages/mypage/MyPagePost.dart'; import 'package:babymeal/pages/mypage/ViewChildInfoPage.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + class ViewMyPageWidget extends StatefulWidget { const ViewMyPageWidget({Key? key}) : super(key: key); @@ -13,9 +20,12 @@ class ViewMyPageWidget extends StatefulWidget { class _ViewMyPageWidgetState extends State { final scaffoldKey = GlobalKey(); + Customer? customer; + @override void initState() { super.initState(); + fetchCustomerInfo(); } @override @@ -23,217 +33,220 @@ class _ViewMyPageWidgetState extends State { super.dispose(); } - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Color(0xFFF4F3F0), - body: Column( - children: [ - Container( - padding: EdgeInsets.fromLTRB(20, 50, 0, 0), - color: Colors.white, - alignment: Alignment.centerLeft, - child: Text( - '마이페이지', - style: TextStyle( - color: Color(0xFF424242), - fontSize: 24, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0, - letterSpacing: -0.48, - ), - )), - Container( - color: Colors.white, - padding: EdgeInsets.fromLTRB(20, 32, 13, 0), - width: MediaQuery.of(context).size.width, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Future fetchCustomerInfo() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/my'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }, + ); + + if (response.statusCode == 200) { + final responseBody = json.decode(utf8.decode(response.bodyBytes)); + setState(() { + customer = Customer.fromJson(responseBody['data']); + }); + } else { + // 에러 처리 + print('Failed to load customer info'); + } + } + + String getRankText(String? rank) { + switch (rank) { + case '초보': + return 'Lv.1 ${rank}'; + case '요리사': + return 'Lv.2 ${rank}'; + case '주방장': + return 'Lv.3 ${rank}'; + default: + return '알 수 없음'; // 기본값 + } +} + + Widget rankWidget() { + if (customer?.rank == "초보") { + return Container( + padding: + EdgeInsets.only(left: 20, top: 18, right: 20, bottom: 21), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + '회원 등급', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF757575), + fontSize: 14, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.28, + ), + ), Container( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Container( - padding: EdgeInsets.only(right: 5), - child: Text( - '서준맘', - style: TextStyle( - color: Color(0xFF424242), - fontSize: 22, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w700, - height: 0, - letterSpacing: -0.44, - ), - )), - Image.asset("assets/images/star.png") - ], + Transform.translate( + offset: Offset(20,0), + child: Text( + 'LEVEL 1', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFF5C39), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w700, + height: 0, + letterSpacing: -0.26, + ), + ), ), Container( - padding: EdgeInsets.fromLTRB(4, 5, 0, 0), - child: Text( - 'Google 계정 연결', - style: TextStyle( - color: Color(0xFF9E9E9E), - fontSize: 12, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - letterSpacing: -0.24, - ), - )) - ], - )), - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ManageMyPageWidget())); - }, - child: Container( - child: Row( - children: [ - Text( - '계정 관리', - textAlign: TextAlign.right, - style: TextStyle( - color: Color(0xFF616161), - fontSize: 14, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - ), - ), - Icon(Icons.keyboard_arrow_right, - color: Color(0xff616161)) - ], - ))) - ], - )), - Container( - padding: EdgeInsets.only(top: 26), - height: MediaQuery.of(context).size.height * 0.18, - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ViewChildInfoPageWidget())); - }, - child: Container( - padding: EdgeInsets.only(top: 18), - width: MediaQuery.of(context).size.width * 0.25, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + width: 88, + height: 49, + child: Stack( children: [ - Container( - padding: EdgeInsets.only(bottom: 5), - child: Image.asset("assets/images/baby.png"), + Positioned( + left: 30, + top: 49, + child: Transform( + transform: Matrix4.identity() + ..translate(0.0, 0.0) + ..rotateZ(-3.14), + child: Container( + width: 31, + height: 34, + decoration: ShapeDecoration( + color: Color(0xFFFF5C39), + shape: StarBorder.polygon( + sides: 3, + ), + ), + ), + ), ), - Text( - '아이 정보', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF212121), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - letterSpacing: -0.30, + Positioned( + left: 0, + top: 0, + child: Container( + child: Container( + padding: EdgeInsets.only(top: 16), + child: Text( + '초보', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0.08, + letterSpacing: -0.36, + ), + ), + ), + width: 88, + height: 33, + decoration: ShapeDecoration( + color: Color(0xFFFF5C39), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), ), - ) + ), ], - )), - ), - Container( - height: MediaQuery.of(context).size.height * - 0.08, // Divider의 높이 설정 - width: 1.0, // Divider의 두께 설정 - color: Color(0xffE0E0E0), // Divider의 색상 설정 - ), - InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - MyPageMyPostsPageWidget())); - }, - child: Container( - padding: EdgeInsets.only(top: 18), - width: MediaQuery.of(context).size.width * 0.28, - child: Column( + ), + ), + Stack( + alignment: Alignment.center, + children: [ + Stack( children: [ Container( - padding: EdgeInsets.only(bottom: 5), - child: Image.asset( - "assets/images/post_write.png"), + child: Container( + width: + MediaQuery.of(context).size.width * 0.9, + height: 18, + decoration: ShapeDecoration( + color: Color(0xFFE0E0E0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + )), + Container( + width: + MediaQuery.of(context).size.width * 0, + height: 18, + decoration: ShapeDecoration( + color: Color(0xFFFF5C39), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + ), + ), + ), ), + ], + ), + Column( + children: [ + Image.asset("assets/images/level2_none.png"), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image.asset("assets/images/progress_none.png") + ], + ) + ], + ), + Container( + padding: EdgeInsets.only(top: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: MediaQuery.of(context).size.width * 0.15), Text( - '내 게시글', - textAlign: TextAlign.center, + 'Lv2.요리사', style: TextStyle( - color: Color(0xFF212121), - fontSize: 15, + color: Color(0xFF9E9E9E), + fontSize: 12, fontFamily: 'Pretendard', fontWeight: FontWeight.w500, height: 0, - letterSpacing: -0.30, + letterSpacing: -0.24, + ), + ), + Text( + 'Lv3.주방장', + textAlign: TextAlign.right, + style: TextStyle( + color: Color(0xFF9E9E9E), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.24, ), ) ], - ))), - Container( - height: MediaQuery.of(context).size.height * - 0.08, // Divider의 높이 설정 - width: 1.0, // Divider의 두께 설정 - color: Color(0xffE0E0E0), // Divider의 색상 설정 - ), - InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - MypageMyCommentsPageWidget())); - }, - child: Container( - padding: EdgeInsets.only(top: 10), - width: MediaQuery.of(context).size.width * 0.25, - child: Column( - children: [ - Container( - child: Image.asset( - "assets/images/comment.png", - ), - ), - Text( - '내 댓글', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF212121), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - letterSpacing: -0.30, - ), - ) - ], - )), - ) + )) + ], + )) ], - )), - Container( + )); + } else if (customer?.rank == "요리사") { + return Container( padding: EdgeInsets.only(left: 20, top: 28, right: 20, bottom: 21), child: Column( @@ -397,279 +410,676 @@ class _ViewMyPageWidgetState extends State { ], )) ], - )), - Container( - margin: EdgeInsets.only(right: 20, left: 20), - decoration: ShapeDecoration( - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: Column( - children: [ - Container( - padding: EdgeInsets.only(top: 25), - child: Text.rich( - TextSpan( - children: [ - TextSpan( - text: '서준맘님의 ', - style: TextStyle( - color: Color(0xFF424242), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0, - letterSpacing: -0.30, - ), - ), - TextSpan( - text: 'Lv.3 주방장', + )); + } else { + return Container( + padding: + EdgeInsets.only(left: 20, top: 28, right: 20, bottom: 21), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '회원 등급', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF757575), + fontSize: 14, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.28, + ), + ), + Container( + child: Column( + children: [ + Transform.translate( + offset: Offset(145,0), + child: Text( + 'LEVEL 3', + textAlign: TextAlign.center, style: TextStyle( color: Color(0xFFFF5C39), - fontSize: 15, + fontSize: 13, fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w700, height: 0, - letterSpacing: -0.30, + letterSpacing: -0.26, ), ), - TextSpan( - text: ' 달성률', - style: TextStyle( - color: Color(0xFF424242), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0, - letterSpacing: -0.30, + ), + Padding( + padding: const EdgeInsets.fromLTRB(280,0,0,0), + child: Container( + width: 88, + height: 49, + child: Stack( + children: [ + Positioned( + left: 92, + top: 49, + child: Transform( + transform: Matrix4.identity() + ..translate(0.0, 0.0) + ..rotateZ(-3.14), + child: Container( + width: 31, + height: 34, + decoration: ShapeDecoration( + color: Color(0xFFFF5C39), + shape: StarBorder.polygon( + sides: 3, + ), + ), + ), + ), + ), + Positioned( + left: 0, + top: 0, + child: Container( + child: Container( + padding: EdgeInsets.only(top: 16), + child: Text( + '주방장', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0.08, + letterSpacing: -0.36, + ), + ), + ), + width: 88, + height: 33, + decoration: ShapeDecoration( + color: Color(0xFFFF5C39), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + ), + ], ), ), - ], - ), - textAlign: TextAlign.center, + ), + Stack( + alignment: Alignment.center, + children: [ + Stack( + children: [ + Container( + child: Container( + width: + MediaQuery.of(context).size.width * 0.9, + height: 18, + decoration: ShapeDecoration( + color: Color(0xFFE0E0E0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + )), + Container( + width: + MediaQuery.of(context).size.width * 0.88, + height: 18, + decoration: ShapeDecoration( + color: Color(0xFFFF5C39), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + ), + ), + ), + ), + ], + ), + Image.asset("assets/images/progress.png"), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image.asset("assets/images/progress2.png") + ], + ) + ], + ), + Container( + padding: EdgeInsets.only(top: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Lv1.초보', + style: TextStyle( + color: Color(0xFF9E9E9E), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.24, + ), + ), + SizedBox(width: + MediaQuery.of(context).size.width * 0.3,), + Text( + 'Lv2.요리사', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF9E9E9E), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.24, + ), + ) + ], + )) + ], + )) + ], + )); + } +} + + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFFF4F3F0), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + padding: EdgeInsets.fromLTRB(20, 50, 0, 0), + color: Colors.white, + alignment: Alignment.centerLeft, + child: Text( + '마이페이지', + style: TextStyle( + color: Color(0xFF424242), + fontSize: 24, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.48, + ), + )), + Container( + color: Colors.white, + padding: EdgeInsets.fromLTRB(20, 32, 13, 0), + width: MediaQuery.of(context).size.width, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + child: Column( + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.only(right: 5), + child: Text( + customer?.customerName ?? '회원이름', + style: TextStyle( + color: Color(0xFF424242), + fontSize: 22, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w700, + height: 0, + letterSpacing: -0.44, + ), + )), + Image.asset("assets/images/star.png") + ], + ), + Container( + padding: EdgeInsets.fromLTRB(4, 5, 0, 0), + child: Text( + 'Google 계정 연결', + style: TextStyle( + color: Color(0xFF9E9E9E), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.24, + ), + )) + ], + )), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ManageMyPageWidget())); + }, + child: Container( + child: Row( + children: [ + Text( + '계정 관리', + textAlign: TextAlign.right, + style: TextStyle( + color: Color(0xFF616161), + fontSize: 14, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + ), + ), + Icon(Icons.keyboard_arrow_right, + color: Color(0xff616161)) + ], + ))) + ], + )), + Container( + padding: EdgeInsets.only(top: 26), + height: MediaQuery.of(context).size.height * 0.18, + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ViewChildInfoPageWidget())); + }, + child: Container( + padding: EdgeInsets.only(top: 18), + width: MediaQuery.of(context).size.width * 0.25, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(bottom: 5), + child: Image.asset("assets/images/baby.png"), + ), + Text( + '아이 정보', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF212121), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.30, + ), + ) + ], + )), ), + Container( + height: MediaQuery.of(context).size.height * + 0.08, // Divider의 높이 설정 + width: 1.0, // Divider의 두께 설정 + color: Color(0xffE0E0E0), // Divider의 색상 설정 + ), + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + MyPageMyPostsPageWidget())); + }, + child: Container( + padding: EdgeInsets.only(top: 18), + width: MediaQuery.of(context).size.width * 0.28, + child: Column( + children: [ + Container( + padding: EdgeInsets.only(bottom: 5), + child: Image.asset( + "assets/images/post_write.png"), + ), + Text( + '내 게시글', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF212121), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.30, + ), + ) + ], + ))), + Container( + height: MediaQuery.of(context).size.height * + 0.08, // Divider의 높이 설정 + width: 1.0, // Divider의 두께 설정 + color: Color(0xffE0E0E0), // Divider의 색상 설정 + ), + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + MypageMyCommentsPageWidget())); + }, + child: Container( + padding: EdgeInsets.only(top: 10), + width: MediaQuery.of(context).size.width * 0.25, + child: Column( + children: [ + Container( + child: Image.asset( + "assets/images/comment.png", + ), + ), + Text( + '내 댓글', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF212121), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.30, + ), + ) + ], + )), + ) + ], + )), + rankWidget(), + Container( + margin: EdgeInsets.only(right: 20, left: 20), + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), - Container( - padding: EdgeInsets.only(top: 26), - height: MediaQuery.of(context).size.height * 0.18, - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.only(top: 18), - width: MediaQuery.of(context).size.width * 0.25, - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - '받은 좋아요', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF424242), - fontSize: 13, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - letterSpacing: -0.26, + ), + child: Column( + children: [ + Container( + padding: EdgeInsets.only(top: 25), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${customer?.customerName}님의 ', + style: TextStyle( + color: Color(0xFF424242), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.30, + ), + ), + TextSpan( + text: getRankText(customer?.rank), + style: TextStyle( + color: Color(0xFFFF5C39), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.30, + ), + ), + TextSpan( + text: ' 달성률', + style: TextStyle( + color: Color(0xFF424242), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.30, + ), + ), + ], + ), + textAlign: TextAlign.center, + ), + ), + Container( + padding: EdgeInsets.only(top: 26), + height: MediaQuery.of(context).size.height * 0.18, + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 18), + width: MediaQuery.of(context).size.width * 0.25, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '받은 좋아요', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF424242), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.26, + ), ), - ), - Container( - padding: EdgeInsets.only(bottom: 5), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.only(top: 7), - height: 30, - child: Text( - '99', - textAlign: TextAlign.right, - style: TextStyle( - color: Color(0xFFFF5C39), - fontSize: 20, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0.07, - ), - ), - ), - Image.asset( - "assets/images/slash_my.png", - width: 10, - height: 17), - Container( - padding: - EdgeInsets.only(top: 8), - height: 20, + Container( + padding: EdgeInsets.only(bottom: 5), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 7), + height: 30, child: Text( - '100', + customer?.myLikes.toString() ?? '', + textAlign: TextAlign.right, style: TextStyle( - color: Color(0xFF757575), - fontSize: 14, + color: Color(0xFFFF5C39), + fontSize: 20, fontFamily: 'Pretendard', fontWeight: FontWeight.w600, - height: 0.11, + height: 0.07, ), - )) - ], - )), - ], - )), - Container( - height: MediaQuery.of(context).size.height * - 0.08, // Divider의 높이 설정 - width: 1.0, // Divider의 두께 설정 - color: Color(0xffE0E0E0), // Divider의 색상 설정 - ), - Container( - padding: EdgeInsets.only(top: 18), - width: MediaQuery.of(context).size.width * 0.25, - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - '누른 좋아요', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF424242), - fontSize: 13, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - letterSpacing: -0.26, - ), - ), - Container( - padding: EdgeInsets.only(bottom: 5), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.only(top: 7), - height: 30, - child: Text( - '100', - textAlign: TextAlign.right, - style: TextStyle( - color: Color(0xFFFF5C39), - fontSize: 20, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0.07, ), ), - ), - Image.asset( - "assets/images/slash_my.png", - width: 10, - height: 17), - Container( - padding: - EdgeInsets.only(top: 8), - height: 20, + Image.asset( + "assets/images/slash_my.png", + width: 10, + height: 17), + Container( + padding: + EdgeInsets.only(top: 8), + height: 20, + child: Text( + customer?.rank == '초보' ? '10' : + customer?.rank == '요리사' ? '50' : + customer?.rank == '주방장' ? '100' : '0', + style: TextStyle( + color: Color(0xFF757575), + fontSize: 14, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0.11, + ), + )) + ], + )), + ], + )), + Container( + height: MediaQuery.of(context).size.height * + 0.08, // Divider의 높이 설정 + width: 1.0, // Divider의 두께 설정 + color: Color(0xffE0E0E0), // Divider의 색상 설정 + ), + Container( + padding: EdgeInsets.only(top: 18), + width: MediaQuery.of(context).size.width * 0.25, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '작성 댓글', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF424242), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.26, + ), + ), + Container( + padding: EdgeInsets.only(bottom: 5), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 7), + height: 30, child: Text( - '20', + customer?.myComments.toString() ?? '', + textAlign: TextAlign.right, style: TextStyle( - color: Color(0xFF757575), - fontSize: 14, + color: Color(0xFFFF5C39), + fontSize: 20, fontFamily: 'Pretendard', fontWeight: FontWeight.w600, - height: 0.11, + height: 0.07, ), - )) - ], - )), - ], - )), - Container( - height: MediaQuery.of(context).size.height * - 0.08, // Divider의 높이 설정 - width: 1.0, // Divider의 두께 설정 - color: Color(0xffE0E0E0), // Divider의 색상 설정 - ), - Container( - padding: EdgeInsets.only(top: 18), - width: MediaQuery.of(context).size.width * 0.25, - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - '작성 게시물', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF424242), - fontSize: 13, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - letterSpacing: -0.26, - ), - ), - Container( - padding: EdgeInsets.only(bottom: 5), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.only(top: 7), - height: 30, - child: Text( - '9', - textAlign: TextAlign.right, - style: TextStyle( - color: Color(0xFFFF5C39), - fontSize: 20, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0.07, ), ), - ), - Image.asset( - "assets/images/slash_my.png", - width: 10, - height: 17), - Container( - padding: - EdgeInsets.only(top: 8), - height: 20, + Image.asset( + "assets/images/slash_my.png", + width: 10, + height: 17), + Container( + padding: + EdgeInsets.only(top: 8), + height: 20, + child: Text( + customer?.rank == '초보' ? '10' : + customer?.rank == '요리사' ? '50' : + customer?.rank == '주방장' ? '100' : '0', + style: TextStyle( + color: Color(0xFF757575), + fontSize: 14, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0.11, + ), + )) + ], + )), + ], + )), + Container( + height: MediaQuery.of(context).size.height * + 0.08, // Divider의 높이 설정 + width: 1.0, // Divider의 두께 설정 + color: Color(0xffE0E0E0), // Divider의 색상 설정 + ), + Container( + padding: EdgeInsets.only(top: 18), + width: MediaQuery.of(context).size.width * 0.25, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '작성 게시물', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF424242), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + height: 0, + letterSpacing: -0.26, + ), + ), + Container( + padding: EdgeInsets.only(bottom: 5), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 7), + height: 30, child: Text( - '10', + customer?.myPosts.toString() ?? '', + textAlign: TextAlign.right, style: TextStyle( - color: Color(0xFF757575), - fontSize: 14, + color: Color(0xFFFF5C39), + fontSize: 20, fontFamily: 'Pretendard', fontWeight: FontWeight.w600, - height: 0.11, + height: 0.07, ), - )) - ], - )), - ], - )), - ], - )), - ], - )) - ], + ), + ), + Image.asset( + "assets/images/slash_my.png", + width: 10, + height: 17), + Container( + padding: + EdgeInsets.only(top: 8), + height: 20, + child: Text( + customer?.rank == '초보' ? '10' : + customer?.rank == '요리사' ? '50' : + customer?.rank == '주방장' ? '100' : '0', + style: TextStyle( + color: Color(0xFF757575), + fontSize: 14, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0.11, + ), + )) + ], + )), + ], + )), + ], + )), + ], + )) + ], + ), )); } } diff --git a/babymeal/lib/pages/recommend/RecomChooseMaterialPage.dart b/babymeal/lib/pages/recommend/RecomChooseMaterialPage.dart index a9ea934..7ff40d7 100644 --- a/babymeal/lib/pages/recommend/RecomChooseMaterialPage.dart +++ b/babymeal/lib/pages/recommend/RecomChooseMaterialPage.dart @@ -1,8 +1,15 @@ import 'package:babymeal/pages/recommend/SelectKeywordPage.dart'; +import 'package:babymeal/services/FridgeContentService.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class RecomChooseMaterialPageWidget extends StatefulWidget { - const RecomChooseMaterialPageWidget({super.key}); + final String selectedOption; + + const RecomChooseMaterialPageWidget({Key? key, required this.selectedOption}) : super(key: key); + @override State createState() => @@ -13,17 +20,7 @@ class _RecomChooseMaterialPageWidgetState extends State { final scaffoldKey = GlobalKey(); - List materialList = [ - "체다치즈", - "고등어", - "달걀", - "통밀 식빵", - "브로콜리", - "양파", - "당근", - "우유", - "토마토" - ]; + List FridgeContentsList = []; List imageList = [ "assets/images/cheese.png", @@ -47,6 +44,7 @@ class _RecomChooseMaterialPageWidgetState super.initState(); fadeInQuestion(); fadeInOption(); + loadUserTokenAndFetchFridgeContents(); } fadeInQuestion() { @@ -76,10 +74,19 @@ class _RecomChooseMaterialPageWidgetState elevation: 0, backgroundColor: Color(0xFFFF5C39), onPressed: () { + List selectedMaterials = []; + for (int i = 0; i < isSelected.length; i++) { + if (isSelected[i]) { + selectedMaterials.add(FridgeContentsList[i].ingredients); + } + } Navigator.push( context, MaterialPageRoute( - builder: (context) => SelectKeywordPageWidget())); + builder: (context) => SelectKeywordPageWidget( + selectedOption: widget.selectedOption, + selectedMaterials: selectedMaterials + ))); }, label: Container( width: MediaQuery.of(context).size.width * 0.88, @@ -224,7 +231,7 @@ class _RecomChooseMaterialPageWidgetState childAspectRatio: 4, mainAxisSpacing: 8.0, ), - itemCount: 9, // 아이템 개수 + itemCount: FridgeContentsList.length, // 아이템 개수 itemBuilder: (BuildContext context, int index) { return AnimatedOpacity( opacity: opacity2, @@ -246,14 +253,15 @@ class _RecomChooseMaterialPageWidgetState padding: EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row(children: [ Padding( - padding: - EdgeInsets.fromLTRB(0, 0, 10, 0), - child: Image.asset(imageList[index]), + padding: EdgeInsets.fromLTRB(0, 0, 10, 0), + child: FridgeContentsList[index].emoticon.isNotEmpty + ? Image.asset(FridgeContentsList[index].emoticon) + : SizedBox.shrink(), // emoticon이 빈 문자열이면 아무 것도 표시하지 않음 ), RichText( textAlign: TextAlign.left, text: TextSpan( - text: materialList[index], + text: FridgeContentsList[index].ingredients, style: isSelected[index] ? TextStyle( color: Color(0xFFFF5C39), @@ -280,7 +288,7 @@ class _RecomChooseMaterialPageWidgetState shape: RoundedRectangleBorder( side: BorderSide( width: 2, - color: Color(0xFFFF5C39)), + color: Color.fromARGB(255, 141, 74, 59)), borderRadius: BorderRadius.circular(12), ), @@ -304,4 +312,61 @@ class _RecomChooseMaterialPageWidgetState ), ); } + + Future loadUserTokenAndFetchFridgeContents() async { + final prefs = await SharedPreferences.getInstance(); + final String userToken = prefs.getString('accessToken') ?? ''; // accessToken 키로 저장된 토큰을 불러옵니다. 토큰이 없으면 빈 문자열을 반환합니다. + + print('Loaded user token: $userToken'); + + if (userToken.isNotEmpty) { + fetchFridgeContents(userToken).then((contents) { + if (mounted) { // Flutter 위젯의 상태가 여전히 활성 상태인지 확인 + setState(() { + // materialList의 타입이 List로 변경되었다고 가정 + FridgeContentsList = contents; // FridgeContent 객체의 리스트를 UI에 반영 + }); + } + }).catchError((error) { + print('Error fetching fridge contents: $error'); + }); + } else { + print("No user token found."); + // 토큰이 없을 경우의 처리 로직을 여기에 추가할 수 있습니다. + } +} + + Future> fetchFridgeContents(String token) async { + final baseUrl = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080'; + final url = Uri.parse('$baseUrl/fridge/customer'); + + try { + final response = await http.get( + url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', // 여기서 인증 토큰을 전달 + }, + ); + + if (response.statusCode == 200) { + final String responseBody = utf8.decode(response.bodyBytes); + final Map decodedResponse = jsonDecode(responseBody); + + // 'data' 키에 해당하는 값을 List으로 추출합니다. + final List fridgeContents = decodedResponse['data']; + + // FridgeContent 객체의 리스트로 변환합니다. + List contents = fridgeContents.map((item) => FridgeContent.fromJson(item)).toList(); + + return contents; + } else { + throw Exception('Failed to load fridge contents'); + } + } catch (e) { + throw Exception('Failed to load fridge contents: $e'); + } +} + + } diff --git a/babymeal/lib/pages/recommend/RecomChooseMealPage.dart b/babymeal/lib/pages/recommend/RecomChooseMealPage.dart index 7d3bbc0..6fdae4a 100644 --- a/babymeal/lib/pages/recommend/RecomChooseMealPage.dart +++ b/babymeal/lib/pages/recommend/RecomChooseMealPage.dart @@ -65,11 +65,13 @@ class _RecomChooseMealPageWidgetState extends State { onPressed: (isSelected[0] == true && !isSelected[1] == true) || (!isSelected[0] == true && isSelected[1] == true) ? () { + int selectedOptionIndex = isSelected.indexOf(true); + String selectedValue = mealordessert[selectedOptionIndex]; Navigator.push( context, MaterialPageRoute( builder: (context) => - RecomChooseMaterialPageWidget())); + RecomChooseMaterialPageWidget(selectedOption: selectedValue))); } : () {}, label: Container( diff --git a/babymeal/lib/pages/recommend/SelectKeywordPage.dart b/babymeal/lib/pages/recommend/SelectKeywordPage.dart index 6ab6ab1..b046b59 100644 --- a/babymeal/lib/pages/recommend/SelectKeywordPage.dart +++ b/babymeal/lib/pages/recommend/SelectKeywordPage.dart @@ -3,7 +3,10 @@ import 'package:babymeal/pages/recommend/WaitRecipesPage.dart'; import 'package:flutter/material.dart'; class SelectKeywordPageWidget extends StatefulWidget { - const SelectKeywordPageWidget({Key? key}) : super(key: key); + final String selectedOption; + final List selectedMaterials; + + const SelectKeywordPageWidget({Key? key, required this.selectedOption, required this.selectedMaterials}) : super(key: key); @override _SelectKeywordPageWidgetState createState() => @@ -13,8 +16,8 @@ class SelectKeywordPageWidget extends StatefulWidget { class _SelectKeywordPageWidgetState extends State { final scaffoldKey = GlobalKey(); - List isSelected = List.generate(nutKeywords.length, (index) => false); - + List isSelected = List.generate(nutKeywords.length + genKeywords.length, (index) => false); + double opacity1 = 0.0; double opacity2 = 0.0; @@ -57,10 +60,27 @@ class _SelectKeywordPageWidgetState extends State { elevation: 0, backgroundColor: Color(0xFFFF5C39), onPressed: () { + List selectedKeywords = []; + for (int i = 0; i < isSelected.length; i++) { + if (isSelected[i]) { + selectedKeywords.add(nutKeywords[i]); + } + } + + for (int i = 0; i < genKeywords.length; i++) { + if (isSelected[i + nutKeywords.length]) { + selectedKeywords.add(genKeywords[i]); + } + } + Navigator.push( context, MaterialPageRoute( - builder: (context) => WaitRecipesPageWidget())); + builder: (context) => WaitRecipesPageWidget( + selectedOption: widget.selectedOption, + selectedMaterials: widget.selectedMaterials, + selectedKeywords: selectedKeywords, + ))); }, label: Container( width: MediaQuery.of(context).size.width * 0.88, @@ -358,16 +378,18 @@ class _SelectKeywordPageWidgetState extends State { Container( margin: EdgeInsets.fromLTRB( 0, 11, 20, 11), + child: FittedBox( + fit: BoxFit.scaleDown, // 텍스트가 컨테이너에 맞도록 크기 조정 child: Text( '${genKeywords[index]}', style: TextStyle( color: Color(0xFF212121), - fontSize: 15, + fontSize: 15, // 원하는 최대 글자 크기 설정 fontFamily: 'Pretendard', fontWeight: FontWeight.w600, - height: 0, ), ), + ), ) ], ), diff --git a/babymeal/lib/pages/recommend/ShowDetailFridgeRecipePage.dart b/babymeal/lib/pages/recommend/ShowDetailFridgeRecipePage.dart new file mode 100644 index 0000000..cddc053 --- /dev/null +++ b/babymeal/lib/pages/recommend/ShowDetailFridgeRecipePage.dart @@ -0,0 +1,459 @@ +import 'package:babymeal/model/FridgeRecipe.dart'; +import 'package:babymeal/model/RecipeDetailModel.dart'; +import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + +class ShowDetailFridgeRecipePageWidget extends StatefulWidget { + final List selectedMaterials; + final List selectedKeywords; + final int? simpleDietId; + final List fridgeRecipes; + final Function(int, bool) onHeartChanged; + + const ShowDetailFridgeRecipePageWidget({Key? key, required this.selectedMaterials, required this.selectedKeywords, this.simpleDietId, required this.fridgeRecipes, required this.onHeartChanged}) : super(key: key); + + @override + State createState() => _ShowDetailRecipePageWidgetState(); +} + +class _ShowDetailRecipePageWidgetState extends State { + bool isScrabed = false; + bool isLoading = true; // 로딩 상태 관리 + RecipeDetail? recipeDetails; + + @override + void initState() { + super.initState(); + loadTokenAndFetchDetails(); + } + + void loadTokenAndFetchDetails() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + String fridge = widget.selectedMaterials.join(','); // selectedMaterials를 콤마로 구분된 문자열로 변환 + String keyword = widget.selectedKeywords.join(','); + if (token != null) { + fetchRecipeDetails(token, fridge, keyword); + } else { + print('No token found'); + // 토큰이 없는 경우의 처리를 여기에 추가하세요. + } +} + + Future fetchRecipeDetails(String token, String fridge, String keyword) async { + print(widget.simpleDietId); + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/detail?simpleDietId=${widget.simpleDietId}'); + // print(url); + var body = jsonEncode({ + 'fridge': fridge, + 'keyword': keyword, + }); + + try { + var response = await http.post( + url, + body: body, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + setState(() { + final responseBody = json.decode(utf8.decode(response.bodyBytes)); + recipeDetails = RecipeDetail.fromJson(responseBody['data']); + isLoading = false; + }); + } else { + // 요청 실패 처리 + print('Failed to load recipe details'); + } + } catch (e) { + print('Error fetching recipe details: $e'); + } + } + + void toggleScrab() async { + final String simpleDietId = widget.simpleDietId?.toString() ?? "기본값";; + print(simpleDietId); + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + + setState(() { + recipeDetails?.heart = heart; // 서버 응답에 따라 isScrabed 상태 업데이트 + print("isScrabed updated to: ${recipeDetails?.heart}"); + }); + + if(widget.simpleDietId != null) { // simpleDietId가 null이 아닌 경우에만 콜백 호출 + widget.onHeartChanged(widget.simpleDietId!, heart); + } + + } else { + print('Failed to change scrab status'); + } + } catch (e) { + print('Exception occurred: $e'); + } +} + + @override + Widget build(BuildContext context) { + + return Scaffold( + backgroundColor: Color(0xFFF4F3F0), + appBar: AppBar( + backgroundColor: Color(0xFFF4F3F0), + leading: IconButton( + onPressed: () { + setState(() { + // 이전 페이지로 돌아가는 동안 변경된 Heart 정보를 반영 + widget.fridgeRecipes.forEach((recipe) { + // 변경된 Heart 정보가 반영된 경우에만 각 레시피의 정보를 업데이트합니다. + if (recipe.heart != null) { + // print(recipe.simpleDietId); + int index = widget.fridgeRecipes.indexWhere((element) => element.simpleDietId == recipe.simpleDietId); + // print(index); + // print('////'); + if (index != -1 && widget.fridgeRecipes[index].simpleDietId == widget.simpleDietId) { + // print('*****'); + // print(widget.simpleDietId); + // print('^^^^^^^'); + print(widget.fridgeRecipes[index].heart); + print(recipeDetails?.heart); + widget.fridgeRecipes[index].heart = recipeDetails?.heart; + print(widget.fridgeRecipes[index].heart); + } + } + }); + }); + // setState(() {}); + Navigator.pop(context); + }, + color: Colors.transparent, + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + icon: Icon( + Icons.arrow_back_ios, + color: Color(0xFF949494), + size: 24, + ), + ), + ), + body: isLoading + ? Center(child:CircularProgressIndicator()) + : SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 15, 20, 10), + child:Text( + recipeDetails?.dietName ?? '기본 레시피 이름', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + fontFamily: 'GowunBatang', + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 15, 20, 10), + child: Text( + recipeDetails?.description ?? '기본 레시피 설명', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + fontFamily: 'Pretendard', + ), + ), + ), + Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 15, 1, 15), + child: Container( + decoration: BoxDecoration( + color: Color(0xFFDEFCE9), + borderRadius: BorderRadius.circular(18), + ), + padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + child: Text( + recipeDetails?.difficulty ?? '기본 레시피 난이도', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Pretendard', + color: Color(0xFF29CC5A), + ), + ), + ),), + SizedBox(width: 5), + Padding( + padding: EdgeInsets.fromLTRB(1, 15, 1, 15), + child: Container( + padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: Color(0xFFFFFFFF), + ), + child: Text( + "${recipeDetails?.time?.toString() ?? '기본 레시피 시간'}분", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Pretendard', + color: Color(0xFF616161), + ), + ), + ), + ), + SizedBox(width: 5), + Padding( + padding: EdgeInsets.fromLTRB(1, 15, 5, 15), + child: Container( + padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: Color(0xFFFFFFFF), + ), + child: Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(2, 2, 2, 2), + child: Icon( + Icons.tag, + color: Color(0xFF9E9E9E), + size: 16, + ), + ), + Text( + widget?.selectedKeywords.toString() ?? ' ', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Pretendard', + color: Color(0xFF616161), + ), + ), + ], + ), + ), + ), + ], + ), + Stack( + children: [ + // Container( + // color: Color(0xFFF4F3F0), + // child: Row( + // children: [ + // Padding( + // padding: EdgeInsets.fromLTRB(20, 15, 1, 15), + // child: Container( + // decoration: BoxDecoration( + // color: Color(0xFFDEFCE9), + // borderRadius: BorderRadius.circular(18), + // ), + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // child: Text( + // '간단해요', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF29CC5A), + // ), + // ), + // ), + // ), + // Padding( + // padding: EdgeInsets.fromLTRB(1, 15, 1, 15), + // child: Container( + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(18), + // color: Color(0xFFFFFFFF), + // ), + // child: Text( + // '20분 소요', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF616161), + // ), + // ), + // ), + // ), + // SizedBox(width: 5), + // Padding( + // padding: EdgeInsets.fromLTRB(1, 15, 5, 15), + // child: Container( + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(18), + // color: Color(0xFFFFFFFF), + // ), + // child: Row( + // children: [ + // Padding( + // padding: EdgeInsets.fromLTRB(2, 2, 2, 2), + // child: Icon( + // Icons.tag, + // color: Color(0xFF9E9E9E), + // size: 16, + // ), + // ), + // Text( + // '비타민이 풍부한', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF616161), + // ), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + Container( + color: Colors.white, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 20, 20, 0), + child: Text( + '재료', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + fontFamily: 'Pretendard', + color: Color(0xFF424242), + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 20, 20, 20), + child: Text( + recipeDetails?.ingredients ?? '기본 레시피 재료', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + fontFamily: 'Pretendard', + color: Color(0xFF212121), + ), + ), + ), + ], + ), + ), + Positioned( + top: -18, + left: 325, + //TO DO + //Icon Button 완전히 보이기 + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 8, + spreadRadius: 1, + ), + ], + ), + width: 45, + height: 45, + child: IconButton( + icon: Icon( + Icons.favorite, + color: recipeDetails != null && recipeDetails!.heart == true ? Color(0xFFFF5C39) : Color(0xFFDDDDDD), + ), + onPressed: () { + setState(() { + toggleScrab(); + }); + }, + ), + ), + ), + ], + ), + Container( + color: Color(0xFFF4F3F0), + height: 10, + width: double.infinity, + ), + Container( + color: Colors.white, + child: Padding( + padding: EdgeInsets.fromLTRB(20, 20, 0, 10), + child:Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '요리 순서', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + fontFamily: 'Pretendard', + color: Color(0xFF424242), + ), + ), + if (recipeDetails?.recipe != null) + ...recipeDetails!.recipe!.split('\n').map((step) => Padding( + padding: EdgeInsets.only(top: 8.0), + child: Text( + step, + style: TextStyle( + fontSize: 16, + fontFamily: 'Pretendard', + ), + ), + )).toList(), + ], + ) + ), + ), + ] + ), + )); + } +} \ No newline at end of file diff --git a/babymeal/lib/pages/recommend/ShowDetailMainFridgeRecipe.dart b/babymeal/lib/pages/recommend/ShowDetailMainFridgeRecipe.dart new file mode 100644 index 0000000..0fc1e86 --- /dev/null +++ b/babymeal/lib/pages/recommend/ShowDetailMainFridgeRecipe.dart @@ -0,0 +1,442 @@ +import 'package:babymeal/model/FridgeRecipe.dart'; +import 'package:babymeal/model/RecipeDetailModel.dart'; +import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + +class ShowDetailMainFridgeRecipePageWidget extends StatefulWidget { + final int? simpleDietId; + final List fridgeRecipes; + final Function(int, bool) onHeartChanged; + + const ShowDetailMainFridgeRecipePageWidget({Key? key, this.simpleDietId, required this.fridgeRecipes, required this.onHeartChanged}) : super(key: key); + + @override + State createState() => _ShowDetailRecipePageWidgetState(); +} + +class _ShowDetailRecipePageWidgetState extends State { + bool isScrabed = false; + bool isLoading = true; // 로딩 상태 관리 + RecipeDetail? recipeDetails; + + @override + void initState() { + super.initState(); + loadTokenAndFetchDetails(); + } + + void loadTokenAndFetchDetails() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token != null) { + fetchRecipeDetails(token); + } else { + print('No token found'); + // 토큰이 없는 경우의 처리를 여기에 추가하세요. + } +} + + Future fetchRecipeDetails(String token) async { + print(widget.simpleDietId); + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/fridge/detail?simpleDietId=${widget.simpleDietId}'); + // print(url); + + try { + var response = await http.post( + url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + setState(() { + final responseBody = json.decode(utf8.decode(response.bodyBytes)); + recipeDetails = RecipeDetail.fromJson(responseBody['data']); + isLoading = false; + }); + } else { + // 요청 실패 처리 + print('Failed to load recipe details'); + } + } catch (e) { + print('Error fetching recipe details: $e'); + } + } + + void toggleScrab() async { + final String simpleDietId = widget.simpleDietId?.toString() ?? "기본값";; + print(simpleDietId); + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + + setState(() { + recipeDetails?.heart = heart; // 서버 응답에 따라 isScrabed 상태 업데이트 + print("isScrabed updated to: ${recipeDetails?.heart}"); + }); + + if(widget.simpleDietId != null) { // simpleDietId가 null이 아닌 경우에만 콜백 호출 + widget.onHeartChanged(widget.simpleDietId!, heart); + } + + } else { + print('Failed to change scrab status'); + } + } catch (e) { + print('Exception occurred: $e'); + } +} + + @override + Widget build(BuildContext context) { + + return Scaffold( + backgroundColor: Color(0xFFF4F3F0), + appBar: AppBar( + backgroundColor: Color(0xFFF4F3F0), + leading: IconButton( + onPressed: () { + setState(() { + // 이전 페이지로 돌아가는 동안 변경된 Heart 정보를 반영 + widget.fridgeRecipes.forEach((recipe) { + // 변경된 Heart 정보가 반영된 경우에만 각 레시피의 정보를 업데이트합니다. + if (recipe.heart != null) { + // print(recipe.simpleDietId); + int index = widget.fridgeRecipes.indexWhere((element) => element.simpleDietId == recipe.simpleDietId); + // print(index); + // print('////'); + if (index != -1 && widget.fridgeRecipes[index].simpleDietId == widget.simpleDietId) { + // print('*****'); + // print(widget.simpleDietId); + // print('^^^^^^^'); + print(widget.fridgeRecipes[index].heart); + print(recipeDetails?.heart); + widget.fridgeRecipes[index].heart = recipeDetails?.heart; + print(widget.fridgeRecipes[index].heart); + } + } + }); + }); + // setState(() {}); + Navigator.pop(context); + }, + color: Colors.transparent, + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + icon: Icon( + Icons.arrow_back_ios, + color: Color(0xFF949494), + size: 24, + ), + ), + ), + body: isLoading + ? Center(child:CircularProgressIndicator()) + : SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 15, 20, 10), + child:Text( + recipeDetails?.dietName ?? '기본 레시피 이름', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + fontFamily: 'GowunBatang', + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 15, 20, 10), + child: Text( + recipeDetails?.description ?? '기본 레시피 설명', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + fontFamily: 'Pretendard', + ), + ), + ), + Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 15, 1, 15), + child: Container( + decoration: BoxDecoration( + color: Color(0xFFDEFCE9), + borderRadius: BorderRadius.circular(18), + ), + padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + child: Text( + recipeDetails?.difficulty ?? '기본 레시피 난이도', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Pretendard', + color: Color(0xFF29CC5A), + ), + ), + ),), + SizedBox(width: 5), + Padding( + padding: EdgeInsets.fromLTRB(1, 15, 1, 15), + child: Container( + padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: Color(0xFFFFFFFF), + ), + child: Text( + "${recipeDetails?.time?.toString() ?? '기본 레시피 시간'}분", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Pretendard', + color: Color(0xFF616161), + ), + ), + ), + ), + SizedBox(width: 5), + Padding( + padding: EdgeInsets.fromLTRB(1, 15, 5, 15), + child: Container( + padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: Color(0xFFFFFFFF), + ), + child: Row( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(2, 2, 2, 2), + child: Icon( + Icons.tag, + color: Color(0xFF9E9E9E), + size: 16, + ), + ), + + ], + ), + ), + ), + ], + ), + Stack( + children: [ + // Container( + // color: Color(0xFFF4F3F0), + // child: Row( + // children: [ + // Padding( + // padding: EdgeInsets.fromLTRB(20, 15, 1, 15), + // child: Container( + // decoration: BoxDecoration( + // color: Color(0xFFDEFCE9), + // borderRadius: BorderRadius.circular(18), + // ), + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // child: Text( + // '간단해요', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF29CC5A), + // ), + // ), + // ), + // ), + // Padding( + // padding: EdgeInsets.fromLTRB(1, 15, 1, 15), + // child: Container( + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(18), + // color: Color(0xFFFFFFFF), + // ), + // child: Text( + // '20분 소요', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF616161), + // ), + // ), + // ), + // ), + // SizedBox(width: 5), + // Padding( + // padding: EdgeInsets.fromLTRB(1, 15, 5, 15), + // child: Container( + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(18), + // color: Color(0xFFFFFFFF), + // ), + // child: Row( + // children: [ + // Padding( + // padding: EdgeInsets.fromLTRB(2, 2, 2, 2), + // child: Icon( + // Icons.tag, + // color: Color(0xFF9E9E9E), + // size: 16, + // ), + // ), + // Text( + // '비타민이 풍부한', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF616161), + // ), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + Container( + color: Colors.white, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 20, 20, 0), + child: Text( + '재료', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + fontFamily: 'Pretendard', + color: Color(0xFF424242), + ), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 20, 20, 20), + child: Text( + recipeDetails?.ingredients ?? '기본 레시피 재료', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + fontFamily: 'Pretendard', + color: Color(0xFF212121), + ), + ), + ), + ], + ), + ), + Positioned( + top: -18, + left: 325, + //TO DO + //Icon Button 완전히 보이기 + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 8, + spreadRadius: 1, + ), + ], + ), + width: 45, + height: 45, + child: IconButton( + icon: Icon( + Icons.favorite, + color: recipeDetails != null && recipeDetails!.heart == true ? Color(0xFFFF5C39) : Color(0xFFDDDDDD), + ), + onPressed: () { + setState(() { + toggleScrab(); + }); + }, + ), + ), + ), + ], + ), + Container( + color: Color(0xFFF4F3F0), + height: 10, + width: double.infinity, + ), + Container( + color: Colors.white, + child: Padding( + padding: EdgeInsets.fromLTRB(20, 20, 0, 10), + child:Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '요리 순서', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + fontFamily: 'Pretendard', + color: Color(0xFF424242), + ), + ), + if (recipeDetails?.recipe != null) + ...recipeDetails!.recipe!.split('\n').map((step) => Padding( + padding: EdgeInsets.only(top: 8.0), + child: Text( + step, + style: TextStyle( + fontSize: 16, + fontFamily: 'Pretendard', + ), + ), + )).toList(), + ], + ) + ), + ), + ] + ), + )); + } +} + diff --git a/babymeal/lib/pages/recommend/ShowDetailRecipePage.dart b/babymeal/lib/pages/recommend/ShowDetailRecipePage.dart index c185f78..3101903 100644 --- a/babymeal/lib/pages/recommend/ShowDetailRecipePage.dart +++ b/babymeal/lib/pages/recommend/ShowDetailRecipePage.dart @@ -1,7 +1,19 @@ +import 'package:babymeal/model/RecipeDetailModel.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; class ShowDetailRecipePageWidget extends StatefulWidget { - const ShowDetailRecipePageWidget({Key? key}) : super(key: key); + final List selectedMaterials; + final List selectedKeywords; + final int? simpleDietId; + final List recipes; + final Function(int, bool) onHeartChanged; + + const ShowDetailRecipePageWidget({Key? key, required this.selectedMaterials, required this.selectedKeywords, this.simpleDietId, required this.recipes, required this.onHeartChanged}) : super(key: key); @override State createState() => _ShowDetailRecipePageWidgetState(); @@ -9,18 +21,101 @@ class ShowDetailRecipePageWidget extends StatefulWidget { class _ShowDetailRecipePageWidgetState extends State { bool isScrabed = false; - List> cookorder = [ - {1 : '볼에 계란 2개를 깨뜨린 후 소금과 후추로 간을 해줍니다.\n잘 풀어서 섞습니다.'}, - {2 : '토스터나 팬에 토스트 빵을 약간 바삭하게 구워줍니다.'}, - {3 : '중불에 팬을 올린 후 버터를 녹입니다. 버터가 녹으면 계란물을 부어줍니다. 중불에서 부드럽게 계란을 젓습니다.계란이 반죽 상태가 되면 불을 끄고 팬에서 떼어줍니다.'}, - {4 : '햄이나 베이컨을 팬에 약간 구워 주고, 원하는 야채 (예: 토마토나 양상추)를 준비합니다.'}, - ]; + bool isLoading = true; // 로딩 상태 관리 + RecipeDetail? recipeDetails; @override void initState() { super.initState(); + loadTokenAndFetchDetails(); + } + + void loadTokenAndFetchDetails() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + String fridge = widget.selectedMaterials.join(','); // selectedMaterials를 콤마로 구분된 문자열로 변환 + String keyword = widget.selectedKeywords.join(','); + if (token != null) { + fetchRecipeDetails(token, fridge, keyword); + } else { + print('No token found'); + // 토큰이 없는 경우의 처리를 여기에 추가하세요. + } +} + + Future fetchRecipeDetails(String token, String fridge, String keyword) async { + print(widget.simpleDietId); + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/detail?simpleDietId=${widget.simpleDietId}'); + // print(url); + var body = jsonEncode({ + 'fridge': fridge, + 'keyword': keyword, + }); + + try { + var response = await http.post( + url, + body: body, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + setState(() { + final responseBody = json.decode(utf8.decode(response.bodyBytes)); + recipeDetails = RecipeDetail.fromJson(responseBody['data']); + isLoading = false; + }); + } else { + // 요청 실패 처리 + print('Failed to load recipe details'); + } + } catch (e) { + print('Error fetching recipe details: $e'); + } } + void toggleScrab() async { + final String simpleDietId = widget.simpleDietId?.toString() ?? "기본값";; + print(simpleDietId); + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + + setState(() { + recipeDetails?.heart = heart; // 서버 응답에 따라 isScrabed 상태 업데이트 + print("isScrabed updated to: ${recipeDetails?.heart}"); + }); + + if(widget.simpleDietId != null) { // simpleDietId가 null이 아닌 경우에만 콜백 호출 + widget.onHeartChanged(widget.simpleDietId!, heart); + } + + } else { + print('Failed to change scrab status'); + } + } catch (e) { + print('Exception occurred: $e'); + } +} @override Widget build(BuildContext context) { @@ -30,7 +125,29 @@ class _ShowDetailRecipePageWidgetState extends State appBar: AppBar( backgroundColor: Color(0xFFF4F3F0), leading: IconButton( - onPressed: ()async{ + onPressed: () { + setState(() { + // 이전 페이지로 돌아가는 동안 변경된 Heart 정보를 반영 + widget.recipes.forEach((recipe) { + // 변경된 Heart 정보가 반영된 경우에만 각 레시피의 정보를 업데이트합니다. + if (recipe.heart != null) { + // print(recipe.simpleDietId); + int index = widget.recipes.indexWhere((element) => element.simpleDietId == recipe.simpleDietId); + // print(index); + // print('////'); + if (index != -1 && widget.recipes[index].simpleDietId == widget.simpleDietId) { + // print('*****'); + // print(widget.simpleDietId); + // print('^^^^^^^'); + print(widget.recipes[index].heart); + print(recipeDetails?.heart); + widget.recipes[index].heart = recipeDetails?.heart; + print(widget.recipes[index].heart); + } + } + }); + }); + // setState(() {}); Navigator.pop(context); }, color: Colors.transparent, @@ -42,7 +159,9 @@ class _ShowDetailRecipePageWidgetState extends State ), ), ), - body: SingleChildScrollView( + body: isLoading + ? Center(child:CircularProgressIndicator()) + : SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -50,7 +169,7 @@ class _ShowDetailRecipePageWidgetState extends State Padding( padding: EdgeInsets.fromLTRB(20, 15, 20, 10), child:Text( - '통새우 아보카도 샌드위치', + recipeDetails?.dietName ?? '기본 레시피 이름', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -61,7 +180,7 @@ class _ShowDetailRecipePageWidgetState extends State Padding( padding: EdgeInsets.fromLTRB(20, 15, 20, 10), child: Text( - '계란을 스크램블해서 만든 부드러운 계란 스크램블을 식빵 사이에 넣어 샌드위치를 만듭니다.', + recipeDetails?.description ?? '기본 레시피 설명', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, @@ -80,7 +199,7 @@ class _ShowDetailRecipePageWidgetState extends State ), padding: EdgeInsets.fromLTRB(10, 3, 10, 3), child: Text( - '간단해요', + recipeDetails?.difficulty ?? '기본 레시피 난이도', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -100,7 +219,7 @@ class _ShowDetailRecipePageWidgetState extends State color: Color(0xFFFFFFFF), ), child: Text( - '20분 소요', + "${recipeDetails?.time?.toString() ?? '기본 레시피 시간'}분", textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -131,7 +250,7 @@ class _ShowDetailRecipePageWidgetState extends State ), ), Text( - '비타민이 풍부한', + widget?.selectedKeywords.toString() ?? ' ', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -148,86 +267,86 @@ class _ShowDetailRecipePageWidgetState extends State ), Stack( children: [ - Container( - color: Color(0xFFF4F3F0), - child: Row( - children: [ - Padding( - padding: EdgeInsets.fromLTRB(20, 15, 1, 15), - child: Container( - decoration: BoxDecoration( - color: Color(0xFFDEFCE9), - borderRadius: BorderRadius.circular(18), - ), - padding: EdgeInsets.fromLTRB(10, 3, 10, 3), - child: Text( - '간단해요', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - fontFamily: 'Pretendard', - color: Color(0xFF29CC5A), - ), - ), - ), - ), - Padding( - padding: EdgeInsets.fromLTRB(1, 15, 1, 15), - child: Container( - padding: EdgeInsets.fromLTRB(10, 3, 10, 3), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(18), - color: Color(0xFFFFFFFF), - ), - child: Text( - '20분 소요', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - fontFamily: 'Pretendard', - color: Color(0xFF616161), - ), - ), - ), - ), - SizedBox(width: 5), - Padding( - padding: EdgeInsets.fromLTRB(1, 15, 5, 15), - child: Container( - padding: EdgeInsets.fromLTRB(10, 3, 10, 3), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(18), - color: Color(0xFFFFFFFF), - ), - child: Row( - children: [ - Padding( - padding: EdgeInsets.fromLTRB(2, 2, 2, 2), - child: Icon( - Icons.tag, - color: Color(0xFF9E9E9E), - size: 16, - ), - ), - Text( - '비타민이 풍부한', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - fontFamily: 'Pretendard', - color: Color(0xFF616161), - ), - ), - ], - ), - ), - ), - ], - ), - ), + // Container( + // color: Color(0xFFF4F3F0), + // child: Row( + // children: [ + // Padding( + // padding: EdgeInsets.fromLTRB(20, 15, 1, 15), + // child: Container( + // decoration: BoxDecoration( + // color: Color(0xFFDEFCE9), + // borderRadius: BorderRadius.circular(18), + // ), + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // child: Text( + // '간단해요', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF29CC5A), + // ), + // ), + // ), + // ), + // Padding( + // padding: EdgeInsets.fromLTRB(1, 15, 1, 15), + // child: Container( + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(18), + // color: Color(0xFFFFFFFF), + // ), + // child: Text( + // '20분 소요', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF616161), + // ), + // ), + // ), + // ), + // SizedBox(width: 5), + // Padding( + // padding: EdgeInsets.fromLTRB(1, 15, 5, 15), + // child: Container( + // padding: EdgeInsets.fromLTRB(10, 3, 10, 3), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(18), + // color: Color(0xFFFFFFFF), + // ), + // child: Row( + // children: [ + // Padding( + // padding: EdgeInsets.fromLTRB(2, 2, 2, 2), + // child: Icon( + // Icons.tag, + // color: Color(0xFF9E9E9E), + // size: 16, + // ), + // ), + // Text( + // '비타민이 풍부한', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // fontFamily: 'Pretendard', + // color: Color(0xFF616161), + // ), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // ), Container( color: Colors.white, child: Column( @@ -249,7 +368,7 @@ class _ShowDetailRecipePageWidgetState extends State Padding( padding: EdgeInsets.fromLTRB(20, 20, 20, 20), child: Text( - '계란 2개, 소금 약간, 후추 약간, 설탕 1 큰 술, 버터 1 큰 술, 토스트 빵 2조각', + recipeDetails?.ingredients ?? '기본 레시피 재료', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, @@ -283,12 +402,12 @@ class _ShowDetailRecipePageWidgetState extends State child: IconButton( icon: Icon( Icons.favorite, - color: isScrabed ? Color(0xFFFF5C39) : Color(0xFFDDDDDD), + color: recipeDetails != null && recipeDetails!.heart == true ? Color(0xFFFF5C39) : Color(0xFFDDDDDD), ), onPressed: () { setState(() { - isScrabed = !isScrabed; - }); + toggleScrab(); + }); }, ), ), @@ -317,11 +436,17 @@ class _ShowDetailRecipePageWidgetState extends State color: Color(0xFF424242), ), ), - for (Map order in cookorder) - showOrderWidget( - order: order.keys.first, - cookorder: order.values.first, - ), + if (recipeDetails?.recipe != null) + ...recipeDetails!.recipe!.split('\n').map((step) => Padding( + padding: EdgeInsets.only(top: 8.0), + child: Text( + step, + style: TextStyle( + fontSize: 16, + fontFamily: 'Pretendard', + ), + ), + )).toList(), ], ) ), @@ -331,53 +456,4 @@ class _ShowDetailRecipePageWidgetState extends State )); } } -class showOrderWidget extends StatelessWidget { - - final int order; - final String cookorder; - - showOrderWidget({ - required this.order, - required this.cookorder, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - child: Row( - children: [ - Container( - color: Color(0xFFFFFFFF), - child: Text( - '$order', - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - fontFamily: 'GowunBatang', - color: Color(0xFF616161), - ), - ), - ), - SizedBox(width: 10), - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(5, 0, 20, 0), - child: Text( - '$cookorder', - style: TextStyle( - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ], - ), - ); - } -} - diff --git a/babymeal/lib/pages/recommend/ShowRecipesPage.dart b/babymeal/lib/pages/recommend/ShowRecipesPage.dart index 20fa78b..7f164e3 100644 --- a/babymeal/lib/pages/recommend/ShowRecipesPage.dart +++ b/babymeal/lib/pages/recommend/ShowRecipesPage.dart @@ -1,9 +1,24 @@ import 'package:babymeal/NavigationPage.dart'; +import 'package:babymeal/model/FridgeRecipe.dart'; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:babymeal/pages/recommend/ShowDetailFridgeRecipePage.dart'; import 'package:babymeal/pages/recommend/ShowDetailRecipePage.dart'; +import 'package:babymeal/pages/recommend/WaitRecipesPage.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + class ShowRecipesPageWidget extends StatefulWidget { - const ShowRecipesPageWidget({Key? key}) : super(key: key); + final String selectedOption; + final List selectedMaterials; + final List selectedKeywords; + final List recipes; + final List fridgeRecipes; + const ShowRecipesPageWidget({Key? key, required this.selectedOption, required this.selectedMaterials, required this.selectedKeywords, required this.recipes, required this.fridgeRecipes}) : super(key: key); @override _ShowRecipesPageWidgetState createState() => _ShowRecipesPageWidgetState(); @@ -11,18 +26,100 @@ class ShowRecipesPageWidget extends StatefulWidget { class _ShowRecipesPageWidgetState extends State { final scaffoldKey = GlobalKey(); + Future>? refrigeratorRecipesFuture; - List likeStates = [false, false, false]; + void changeRecipeLike(int index) async { + final String simpleDietId = widget.recipes[index].simpleDietId.toString(); - void changeLike(int index) { - setState(() { - likeStates[index] = !likeStates[index]; + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + setState(() { + widget.recipes[index].heart = heart; // 서버 응답에 따라 상태 업데이트 + print("[$index] updated to: ${widget.recipes[index].heart}"); + + }); + } else { + print('Failed to change like status'); + } + } catch (e) { + print('Exception occurred: $e'); + } +} + +void changeFridgeRecipeLike(int index) async { + final String simpleDietId = widget.fridgeRecipes[index].simpleDietId.toString(); + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + setState(() { + widget.fridgeRecipes[index].heart = heart; // 서버 응답에 따라 상태 업데이트 + print("[$index] updated to: ${widget.fridgeRecipes[index].heart}"); + + }); + } else { + print('Failed to change like status'); + } + } catch (e) { + print('Exception occurred: $e'); } +} + +void onHeartChangedRecipe(int? simpleDietId, bool newHeartValue) { + setState(() { + final recipeIndex = widget.recipes.indexWhere((recipe) => recipe.simpleDietId == simpleDietId); + if (recipeIndex != -1) { + widget.recipes[recipeIndex].heart = newHeartValue; + } + }); +} + +void onHeartChangedFridgeRecipe(int? simpleDietId, bool newHeartValue) { + setState(() { + final recipeIndex = widget.fridgeRecipes.indexWhere((recipe) => recipe.simpleDietId == simpleDietId); + if (recipeIndex != -1) { + widget.fridgeRecipes[recipeIndex].heart = newHeartValue; + } + }); +} + @override void initState() { super.initState(); + // loadUserTokenAndFetchRecipes(); } @override @@ -30,6 +127,19 @@ class _ShowRecipesPageWidgetState extends State { super.dispose(); } + // void loadUserTokenAndFetchRecipes() async { + // final SharedPreferences prefs = await SharedPreferences.getInstance(); + // final String? userToken = prefs.getString('accessToken'); + // if (userToken != null) { + // // babyId 처리 + + // //refrigeratorRecipesFuture = fetchRefrigeratorRecipes(userToken, 6); + // } else { + // // 처리: 토큰이 없을 경우, 예를 들어 로그인 화면으로 이동 + // print('User token not found, please login.'); + // } + // } + @override Widget build(BuildContext context) { return Scaffold( @@ -38,7 +148,7 @@ class _ShowRecipesPageWidgetState extends State { children: [ Container( alignment: Alignment.centerLeft, - margin: EdgeInsets.only(left: 20, top: 62), + margin: EdgeInsets.only(left: 20, top: 40), child: Text( 'AI 유아식 추천', style: TextStyle( @@ -51,9 +161,22 @@ class _ShowRecipesPageWidgetState extends State { ), )), Container( - margin: EdgeInsets.fromLTRB(0, 21, 20, 11), + margin: EdgeInsets.fromLTRB(0, 0, 20, 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, + children:[ + GestureDetector( + onTap: () { + // Navigator를 사용하여 WaitRecipesPageWidget으로 이동 + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => WaitRecipesPageWidget( + selectedOption: widget.selectedOption, + selectedMaterials: widget.selectedMaterials, + selectedKeywords: widget.selectedKeywords, + )), + ); + },child: Row( children: [ Image.asset("assets/images/autorenew.png"), Text( @@ -68,61 +191,45 @@ class _ShowRecipesPageWidgetState extends State { ), ) ], - )), - Container( - margin: EdgeInsets.only(bottom: 14), - child: Column( - children: [ - Row(children: [ - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - ShowDetailRecipePageWidget())); - }, - child: AIRecipe(), + )),],),), + ListView.builder( + shrinkWrap: true, // Column 내부에 ListView를 넣을 때 필요 + physics: NeverScrollableScrollPhysics(), // Column 스크롤과의 충돌 방지 + itemCount: widget.recipes.length, // 레시피 개수 + itemBuilder: (context, index) { + return Container( + margin: EdgeInsets.only(bottom: 7), // 여기에 마진 추가 + child: Row( + children: [ + Expanded( + child: AIRecipe( + recipe: widget.recipes[index], + selectedMaterials: widget.selectedMaterials, // 이렇게 넘겨줌 + selectedKeywords: widget.selectedKeywords, + recipes: widget.recipes, + onHeartChanged: onHeartChangedRecipe, + ), ), GestureDetector( - onTap: () { - changeLike(0); - }, - child: ImageIcon( - AssetImage(likeStates[0] - ? "assets/images/like_sel.png" - : "assets/images/like.png"), - color: Color(0xFFCE4040), - )) - ]), - Row(children: [ - AIRecipe(), - GestureDetector( - onTap: () { - changeLike(0); + onTap: () { + setState(() { + changeRecipeLike(index); + }); }, + child: Padding( + padding: EdgeInsets.only(left: 5, right: 15), child: ImageIcon( - AssetImage(likeStates[0] - ? "assets/images/like_sel.png" - : "assets/images/like.png"), - color: Color(0xFFCE4040), - )) - ]), - Row(children: [ - AIRecipe(), - GestureDetector( - onTap: () { - changeLike(0); - }, - child: ImageIcon( - AssetImage(likeStates[0] - ? "assets/images/like_sel.png" - : "assets/images/like.png"), - color: Color(0xFFCE4040), - )) - ]), - ], - )), + AssetImage("assets/images/like.png"), + color: widget.recipes[index].heart ?? false ? Colors.red : Colors.grey, + ), + ), + ) + ], + ), + ); + }, + ), + ElevatedButton( style: ElevatedButton.styleFrom( minimumSize: Size(160, 55), @@ -150,12 +257,12 @@ class _ShowRecipesPageWidgetState extends State { ), )), Container( - margin: EdgeInsets.only(left: 22, top: 34, bottom: 14), + margin: EdgeInsets.only(left: 22, top: 24, bottom: 0), child: Column( children: [ Container( alignment: Alignment.centerLeft, - margin: EdgeInsets.only(bottom: 14), + margin: EdgeInsets.only(bottom: 0), child: Text( '냉장고 재료 기반, 빠른 추천', style: TextStyle( @@ -167,51 +274,107 @@ class _ShowRecipesPageWidgetState extends State { letterSpacing: -0.36, ), )), - Container( - child: Column( - children: [ - Row(children: [ - RefrigeratorRecipe(), - GestureDetector( - onTap: () { - changeLike(0); - }, - child: ImageIcon( - AssetImage(likeStates[0] - ? "assets/images/like_sel.png" - : "assets/images/like.png"), - color: Color(0xFFCE4040), - )) - ]), - Row(children: [ - RefrigeratorRecipe(), - GestureDetector( + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), // 스크롤이 가능하도록 설정 + itemCount: widget.fridgeRecipes.length, // 아이템 개수는 fridgeRecipes 리스트의 길이 + itemBuilder: (context, index) { + FridgeRecipe fridgeRecipe = widget.fridgeRecipes[index]; // 현재 인덱스의 레시피 + return Container( + margin: EdgeInsets.only(bottom: 14), + child: Row( + children: [ + Expanded( + child: RefrigeratorRecipe( + fridgeRecipe: fridgeRecipe, + selectedMaterials: widget.selectedMaterials, // 이렇게 넘겨줌 + selectedKeywords: widget.selectedKeywords, + fridgeRecipes: widget.fridgeRecipes, + onHeartChanged: onHeartChangedFridgeRecipe,), // 레시피 위젯 + ), + GestureDetector( onTap: () { - changeLike(0); + changeFridgeRecipeLike(index); }, - child: ImageIcon( - AssetImage(likeStates[0] - ? "assets/images/like_sel.png" - : "assets/images/like.png"), - color: Color(0xFFCE4040), - )) - ]), - ], - )) + child: Padding( + padding: EdgeInsets.only(left: 5, right: 15), + child: ImageIcon( + AssetImage("assets/images/like.png"), + color: widget.fridgeRecipes[index].heart ?? false ? Colors.red : Colors.grey, + ), + ), + ), + ], + ), + ); + }, + ), ], )) ], )); } + + } class AIRecipe extends StatelessWidget { - AIRecipe({super.key}); + final GetRecipe recipe; + final List selectedMaterials; + final List selectedKeywords; + final List recipes; + final Function(int? simpleDietId, bool newHeartStatus) onHeartChanged; + // GetRecipe 객체를 받기 위한 변수 추가 + AIRecipe({Key? key, required this.recipe, required this.selectedMaterials, required this.selectedKeywords, required this.recipes, required this.onHeartChanged,}) : super(key: key); // 생성자 수정 + bool isLiked = false; + + Color getDifficultyTextColor(String? difficulty) { + switch (difficulty) { + case '간단': + return Color(0xFF28CC59); // 초록색 + case '보통': + return Color(0xFFFFA726); // 주황색 + case '복잡': + return Color(0xFFEF5350); // 빨간색 + default: + return Color(0xFF9E9E9E); // 기본 색상 (회색) + } +} + Color getDifficultyBackgroundColor(String? difficulty) { + switch (difficulty) { + case '간단': + return Color(0xFFDEFCE9); // 초록색 + case '보통': + return Color(0xFFFFE8CC); // 주황색 + case '복잡': + return Color(0xFFFFE5DF); // 빨간색 + default: + return Color(0xFF9E9E9E); // 기본 색상 (회색) + } +} + @override Widget build(BuildContext context) { - return Container( + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ShowDetailRecipePageWidget( + selectedMaterials: selectedMaterials, + selectedKeywords: selectedKeywords, + simpleDietId: recipe.simpleDietId, + recipes: recipes, + onHeartChanged: (simpleDietId, newHeartStatus) { + // 상위 위젯의 onHeartChanged 콜백을 호출합니다. + onHeartChanged(simpleDietId, newHeartStatus); + },), + ), + ); + }, + child: Container( margin: EdgeInsets.fromLTRB(20, 0, 12, 10), height: MediaQuery.of(context).size.height * 0.14, width: MediaQuery.of(context).size.width * 0.8, @@ -229,7 +392,8 @@ class AIRecipe extends StatelessWidget { children: [ Container( child: Text( - '스크램블 에그 샌드위치', + recipe.dietName ?? '', + overflow: TextOverflow.ellipsis, style: TextStyle( color: Color(0xFF212121), fontSize: 18, @@ -247,16 +411,16 @@ class AIRecipe extends StatelessWidget { horizontal: 8, vertical: 2), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: Color(0xFFDEFCE9), + color: getDifficultyBackgroundColor(recipe.difficulty), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(17.12), ), ), child: Text( - '간단', + recipe.difficulty ?? '', textAlign: TextAlign.center, style: TextStyle( - color: Color(0xFF28CC59), + color: getDifficultyTextColor(recipe.difficulty), fontSize: 12, fontFamily: 'Pretendard', fontWeight: FontWeight.w600, @@ -278,7 +442,7 @@ class AIRecipe extends StatelessWidget { ), ), child: Text( - '20분', + '${recipe.time?.toString() ?? '0'}분', textAlign: TextAlign.center, style: TextStyle( color: Color(0xFF757575), @@ -295,9 +459,9 @@ class AIRecipe extends StatelessWidget { Container( margin: EdgeInsets.only(left: 21, right: 20), child: Text( + recipe.description ?? '', overflow: TextOverflow.ellipsis, maxLines: 2, - '계란을 스크램블해서 만든 부드러운 계란 스크램블을 식빵 사이에 넣어 샌드위치를 만듭니다.', style: TextStyle( color: Color(0xFF616161), fontSize: 13, @@ -308,100 +472,138 @@ class AIRecipe extends StatelessWidget { ), )) ], - )); + )), + ); + } } class RefrigeratorRecipe extends StatelessWidget { - const RefrigeratorRecipe({super.key}); + final FridgeRecipe fridgeRecipe; + final List selectedMaterials; + final List selectedKeywords; + final List fridgeRecipes; + final Function(int? simpleDietId, bool newHeartStatus) onHeartChanged; + const RefrigeratorRecipe({Key? key, required this.fridgeRecipe, required this.selectedMaterials, required this.selectedKeywords, required this.fridgeRecipes, required this.onHeartChanged}) : super(key: key); + + Color getDifficultyTextColor(String? difficulty) { + switch (difficulty) { + case '간단': + return Color(0xFF28CC59); // 초록색 + case '보통': + return Color(0xFFFFA726); // 주황색 + case '복잡': + return Color(0xFFEF5350); // 빨간색 + default: + return Color(0xFF9E9E9E); // 기본 색상 (회색) + } +} + +Color getDifficultyBackgroundColor(String? difficulty) { + switch (difficulty) { + case '간단': + return Color(0xFFDEFCE9); // 초록색 + case '보통': + return Color(0xFFFFE8CC); // 주황색 + case '복잡': + return Color(0xFFFFE5DF); // 빨간색 + default: + return Color(0xFF9E9E9E); // 기본 색상 (회색) + } +} @override Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.only(right: 12, bottom: 8), - height: MediaQuery.of(context).size.height * 0.06, - width: MediaQuery.of(context).size.width * 0.8, - decoration: ShapeDecoration( - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ShowDetailFridgeRecipePageWidget( + selectedMaterials: selectedMaterials, + selectedKeywords: selectedKeywords, + simpleDietId: fridgeRecipe.simpleDietId, + fridgeRecipes: fridgeRecipes, + onHeartChanged: (simpleDietId, newHeartStatus) { + onHeartChanged(simpleDietId, newHeartStatus); + },), ), - ), - child: Row( - children: [ - Container( - margin: EdgeInsets.only(left: 9, right: 8), - child: Text( - '🍞', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black, - fontSize: 26, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w500, - height: 0, - ), - )), - Container( - margin: EdgeInsets.only(right: 8), - child: Text( - '미니 치즈 피자', - style: TextStyle( - color: Color(0xFF212121), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0, - ), - )), - Container( - margin: EdgeInsets.only(right: 8), - width: 37, - height: 18, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: Color(0x33FF8A00), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(17.12), - ), - ), - child: Text( - '보통', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFFFF8900), - fontSize: 12, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0, - letterSpacing: -0.24, - ), - )), - Container( - width: 42, - height: 18, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: Color(0xFFF4F3F0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(17.12), + ); + }, + child: Container( + margin: EdgeInsets.only(right: 12, bottom: 0), + height: MediaQuery.of(context).size.height * 0.06, + width: MediaQuery.of(context).size.width * 0.8, + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + children: [ + + Container( + margin: EdgeInsets.only(left: 8), + child: Text( + fridgeRecipe.dietName ?? '', + style: TextStyle( + color: Color(0xFF212121), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + ), + )), + Container( + margin: EdgeInsets.only(right: 8), + width: 37, + height: 18, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: getDifficultyBackgroundColor(fridgeRecipe.difficulty), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(17.12), + ), ), - ), - child: Text( - '40분', - textAlign: TextAlign.center, - style: TextStyle( - color: Color(0xFF757575), - fontSize: 12, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w600, - height: 0, - letterSpacing: -0.24, + child: Text( + fridgeRecipe.difficulty ?? '', + textAlign: TextAlign.center, + style: TextStyle( + color: getDifficultyTextColor(fridgeRecipe.difficulty), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.24, + ), + )), + Container( + width: 42, + height: 18, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: Color(0xFFF4F3F0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(17.12), + ), ), - )) - ], - )); + child: Text( + '${fridgeRecipe.time?.toString() ?? '0'}분', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF757575), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.24, + ), + )) + ], + )), + ); } } diff --git a/babymeal/lib/pages/recommend/ViewRecommendPage.dart b/babymeal/lib/pages/recommend/ViewRecommendPage.dart index 90cbe35..1bce33f 100644 --- a/babymeal/lib/pages/recommend/ViewRecommendPage.dart +++ b/babymeal/lib/pages/recommend/ViewRecommendPage.dart @@ -1,7 +1,18 @@ import 'package:babymeal/NavigationPage.dart'; +import 'package:babymeal/model/FridgeRecipe.dart'; import 'package:babymeal/pages/recommend/RecomChooseMealPage.dart'; +import 'package:babymeal/pages/recommend/ShowDetailFridgeRecipePage.dart'; +import 'package:babymeal/pages/recommend/ShowDetailMainFridgeRecipe.dart'; +import 'package:babymeal/services/DietService.dart'; +import 'package:babymeal/services/MyPageService.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + class ViewRecommendPageWidget extends StatefulWidget { const ViewRecommendPageWidget({Key? key}) : super(key: key); @@ -13,10 +24,12 @@ class ViewRecommendPageWidget extends StatefulWidget { class _ViewRecommendPageWidgetState extends State { final scaffoldKey = GlobalKey(); List scrab = [false, false]; + List fridgeRecipes = []; @override void initState() { super.initState(); + _loadRecipes(); } @override @@ -24,6 +37,76 @@ class _ViewRecommendPageWidgetState extends State { super.dispose(); } +Future _loadRecipes() async { + DietService dietService = DietService(); + MyPageService myPageService = MyPageService(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + if (userToken == null) { + print('No user token found'); + return; + } + + try { + String babyId = await myPageService.getBabyId(userToken); + var tempRecipes = await dietService.fetchRefrigeratorRecipes(babyId); // 여기서 메소드 호출 + print("fridgeRecipes: $tempRecipes"); + if (mounted) { + setState(() { + fridgeRecipes = tempRecipes; + }); + } + } catch (e) { + print("Error fetching fridge recipes: $e"); + // 에러 핸들링 로직 추가 가능 + } +} + + +Future onHeartChangedFridgeRecipe(int? simpleDietId, bool newHeartValue) async { + final recipeIndex = fridgeRecipes.indexWhere((recipe) => recipe.simpleDietId == simpleDietId); + + if (recipeIndex != -1) { + setState(() { + fridgeRecipes[recipeIndex].heart = newHeartValue; + }); + } +} + +void changeFridgeRecipeLike(int index) async { + final String simpleDietId = fridgeRecipes[index].simpleDietId.toString(); + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + setState(() { + fridgeRecipes[index].heart = heart; // 서버 응답에 따라 상태 업데이트 + print("[$index] updated to: ${fridgeRecipes[index].heart}"); + + }); + } else { + print('Failed to change like status'); + } + } catch (e) { + print('Exception occurred: $e'); + } +} + @override Widget build(BuildContext context) { return Scaffold( @@ -87,110 +170,187 @@ class _ViewRecommendPageWidgetState extends State { letterSpacing: -0.50, ))), ])), - Padding( - padding: EdgeInsetsDirectional.fromSTEB(30, 130, 0, 0), - child: RichText( - text: TextSpan(children: [ - TextSpan( - text: '냉장고 재료 기반, 빠른 추천', - style: TextStyle( - color: Color(0xFF424242), - fontSize: 18, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w700, - height: 0, - letterSpacing: -0.50, - ), - ) - ]))), - Row(children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB(30, 15, 0, 0), - child: Container( - height: 48.0, - width: 315.0, - child: Row(children: [ - Padding( - padding: EdgeInsets.fromLTRB(15, 0, 0, 0), - child: Image.asset("assets/images/bread.png"), + Container( + margin: EdgeInsets.only(left: 22, top: 24, bottom: 0), + child: Column( + children: [ + Container( + alignment: Alignment.centerLeft, + margin: EdgeInsets.only(top: 100, bottom: 20), + child: Text( + '냉장고 재료 기반, 빠른 추천', + style: TextStyle( + color: Color(0xFF424242), + fontSize: 18, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.36, + ), + )), + ListView.builder( + shrinkWrap: true, + itemCount: fridgeRecipes.length, // fridgeRecipes 리스트의 길이로 아이템 개수 설정 + itemBuilder: (context, index) { + FridgeRecipe fridgeRecipe = fridgeRecipes[index]; // 현재 인덱스의 레시피 객체 + return Container( + margin: EdgeInsets.only(bottom: 14), + child: Row( + children: [ + Expanded( + child: RefrigeratorRecipe( + fridgeRecipe: fridgeRecipe, + fridgeRecipes: fridgeRecipes, + onHeartChanged: onHeartChangedFridgeRecipe, + ), ), - Padding( - padding: EdgeInsets.fromLTRB(10, 0, 0, 0), - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - text: '미니 치즈 피자', - style: TextStyle( - color: Color(0xFF212121), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w700, - height: 0, - letterSpacing: -0.50, - )))), - ]), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10.0))), - ), - Padding( - padding: EdgeInsets.fromLTRB(15, 10, 0, 0), - child: GestureDetector( - onTap: () { - setState(() { - scrab[0] = !scrab[0]; - }); - }, - child: scrab[0] - ? Image.asset("assets/images/scrab_full.png") - : Image.asset("assets/images/scrab_empty.png"), - )), - ]), - Row(children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB(30, 10, 0, 0), - child: Container( - height: 48.0, - width: 315.0, - child: Row(children: [ - Padding( - padding: EdgeInsets.fromLTRB(15, 0, 0, 0), - child: Image.asset("assets/images/onion.png"), + GestureDetector( + onTap: () { + changeFridgeRecipeLike(index); + }, + child: Padding( + padding: EdgeInsets.only(left: 5, right: 15), + child: ImageIcon( + AssetImage("assets/images/like.png"), + color: fridgeRecipe.heart ?? false ? Colors.red : Colors.grey, + ), + ), ), - Padding( - padding: EdgeInsets.fromLTRB(10, 0, 0, 0), - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - text: '어니언 스프', - style: TextStyle( - color: Color(0xFF212121), - fontSize: 15, - fontFamily: 'Pretendard', - fontWeight: FontWeight.w700, - height: 0, - letterSpacing: -0.50, - )))), - ]), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10.0))), - ), - Padding( - padding: EdgeInsets.fromLTRB(15, 10, 0, 0), - child: GestureDetector( - onTap: () { - setState(() { - scrab[1] = !scrab[1]; - }); - }, - child: scrab[1] - ? Image.asset("assets/images/scrab_full.png") - : Image.asset("assets/images/scrab_empty.png"), - )), - ]), + ], + ), + ); + }, + ), + ], + )) + ], + )), + ); + } +} + +class RefrigeratorRecipe extends StatelessWidget { + final FridgeRecipe fridgeRecipe; + final List fridgeRecipes; + final Function(int? simpleDietId, bool newHeartStatus) onHeartChanged; + const RefrigeratorRecipe({Key? key, required this.fridgeRecipe, required this.fridgeRecipes, required this.onHeartChanged}) : super(key: key); + + Color getDifficultyTextColor(String? difficulty) { + switch (difficulty) { + case '간단': + return Color(0xFF28CC59); // 초록색 + case '보통': + return Color(0xFFFFA726); // 주황색 + case '복잡': + return Color(0xFFEF5350); // 빨간색 + default: + return Color(0xFF9E9E9E); // 기본 색상 (회색) + } +} + +Color getDifficultyBackgroundColor(String? difficulty) { + switch (difficulty) { + case '간단': + return Color(0xFFDEFCE9); // 초록색 + case '보통': + return Color(0xFFFFE8CC); // 주황색 + case '복잡': + return Color(0xFFFFE5DF); // 빨간색 + default: + return Color(0xFF9E9E9E); // 기본 색상 (회색) + } +} + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ShowDetailMainFridgeRecipePageWidget( + simpleDietId: fridgeRecipe.simpleDietId, + fridgeRecipes: fridgeRecipes, + onHeartChanged: (simpleDietId, newHeartStatus) { + onHeartChanged(simpleDietId, newHeartStatus); + },), + ), + ); + }, + child: Container( + margin: EdgeInsets.only(right: 12, bottom: 0), + height: MediaQuery.of(context).size.height * 0.06, + width: MediaQuery.of(context).size.width * 0.8, + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + children: [ + Container( + margin: EdgeInsets.only(left: 8), + child: Text( + fridgeRecipe.dietName ?? '', + style: TextStyle( + color: Color(0xFF212121), + fontSize: 15, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + ), + )), + Container( + margin: EdgeInsets.only(right: 8), + width: 37, + height: 18, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: getDifficultyBackgroundColor(fridgeRecipe.difficulty), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(17.12), + ), + ), + child: Text( + fridgeRecipe.difficulty ?? '', + textAlign: TextAlign.center, + style: TextStyle( + color: getDifficultyTextColor(fridgeRecipe.difficulty), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.24, + ), + )), + Container( + width: 42, + height: 18, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: Color(0xFFF4F3F0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(17.12), + ), + ), + child: Text( + '${fridgeRecipe.time?.toString() ?? '0'}분', + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFF757575), + fontSize: 12, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w600, + height: 0, + letterSpacing: -0.24, + ), + )) ], )), ); } } + diff --git a/babymeal/lib/pages/recommend/WaitRecipesPage.dart b/babymeal/lib/pages/recommend/WaitRecipesPage.dart index 51bcd10..0733267 100644 --- a/babymeal/lib/pages/recommend/WaitRecipesPage.dart +++ b/babymeal/lib/pages/recommend/WaitRecipesPage.dart @@ -1,12 +1,24 @@ import 'dart:async'; import 'dart:math'; +import 'package:babymeal/model/FridgeRecipe.dart'; +import 'package:babymeal/services/DietService.dart'; +import 'package:babymeal/services/MyPageService.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:babymeal/etc/tips.dart'; +import 'package:babymeal/model/RecipeModel.dart'; import 'package:babymeal/pages/recommend/ShowRecipesPage.dart'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'dart:convert' show utf8; class WaitRecipesPageWidget extends StatefulWidget { - const WaitRecipesPageWidget({Key? key}) : super(key: key); + final String selectedOption; + final List selectedMaterials; + final List selectedKeywords; + + const WaitRecipesPageWidget({Key? key, required this.selectedOption, required this.selectedMaterials, required this.selectedKeywords}) : super(key: key); @override _WaitRecipesPageWidgetState createState() => _WaitRecipesPageWidgetState(); @@ -41,6 +53,118 @@ class _WaitRecipesPageWidgetState extends State }); } + + Future> fetchRecipes(String token, String babyId, String type, String fridge, String keyword) async { + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/simple?babyId=$babyId'); + + var body = jsonEncode({ + 'type': type, + 'fridge': fridge, + 'keyword': keyword, + }); + + try { + var response = await http.post(url, + body: body, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', // 여기서 인증 토큰을 전달 + }, + ); + var responsecode = response.statusCode; + print("fetchRecipe response.statusCode: $responsecode"); + + if (response.statusCode == 200) { + // UTF-8로 명시적으로 디코딩 + var responseBody = utf8.decode(response.bodyBytes); + var jsonResponse = jsonDecode(responseBody); + + if (jsonResponse['data'] != null) { + // 'data' 키 아래의 리스트를 GetRecipe 객체의 리스트로 변환 + var recipesList = List>.from(jsonResponse['data']) + .map((recipeJson) => GetRecipe.fromJson(recipeJson)) + .toList(); + return recipesList; + + } else { + throw Exception('Data key not found'); + } + } else { + // 오류 처리 + throw Exception('Failed to load recipes'); + } + } catch (e) { + // 네트워크 오류 처리 + throw Exception('Failed to load recipes: $e'); + } +} + Future> fetchRefrigeratorRecipes(String token, String babyId) async { + + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/fridge?babyId=$babyId'); + + try { + var response = await http.post(url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', // 여기서 인증 토큰을 전달 + }, + ); + var responsecode = response.statusCode; + print("fetchRefrigeratorRecipes response.statusCode: $responsecode"); + + if (response.statusCode == 200) { + // 서버로부터 받은 JSON 데이터를 파싱 + var responseBody = utf8.decode(response.bodyBytes); + var jsonResponse = jsonDecode(responseBody); + var fridgeRecipesList = List>.from(jsonResponse['data']).map((fridgeRecipe) => FridgeRecipe.fromJson(fridgeRecipe)).toList(); + + return fridgeRecipesList; + + } else { + // 오류 처리 + throw Exception('Failed to load recipes'); + } + } catch (e) { + // 네트워크 오류 처리 + throw Exception('Failed to load recipes: $e'); + } +} + +void changeRecipeLike(List recipes, int index) async { + final String simpleDietId = recipes[index].simpleDietId.toString(); + // print(simpleDietId); + final String url = 'http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/press?simpleDietId=$simpleDietId'; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + if (userToken == null) { + print('No user token found'); + return; + } + + try { + final response = await http.put(Uri.parse(url), headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + final jsonResponse = jsonDecode(response.body); + final bool heart = jsonResponse['data']['heart']; + setState(() { + recipes[index].heart = heart; // 서버 응답에 따라 상태 업데이트 + print("[$index] updated to: ${recipes[index].heart}"); + + }); + } else { + print('Failed to change like status'); + } + } catch (e) { + print('Exception occurred: $e'); + } +} + String getRandomItem() { // 랜덤한 인덱스 생성 int randomIndex = random.nextInt(tips.length); @@ -48,12 +172,51 @@ class _WaitRecipesPageWidgetState extends State return tips[randomIndex]; } - void navigateToNextScreen(BuildContext context) { - // TODO : 식단 가져오면 다음 화면으로 넘어가도록 수정 + void navigateToNextScreen(BuildContext context) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + + // 설정된 변수와 SharedPreferences에서 token 가져오기 + String type = widget.selectedOption; + String fridge = widget.selectedMaterials.join(','); + String keyword = widget.selectedKeywords.join(','); + + if (userToken == null) { + print('No user token found'); + return; + } + + MyPageService myPageService = MyPageService(); + + try { + String babyId = await myPageService.getBabyId(userToken); + final List recipes = await fetchRecipes(userToken, babyId as String, type, fridge, keyword); + for (var recipe in recipes) { + print(recipe.toString()); // 혹은 단순히 print(recipe); 라고 해도 됩니다. + } + final List fridgeRecipes = await fetchRefrigeratorRecipes(userToken, babyId as String); + for (var fetchRecipe in fridgeRecipes) { + print(fetchRecipe.toString()); // 혹은 단순히 print(recipe); 라고 해도 됩니다. + } + + // 화면 전환 Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => ShowRecipesPageWidget()), + MaterialPageRoute( + builder: (context) => ShowRecipesPageWidget( + selectedOption: type, + selectedMaterials: widget.selectedMaterials, + selectedKeywords: widget.selectedKeywords, + recipes: recipes, + fridgeRecipes: fridgeRecipes, + ), + ), ); + } catch (e) { + print("Error fetching data: $e"); + // 오류 처리 로직, 예: 사용자에게 오류 메시지 표시 } +} + @override void dispose() { @@ -198,6 +361,7 @@ class _WaitRecipesPageWidgetState extends State ], )); } + } class WaitingCard extends StatelessWidget { @@ -230,4 +394,6 @@ class WaitingCard extends StatelessWidget { ), ))); } + + } diff --git a/babymeal/lib/services/DietService.dart b/babymeal/lib/services/DietService.dart new file mode 100644 index 0000000..7d84170 --- /dev/null +++ b/babymeal/lib/services/DietService.dart @@ -0,0 +1,45 @@ +import 'package:babymeal/model/FridgeRecipe.dart'; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:babymeal/model/RecipeModel.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + +class DietService extends ChangeNotifier { + +Future> fetchRefrigeratorRecipes(String babyId) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? userToken = prefs.getString('accessToken'); + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/diet/fridge?babyId=$babyId'); + + try { + var response = await http.post(url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', // 여기서 인증 토큰을 전달 + }, + ); + + if (response.statusCode == 200) { + // 서버로부터 받은 JSON 데이터를 파싱 + var responseBody = utf8.decode(response.bodyBytes); + var jsonResponse = jsonDecode(responseBody); + print(jsonResponse); + var fridgeRecipesList = List>.from(jsonResponse['data']).map((fridgeRecipe) => FridgeRecipe.fromJson(fridgeRecipe)).toList(); + print('fridgeRecipesList: $fridgeRecipesList'); + return fridgeRecipesList; + + } else { + // 오류 처리 + throw Exception('Failed to load recipes'); + } + } catch (e) { + // 네트워크 오류 처리 + throw Exception('Failed to load recipes: $e'); + } +} + +} \ No newline at end of file diff --git a/babymeal/lib/services/FridgeContentService.dart b/babymeal/lib/services/FridgeContentService.dart new file mode 100644 index 0000000..427df43 --- /dev/null +++ b/babymeal/lib/services/FridgeContentService.dart @@ -0,0 +1,13 @@ +class FridgeContent { + final String ingredients; + final String emoticon; + + FridgeContent({required this.ingredients, required this.emoticon}); + + factory FridgeContent.fromJson(Map json) { + return FridgeContent( + ingredients: json['ingredients'], + emoticon: json['emoticon'] ?? '', + ); + } +} \ No newline at end of file diff --git a/babymeal/lib/services/MyPageService.dart b/babymeal/lib/services/MyPageService.dart new file mode 100644 index 0000000..8144aae --- /dev/null +++ b/babymeal/lib/services/MyPageService.dart @@ -0,0 +1,70 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert' show utf8; + +class MyPageService extends ChangeNotifier { + + Future getBabyId(String token) async { + var url = Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'); + + final String? userToken = token; // 매개변수로 전달된 token 사용 + + if (userToken == null) { + print('No user token found'); + return Future.error('No user token found'); // 에러 반환 + } + + try { + var response = await http.get(url, headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userToken', + }); + + if (response.statusCode == 200) { + var responseBody = utf8.decode(response.bodyBytes); + var jsonResponse = jsonDecode(responseBody); + + if (jsonResponse['data'][0] != null) { + var newBabyId = jsonResponse['data'][0]['babyId'].toString(); // int를 String으로 변환 + print("newBabyId: $newBabyId"); + return newBabyId; + } else { + throw Exception('Data key not found'); + } + } else { + throw Exception('Failed to load babyId with status code ${response.statusCode}'); + } + } catch (e) { + print('Failed to load babyId: $e'); + return Future.error('Failed to load babyId: $e'); // 수정된 에러 처리 + } +} + +Future?> fetchCurrentBabyData(String babyId) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final String? token = prefs.getString('accessToken'); + + if (token == null) { + print('No token found'); + return null; + } + + final response = await http.get( + Uri.parse('http://ec2-43-200-210-159.ap-northeast-2.compute.amazonaws.com:8080/customer/baby'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + + if (response.statusCode == 200) { + final Map data = json.decode(utf8.decode(response.bodyBytes)); + return data; + } else { + print('Failed to fetch baby data'); + return null; + } +} +} \ No newline at end of file diff --git a/babymeal/macos/Flutter/Flutter-Debug.xcconfig b/babymeal/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/babymeal/macos/Flutter/Flutter-Debug.xcconfig +++ b/babymeal/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/babymeal/macos/Flutter/Flutter-Release.xcconfig b/babymeal/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/babymeal/macos/Flutter/Flutter-Release.xcconfig +++ b/babymeal/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/babymeal/macos/Podfile b/babymeal/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/babymeal/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/babymeal/macos/Podfile.lock b/babymeal/macos/Podfile.lock new file mode 100644 index 0000000..1fa14d2 --- /dev/null +++ b/babymeal/macos/Podfile.lock @@ -0,0 +1,35 @@ +PODS: + - emoji_picker_flutter (0.0.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - emoji_picker_flutter (from `Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +EXTERNAL SOURCES: + emoji_picker_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos + FlutterMacOS: + :path: Flutter/ephemeral + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + emoji_picker_flutter: 533634326b1c5de9a181ba14b9758e6dfe967a20 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.14.3 diff --git a/babymeal/macos/Runner.xcodeproj/project.pbxproj b/babymeal/macos/Runner.xcodeproj/project.pbxproj index 029514c..1b60047 100644 --- a/babymeal/macos/Runner.xcodeproj/project.pbxproj +++ b/babymeal/macos/Runner.xcodeproj/project.pbxproj @@ -21,12 +21,14 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 102F20C02D303FE430690372 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91B132E896C8CFFD56277D6A /* Pods_RunnerTests.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 4836F922322FD53D14FE761A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00817BA04B60FA5C6FD7F141 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 00817BA04B60FA5C6FD7F141 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* babymeal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "babymeal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33C48699D9355887F1FE3541 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* babymeal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = babymeal.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +80,14 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5E03AFDE6E2F7807E5C4CC7B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 91B132E896C8CFFD56277D6A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A48AAE98936A410F9DF348C1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C7BEB6D375625D2F9F1153FF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D3F4E5C49A322D3731AB38FA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + E1A1AB733B555392774DCE07 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 102F20C02D303FE430690372 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,12 +103,27 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4836F922322FD53D14FE761A /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 094D55E48C80A7A6740422C6 /* Pods */ = { + isa = PBXGroup; + children = ( + D3F4E5C49A322D3731AB38FA /* Pods-Runner.debug.xcconfig */, + A48AAE98936A410F9DF348C1 /* Pods-Runner.release.xcconfig */, + 5E03AFDE6E2F7807E5C4CC7B /* Pods-Runner.profile.xcconfig */, + 33C48699D9355887F1FE3541 /* Pods-RunnerTests.debug.xcconfig */, + C7BEB6D375625D2F9F1153FF /* Pods-RunnerTests.release.xcconfig */, + E1A1AB733B555392774DCE07 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 331C80D6294CF71000263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -125,6 +151,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 094D55E48C80A7A6740422C6 /* Pods */, ); sourceTree = ""; }; @@ -175,6 +202,8 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 00817BA04B60FA5C6FD7F141 /* Pods_Runner.framework */, + 91B132E896C8CFFD56277D6A /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + B11D248F91FEE86BD597093E /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + EA26A047EFA396673BCF8133 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 68554F309DDE5843A807DCD1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -328,6 +360,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 68554F309DDE5843A807DCD1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B11D248F91FEE86BD597093E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EA26A047EFA396673BCF8133 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -379,6 +472,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 33C48699D9355887F1FE3541 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -393,6 +487,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = C7BEB6D375625D2F9F1153FF /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -407,6 +502,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E1A1AB733B555392774DCE07 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/babymeal/macos/Runner.xcworkspace/contents.xcworkspacedata b/babymeal/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/babymeal/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/babymeal/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/babymeal/pubspec.lock b/babymeal/pubspec.lock index cf79812..f24552a 100644 --- a/babymeal/pubspec.lock +++ b/babymeal/pubspec.lock @@ -192,22 +192,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "04be76c4a4bb50f14904e64749237e541e7c7bcf7ec0b196907322ab5d2fc739" - url: "https://pub.dev" - source: hosted - version: "9.0.16" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff - url: "https://pub.dev" - source: hosted - version: "1.0.5" lints: dependency: transitive description: @@ -236,18 +220,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.10.0" path: dependency: transitive description: @@ -297,7 +281,7 @@ packages: source: hosted version: "2.1.6" shared_preferences: - dependency: transitive + dependency: "direct main" description: name: shared_preferences sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" @@ -485,22 +469,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" web: dependency: transitive description: name: web - sha256: edc8a9573dd8c5a83a183dae1af2b6fd4131377404706ca4e5420474784906fa + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.3.0" win32: dependency: transitive description: diff --git a/babymeal/pubspec.yaml b/babymeal/pubspec.yaml index f3b1a65..4c8a3c7 100644 --- a/babymeal/pubspec.yaml +++ b/babymeal/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: flutter_screenutil: ^5.9.0 emoji_picker_flutter: ^1.6.3 flutter_keyboard_visibility: ^5.4.1 + shared_preferences: ^2.2.2 dev_dependencies: flutter_test: @@ -58,3 +59,6 @@ flutter: - assets/images/scrab_full.png - assets/images/rectangle.png - assets/images/google.png + - assets/images/like.png + - assets/images/like_sel.png +