From 4babcd9ceec73db06459c4838d36d4d785873f7c Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 11 Aug 2025 21:12:09 +0900 Subject: [PATCH 01/16] =?UTF-8?q?INIT:=20=EB=A3=A8=ED=8B=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80/?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=97=90=EC=85=8B=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable-hdpi/img_repeat_days.png | Bin 0 -> 1134 bytes .../res/drawable-hdpi/img_routine_period.png | Bin 0 -> 1310 bytes .../main/res/drawable-hdpi/img_start_time.png | Bin 0 -> 963 bytes .../main/res/drawable-hdpi/img_subroutines.png | Bin 0 -> 1251 bytes .../main/res/drawable-mdpi/img_repeat_days.png | Bin 0 -> 750 bytes .../res/drawable-mdpi/img_routine_period.png | Bin 0 -> 908 bytes .../main/res/drawable-mdpi/img_start_time.png | Bin 0 -> 703 bytes .../main/res/drawable-mdpi/img_subroutines.png | Bin 0 -> 841 bytes .../main/res/drawable-xhdpi/img_repeat_days.png | Bin 0 -> 1486 bytes .../res/drawable-xhdpi/img_routine_period.png | Bin 0 -> 1716 bytes .../main/res/drawable-xhdpi/img_start_time.png | Bin 0 -> 1296 bytes .../main/res/drawable-xhdpi/img_subroutines.png | Bin 0 -> 1690 bytes .../main/res/drawable-xxhdpi/img_repeat_days.png | Bin 0 -> 2139 bytes .../res/drawable-xxhdpi/img_routine_period.png | Bin 0 -> 2544 bytes .../main/res/drawable-xxhdpi/img_start_time.png | Bin 0 -> 1873 bytes .../main/res/drawable-xxhdpi/img_subroutines.png | Bin 0 -> 2502 bytes .../res/drawable-xxxhdpi/img_repeat_days.png | Bin 0 -> 2862 bytes .../res/drawable-xxxhdpi/img_routine_period.png | Bin 0 -> 3382 bytes .../main/res/drawable-xxxhdpi/img_start_time.png | Bin 0 -> 2421 bytes .../res/drawable-xxxhdpi/img_subroutines.png | Bin 0 -> 3270 bytes .../src/main/res/drawable/ic_close_circle.xml | 15 +++++++++++++++ .../src/main/res/drawable/ic_down_arrow_20.xml | 9 +++++++++ .../src/main/res/drawable/ic_up_arrow_20.xml | 9 +++++++++ 23 files changed, 33 insertions(+) create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_repeat_days.png create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_routine_period.png create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_start_time.png create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_subroutines.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_repeat_days.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_routine_period.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_start_time.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_subroutines.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_repeat_days.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_routine_period.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_start_time.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_subroutines.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_repeat_days.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_routine_period.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_start_time.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_subroutines.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_repeat_days.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_routine_period.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_start_time.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_subroutines.png create mode 100644 core/designsystem/src/main/res/drawable/ic_close_circle.xml create mode 100644 core/designsystem/src/main/res/drawable/ic_down_arrow_20.xml create mode 100644 core/designsystem/src/main/res/drawable/ic_up_arrow_20.xml diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_repeat_days.png b/core/designsystem/src/main/res/drawable-hdpi/img_repeat_days.png new file mode 100644 index 0000000000000000000000000000000000000000..b378995217437f9a81aebe0b39900d28a1c5bec9 GIT binary patch literal 1134 zcmV-!1d;oRP)ses~+ zOt^&w8Yz!LkPtMn6E}8kzjyq;Gq#?s`s`Lslb>Y!z3+W@_q*@!JuCFVuG!q9Ci1}; z%29+RiV87s6AC#Up#+U38{>Ga5NeCO)hH^3(}*zRWE#tsa*iwW^Rcr0rfs}omH#->#O9=?)Ria0(fxOz-OkCe=x$B7&V_kW3D@Jv_fyTqw-3J+k)Lejx*V{UO zT;IX`xd~(zjCX$4wIve=Si}e$5HjKC6;$sxyNd?n3elb&Xo1vh;GR`zDH-?1%A6ZF z2vUGGt8N}c@Myh>->zZn~@{tNYX{l zmH$7J%{=&O$H|A1A=oJ{8In2gQ%br?Fn?~mCk`?T0l$MMj!hp8aPsnuBh(?^)*ndd zn$)2X9;D`U&-JiO=^0BRoxrY>O!Hyn)mQEWVR6N$)QJZn^V&x_JPRZ{f4#az3$?s@ ze52L zYi8M9pVEyjAMeg03X_6feuJ0ntRv<;Pa__NA zf2lJra9NY22+HEqr&6@0c9z-vy4q75r})eVQZK08AM8&OOoNpEzO}~+yEl4+902KM zud-zL!1GW^$1%S2DLEp~7Cv2Mj+&ul1Z?tmjC(8aEGRMbav|+{nUFj7{v_*3?5rkk z>lD4}dOMRETP>%TJ(1$Lc6Cn8YK#Il`cKU$Cq`K`|KT*k&nlRGGwo#aeLJ4Vb3s{4 zP>-US<@rZQ?JMeh_lpf~jH;ha9Ln|s+VD&rM1wQz0Fcuzs1FCq^wz4Ld`A@-N7rz` zpkdyf;p?=^N2c3~8Zo%{31Q2$k3l^ZXq&IqCdRjN?W^9Z-EKnLem5^99+l-x(>GS` z?@)eT4q*IUW{;?_sjsrTpO%CD)Z6MtV(uqH$c;6T%br!G+AW*s4)UBN$H{X2f1~Vv zXAANdOf-fP&0dmw(`xUyo<^Cna&lfFo|Gl=A8F9CI171~UjP6A07*qoM6N<$f+g-6 AlmGw# literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_routine_period.png b/core/designsystem/src/main/res/drawable-hdpi/img_routine_period.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2d267f1ccb48c9fc791a32303e816d86b66113 GIT binary patch literal 1310 zcmV+(1>yRMP)rez_uPB_+;fLP4;aba z{ZN9O0STrAc71!|t$FAX!#^+11A|G%z^Nun*WI!A-6(zzJwQl#jg+A_1Goam0ayH% zlrSADrKh{G>(C1ra{(nb+VU&)o}Douu3J#`Y?z&EU-iuyQ@xuDX`pUHx2XcX&px?^ zpWZ_$jY$PqwB19mziP!Y0|tF6|NdlY|EGs0em8J3qbUZ>A|34GPU4$0i)dOh3E8W! z9f+^2Z`gaw4{hXm1YF=EaiKo)-}qvwU7?#FPl3kob<#R}^)hJ5L{sQf*jvs)FQ73r zwgtabft`Ezp~JS!f>&FB8*@&>hd%%GR+uu1Uw>xV>gG1|3cp-YPYcDh-#Rl&R7mUl zlA65vA=25(&SUlVb_-D5>Rlni?t^6zbq~t<$7**YV3Czrl&KDb#dMG;_(UP(Z4Mgi z8j6GySBAj&Bv+n(TEL{qRk)rWxab&+a*7?x8oOFNAuAhC9lgJQ%qtY~`ar)K%VefR zy$lzN^WhFzT%z3|1fu2z1kr>? zK|9bwbz`fI9y2mWXAOm5JfkxR&(vBv3Gs6y>e!%(rB`p`NsW&|X*I8S2QFD{^yCX; zloph^X!p`DjfX0PLG5&{7&DhIMbc?PdZIu|=*A!}ZZwh)t%XNN=N$B(qnk1W%tPN| zJ#_p8c2dBch!vy2diYNBsOqfKK0%=7obkr2s4`0zC0^!v{O8#`HxP zMfwYD^p`uP5Ii5oLq`We&tz1t=>%Fi<(gDT_><`=n+x3W1TG*<4ps;s>Aqky5y`Ws za7~!!Bk80-G9AsMAYqDYT+Yc;R9CCb8O|-{|E~lYp$XPP;*I7-Q7pjo2I{c+;yaD2 zbgh<{gXav+VV>Rx{!GO_!<;1OvL?kc6-(;hPU9yP73|Oa>^SCS>awuZ9Ejxrvq`?{ zG3^R4VKKb1Ze4tPuEE{~XIJi8_{YqSqGMoN8ff4H*e%}CAMThHqvCe^V|EmO1J?{7 UAv>m3E&u=k07*qoM6N<$g1nPy?EnA( literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_start_time.png b/core/designsystem/src/main/res/drawable-hdpi/img_start_time.png new file mode 100644 index 0000000000000000000000000000000000000000..452a361227fcbee2647365ad57faf4a7f00fa6ed GIT binary patch literal 963 zcmV;!13dhRP)PN4a5_GCjd@hy#bs+xPTzU_{q32;R6Z8 z#ej+hsUQOBcE*`vVQII}owokcq}!RD$Y|2gE@(>ZK^hIYro!};o5aBum;<6}lNfVE_m;v{vqX?@=(EX*IF@i`z7eSsJ zLHXfR?vu`G=n9fTCZv!AE+35u+9>sDsQm?^3i>Bz5_N!lqt#8eX2R-NSfR7HHBv7z z3Ss6xt3Mo8S29A%yuzf{^3g_p$&jCYP;7V#5i^epIoSSzql10yZmgr_MkhLY`@l>K z5s;#cp5XMIn=)?DC?c@A>|)1TlPlZDukT-R{Cgh}vAM<1P2H&==WTjY#+YZQknGW6 zNm)(`iwX%b9`qAK4cS>=gAQr|IZNpT3WAhE!Du2%Cst#V^ox0}Ok>K<;53pKr1m78 zCC>dUj3NU!##*3@U?+BAFf0cesL?3)QCoK}s=#*UcWs88$mq1@ey*f!Pp-7;II^`( zeI*tvDKjh+RbiTO26kems3=nxbER3|LwUGxq#!Yi8ev3>(xnjd7<}J@20SDP?rl}+ zArhII2F5>`pICkOCcn37!I97wq=I5sUw>(za^%u$D|(^~>aJ>p)D9nJZiNyRIdb#d z98>v-%t2m312szB4+nA8ZVRp7+uDpKidexz`YTO|x;yX)F*#br>~4I)`iJ+(9v@cH zmFEt3EaU~L2l=HHu?w``=`LHBUR}9Vlsb{9@1TcVR6ik`>AxYl*Mq^^Q#^9skK zQ$fV}9I8Ya{N}y>ysv_0kF|HeYG^9kj))dcpMw?7(soJ!W2gZt6WCf=MniK9?eX3V zb8VUI_Y{)-q9lLUmPeG2@-dG8gv`aJq=zTZeup5_$w_(ejg&!ZkRg&s>!Hrnd4XjZ zSlU*{p#0g#Zy!5DvmY6riL@8&XjM9aNBx(&?Z~OJj>5Au!^M z927uUJ^}~|4ChG1NQub0$ldw&_DyDHchCE4U*U`WNvGR=Z}z?U`DW(rDo6nrugw6c zI-qtz`7N+81E^dW#|7|g6_l=upkF!z8YW5RGJts*z}CT6>onGs=VURcfi2ay$Mie0 z7DK6)ntlFadVe)gq_J8B-#ZT$=ivUK1&%$XPQBQIxI)<^L(Y@zyN3P>!04z5-uR}9Rv{ZHTb4BtbBiE9qxYk1$bBgf|0cUgOL6YR#OTxtXp>z3%!JKpQQBY+E z&}~Eiw+&De4w&N)$(aAoZCR)E$b&F`?s(2Fs&0xD=zO{?oAJY<{K}2N+}P|B+X|7# zB4tGG>RMtQp6`A0XVN%YYwXRZMakp@v$NPNcWla99zM^K6%I|E1i%BJ3%~icrrUq#;7US9jbK1 z03tO^RVc%RG$H@k(yc4kB}{Q1?Ikpn^un;h-OpAegiKNn&nRGq735+V5#2fYSdt@= za>SE}z*@o-ke{FP*KP&lhaQm3jVH$TLjF31%&!r>Gjr{azX~*_ka4J@wC9b+-*{?! z4Vr7ueVgkssYiL^RGTXAzn0~s;Y(I8ze!AIrOhP;2N*P*Y>|nP}PcLDfhD?7BC_@X5#lppk#xv3T`3CF;P4b-f`3e<4g_*$hN4yi30p?G-+jF7A za6XdDD8U)l2}(rtpGke(A6|7EzJ0hb zWKk`O(w^hKTiT1oq1B1j5c33|APQliX1F>Q9bwaHKXUY;6}-7F{&;79UlawdI(=e_ zPmJ_lhO2N8py8}mr(bANaF$@-ftS>)Cl-Q===&FlOG6*j_Ffm_u^)VONq&y;dYt}s zxUaw^Qs#nsJ@_P(IZWf1*J>2YkLU&RJl#^CL;A@Pr51_=+=8r N002ovPDHLkV1ljMU^@T+ literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_repeat_days.png b/core/designsystem/src/main/res/drawable-mdpi/img_repeat_days.png new file mode 100644 index 0000000000000000000000000000000000000000..e38176e51591febfd8c8d8cb92f642574d81e930 GIT binary patch literal 750 zcmVuD;147PElB(VXw#AW0fI!!hueTeT+%tB;v;CWqs6x$8rXj)$t9SR)+7&Y*=uKPuqYgbrc3jk=*01t-iZ9ty?IlyqEE2IAlRFTk7W9Z*Fn?uk%ul}M>F)cs4(ZG8*gR<~nqjKZ$dZka z7w>0r8a$GJT5H%^Ou(S1xq}IbGy@2EG^04|ZZtE9WwRBNzW+$>A z&L{b~G#8^rDc-@$j@4D#bVwkw`M-<}E}i6k5Bp#2(fSDFC+PCDRh60y;&%sa$t?=0 zVA!g@#|~?R?V8HFlrw`ka8=!9ltOfKlFj0S9Q8fq0m|E*r1Fl{SvqXk!cwCI?L)AE zT;{YxmvVGY2p1n_M?`<^dg8<;fp-JD$e^C&AVH=9r7ukNH$63g!E-C%;XAX*!A$Q3 z^1aF2Qg=rc>TH=3OSh^W-mjr-InhvBft|K)Po>6^+{Grqx{PI=y}J!^a-NLlogSb@ gY3i9}wJr0102CnzA{`MQW&i*H07*qoM6N<$f-gW*?f?J) literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_routine_period.png b/core/designsystem/src/main/res/drawable-mdpi/img_routine_period.png new file mode 100644 index 0000000000000000000000000000000000000000..599de3e94e16b245e4035b4c6a6bd141dba0416d GIT binary patch literal 908 zcmV;719SX|P)-XIj|(;y@o@&@n*KnWBW3Mink z5lN8<;vf)#C~%JPzwefr4gNdZ$P(m}Pv`w+cV>6y+c|^*IQjm`0}Yt+XhIclwHq0$w5V!`&>#h`^`nL*Ub5e>_XmlsvfoI8saxR9WK34bYI^>c%*e|MMe z@5ghQPM(;XR>JRBh1HE*dFzt`4ICf`TIT>!(Mk)YRMEK^v%I*mnJcb;NSlGiEhvXp zV0qb=aF2FY^FL&#WNF1D=jQDc3YMYwVUcrd-wFNd4RGlZehE&gOg!FbZ97!D4!4&U zB-H)$v?%F48tn*>7S?uu!rxGMw+6mo5GtR)dQBEcoR@T*WJjXf0oFglFsN?t3^pR9 z1RN|Boj}_d2xKXm*43JknN*_*-^d6T8JtShcI!3Dw?la@sbFlJ@?M8Vp=k3v7#mY; zWAI#>12QrSPO+q%O2wAZYqRWosu@yiv{{~=yBJ_o{dVjbTY{XYIvJ017}nN*0oX!= ze%#N-^806KOiMs9MQ88ge`QvoL^4&$OAv@2H=I)}!;fb#!50o0p0&@|dy|3cmv0~f zemFWi-Qxe8AR*9o^WMNzAoH$&-)(P(3d#@LbgkavIUw^$g6QPU8US1NIT#$dcpk#V zB8YIf#gVei&ZUZU?J|63(%kyM_!tEX_V|)ohzNR8#0~^G@}ik8FM_8h`tm}%c>&tZ z!jRy?*E-TR1acC!>5N^l)2~fYN30lxMVrpvZd>g4xKk<06wJ<_H-mX#D`)7$F~C(hCnHwtNj z^M8zF$l!uE!TI;mn1ggll;EO>NJN%tchBpm2Cw5z3@Sc@ZnFCb4hkA>iJkb>j7Chu ze+K*9iO*$B(tPOLbTlLdD~jL>ks=LeT@bCyfWT^V6J{~>82%<3-f>D7fuJHdJMGy0000I+ZANW4Om6VRT3<^<&iX>U+&07u{i`Fel{kc`9&NC;Od zX=zi%q!lID^{~5@LlYd^DL={HU3+$Bb~HP~kb>RVwE&|+29^N^5Sc6tTg3S?3$&V3 zEqF=$*|Z-II*02Fv>~P@l4%MpF7y3Or+U*(HDI^DuMpGg!c`ly;R%OUoyW!IR0A>` zq#F$}wQw<9VWf862%AOu@c0Pa&|%Ewk!U3Ft5LXk^flDVT?UZ+BG~LsL}s*f-vxr2+^9|+JCMLsL(L1C&`CF0Iut!`A@HpG(;NL z?MC0^(6owJSCA1Sm&e@vd=^m?+V2VnF)K|{hDUlXIThj2G#%MsCJ$My>B*i@!Nm}sA3b!c z_ePhOPfCcR)fnLwW~V7UUp=o}>6<2PxA)hGaoHneZ?>k0c}g*buVu!>2~0H*W9*r_ l^(4wLBXNP~#bkqz;}1-@>**sgJ+lA+002ovPDHLkV1j3TJyZYy literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_subroutines.png b/core/designsystem/src/main/res/drawable-mdpi/img_subroutines.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c9c614f1a9b39fbe868ba0f15b2d3228b5386d GIT binary patch literal 841 zcmV-P1GfB$P)&QeM{YAX{fSppa}BnpW} zh!n&u7Hng1w*Z9Qxz}^f?CkFFFowsMOlI!uJNJC&oI96Lg`1wupzMu+>{mF`4T};y zn-2!L4P-#B2uCUY&cpdDa9#?Y>xAVsFLFOXa0U;D}(R`hQTv=Gf zL9r{yqOoWx`ful!^xHd~s*P!E*4DKxvWS%aVz2@4Y&&8%IvX57NrIdTgDlv5`95MW z@%TOtbs?MUDf!#&y`dg!VtY|=>% zdUAKY5Tzn%s%|o&W(%jRWYyN2Z<>0Ns^W=P^~T#H@L@VPNR%-!uDEL=FP0ri3?$HQ ztzK-dq3~*b`&PP0Wcm25JD@Qz=@jC9U0TVeYD}>4VhXkpRIiYRI}Qyr^_)R7%NUi2 zC~`!bwi76ST-HY*a)pRzFKUpbutYv3XDQ9eR((tDM6lOc)I5BhS!0}f1tn}OvKUvn zJpT=H;sO7)oEJ4RbQO^2uplpQaGY>HXHkV-Lw|8cP0pMaK~#7F)mcq! z8$}p>v%7ZU#z`CpRMdcE)C8#qN>1cRBHBtlHSLu{gTxh*UbqsDJ&|zckON$+<`fBn zk{sbeClaR-~bQqCrPmcI|=cG?3HkUC0XK5k)= z-|*JF#GZ>;lriMlQkU?Mfzov|1DDj@R=iYWLG}l4Qa*muRhX=Hq(3Oc#+$TGOPvIu zemW0oSa*^)8tk&x(1VCvP%lH$g7@WEO57n2oXZ~>tvo5A(3g=|F@8Og~ zd6@ENLJuIjDno#%OqYOaU9vDWB=%!bZMum(L3@mp@fGi_VzIl(`*4AoZBc#8vzmC9 zE`eRdw0Ni!yTDfm3mHrNcp9$=xqr$?f3qXk5{?VYb+CX+Ao<_1d+#}R?iKLwdJf0L zHHWTfchx&G61GA)=VAA^=ZGe4MC2DuT6OR8BH7&EB^0*GTFR_P@EUv5!E2K-JY7G) z)=zn<@8aBz{Kx??}8I1}{eS4EV5P3K!;w z_0E&}|6PX}q-ldn2M03kIep)0*k=@a zM;z1YX;J^QVla^Oc-y4Kw&?1k@3JjBw_}#p)OFgYVw?JZY>x z1QR<)Lh?=5u))^Pc{C$_{?i0v0m&7%Ty3-@=_5WGJ!GJd@9b+MCI;T^2}Pa|8<=sV ziv^R)Bt`vh#JTJ7I`RaGMw3yrL+^-6m_a79RmdD51 z6K#d$MDL+8y3}Un&A^qOr3@qSz{SH;O!8s;_E9Lx4AaJVHAP95txn0tbrT~dc?KU} zjc9aleH2G0u>Y`#hhJBc0mcSXI3 zk#+L4qioE2hAy3fi;^F?(HY5;{O|M6>S9V5p$uM9bH)nHPAGZ$>P~@VlM}JJvWU4& zB`OOV7k`=5W@nT2ZCS@|LEInEl@G*EL6P* zNtvV>tn-H$Zeu@88~fDM_4<^PeRo55nm9c12 znYz!s=VgH@MHupg>tR_lk`);PnNeCzb$NVeuQn6@pn@4F%UWfmK`;mMk@{o+VNtg6 zwzK=aH-iw%N$N4la}dtolJ z8_0}BfrN4i6{nJtUs~2H%a$!?%yM4L+(*bS(R04&wgqx4UMeq#*>qLP!Zm5UAswZ4 zM?%uJFeu)O9&B9Bdu|O}QA>VX$*rdKfwz*5mDG{?)LFi>igFTK=~<{907*qoM6N<$f~mL5PXGV_ literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_routine_period.png b/core/designsystem/src/main/res/drawable-xhdpi/img_routine_period.png new file mode 100644 index 0000000000000000000000000000000000000000..df715416dfed91279c4001155ea0be9577467fe8 GIT binary patch literal 1716 zcmV;l221&gP)Elqr4q3!@zblOPeA$vZ6yRkT##N>Di`Dp+CBl{ zqN*A|af=W_6^esMD^jW1KuXhuCR;o8-|o(EW}J0xXE)w;LBwBV?RtG?=FIH*o81ry z$iUs}ahofb3Gg^@h!eukKN|UP8k!___r@2%V1g535JFPxuf2C6NP7wak;-$P6v~V5 z4CpC%4mc-CJS1eFj7`Pvd^ZuKec%b;h+6em0-(Xa3AYnL#8n6kzlmyB4Z@$@@~Lwg zM8iq2xDKCxnfC1B;<>nUDp$U`cd%w40A$w{p+tu|2{u6+H>aTp2DFDJ&{h&a$>F|t zk7`O+N74~5K3q>d`Q=yaiD=lH@C)aT=yGzL7bv{Q2QGe`MCHGNJ(PE&u=)_1#mr=ZnO4m;J6G^hUJ!>6nTJcC zUWTbDd8f-hAeE%YInNG(tVynuu&xHj~Ei zs(rZ7#l897IM2xE8zBlQzjjyX^|+CdvX zhR0|_SrQ6?mOKU_t=m&}^V?hUoT98~6CECcuCdWZDuH-1;(JqH(EpU3mj}&3_O|zy z*W7qqk>#cdTc}_Ouep)GcVEhgkJBcQXb5NDoPp|?~Y66u4^opca;@) z$h8k2W<`MYQbwo&J`iS8JAf(ggsW_Ct+}XL4}=E?TxB~(hvlcD6i%q;_*>96cn})F zfo`b^wRKEn4TU4|LqNiqzDu|=W_g9{C*=_C?}P3m2{}QEGEr>17ab$RazS!vI^A!N zq4ERZ7j$AJj?UmqI7r8l21?h{iB21RWyo_;+&~k0f7#3s#umTb69NghCe3=$kW9o44*j&#a@8O;b1=D2k~g1^e|KlKqhu}-<&yc3%q z>E7BSOV?9;uvRG{97O&weUuUyK^G`d_>8l~+l%_bO=Q=uBkptWOpIVgRcmBL@#lsx zt9C5j>cKrQG=hPBMLKc+o!Pni7Y+3|nUe0r>b92#_Ow%B&!XTH6A?D;Rk=C{)3J;v za`lDNx4$|BjVQI}deMnzCovOWhSuZ)7C|<&F8E|_WAx+|EQPK@OTt&mWhiNGPw>CO z`2zaDw44A3rbCOti9Klyoi-}lVLy>*-+&ggKJtMKQ~WXkDFTmD-1E9`r>4zr1k(f4Z}hx`Ni7|qFFW;PH20000< KMNUMnLSTX#<|Fd} literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_start_time.png b/core/designsystem/src/main/res/drawable-xhdpi/img_start_time.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea92d2babce907f21718a8195e8436ce4ff626b GIT binary patch literal 1296 zcmV+r1@HQaP)2 z8%Gp|-&ya5pg?Lwa4D&T5tXQv2)634sI;-KAmj>4uOR6aBwPXX3hLYecVGkRuSUuu zwNmo~#WZP}VqCURN+R#hbk6!C_+!7kv(P>gc0cZA~c>S}5<2SF76rpMB=GMVL&)4UL%<_RLef%>F$(!dt$-y6? z@%F6q+0^_3CO)1;Lge9>nd^10eJdbz{Sk(QJB~Nw=Lb)aAO&Q(!TZM6C4jb$9L5TU z#B~}2KhGGU-Z~OYh=(o5(xGPs_#9H;FS#m;RsrN#%xu)QUKXEbtAStx+}*uj-+<4}r>&Ab;nP0@#li8JeeOC)3#9`5_(z_~bTTU@PrtONHx5y_B8}J& zeRLFx@oZKzw+~jKh|%nXZ<{fqUI2L_4ML6g31VbSu?K3SWmYtBqXfFS{{LZvpO#uS zz?2A}U~Ula{ytF_N}PxCOiI0wUob~dm|ffZ1&w!SP(;fH6hfiI*p1x4Hag&rUj2$! z)>CyNJR4BV^uq1Hb`nA&Xj>(RWt9uibR82ngTn?A@pAV^B!)XBPXOcYcTmXGCkvq> z$l!T^%|zp>-MJ@T+NsNMIv_DS1oi43>ic`X?PN3bGjnT6uAG5bFk$U3PkpAnfA4NV zF_ZcEz?Gn_L~&AW?>XYaxbSw(IUCR_NC6T-b`eDLoQM_dRpGi7x_B?Z%_u22^g@H5 z{rnEDe>mgY=dBd*a`$Oun_z;kwG+P`0j(3ciWH!IKKdp`^;F)eN%mQ-ad z5U%*eW3!ZFLD;+ zd<_>*g*BY}VVxq^iOnAlOgJb|wFD{BbmL;v`f{ogv41=$wJo#5Bq9&B*o?^#VL2D8 zv^Dgz66Q{=AoJXLd0CKx?je41nCz+|2e2P!#(PQhutys&)ve1)J{Y;&d{AFHj3D@v zVF%pzeNc>->60rXGOT$c&`g|VWo2axx>K_5#Cw<{jDG>bjKY-}|2cR70000OoGNIOwVIy9FYvK#KAtR{ zOeheX;}VyDd1?mTPq&YQ%k{bVdNIb#UyU+f8`2QXD@m2o2(QKkemYKD5yHAgcJ+Fc zks1TP&+&@V2h<6$G8e_E!#DIT{+ReH9YZ`I{josjLIzltbJTILf5LyrfOM(zkXOIl zEeLJ8s@8)P>##MU$YpcB$0o|MX)AAt_{yEXsXXz7GW|QK`}`piS%=yNAzXdIpBkT2 z)FRY!g_f?|UVYAf@gdoVcT-#8boJS^K`5_S_iXKI+(fzXlH~K3LH*9{+-7^Nf2l(L zqvu3D4%zSStRH)^NRoect1*wQ}oWU739# zAQZ%@7eze|VDl{3S=mrqh6Y-_|O$8o7rPHeNngjVi9)01PRFgWDi zOkxNVOhgp^)BhUAs$#`s0ESj>KWr@L9mG6|y2bI8tpl-%x!4#5TM6RLiIr2f-?H33mZoI`KNu6$de=$uWK zKTRr=$N*N!W@53TE4TjA<}=g(mKKrChb*kyjDnHumjeyu*d3s$$$K3TB4?9tnTV)m zK(?~HGqtl zCm)l@ZKLk9?*)_bL?~M&p5=e7_(bERXggzu(LS03n0o-02;z#^Dpj|T6SFtVQwp@# z4HI)Jm30o#;EB(^-_Wlb3>saTCaj8>8Oiz*d{wWpBBs^Wkld+28$A(6h;8f(B8mLu(Ctp4KvUc zm}0rO%>8>7+>0k$>yiOelc?selc}qDn#-enAIs#&hUw&W0 z-Kq-~X9h1a8(V)TV11UHugzm!3?YUGMoCG$f8B)GxGC7w%d1rEIw;N^T%}JWwN}5) zj#9tc9T04Y(=V?r kJ5RFenyVNdnopzee;qaplCP%R&Hw-a07*qoM6N<$f~Jr&EdT%j literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_repeat_days.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_repeat_days.png new file mode 100644 index 0000000000000000000000000000000000000000..2e65557b042857f71657a8a0c984daf294f0fe88 GIT binary patch literal 2139 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_routine_period.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_routine_period.png new file mode 100644 index 0000000000000000000000000000000000000000..988fe4028a3365a077b67a09723e92f9a78c771c GIT binary patch literal 3382 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_start_time.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_start_time.png new file mode 100644 index 0000000000000000000000000000000000000000..1e30b1840bcbae7f500285aa12e9d3f4dd3a7821 GIT binary patch literal 2421 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_subroutines.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_subroutines.png new file mode 100644 index 0000000000000000000000000000000000000000..5360e7b846cff6f799be441b6582c5d9d8b6b22c GIT binary patch literal 3270 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 Ef(5TU!T + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_down_arrow_20.xml b/core/designsystem/src/main/res/drawable/ic_down_arrow_20.xml new file mode 100644 index 00000000..174fc19a --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_down_arrow_20.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_up_arrow_20.xml b/core/designsystem/src/main/res/drawable/ic_up_arrow_20.xml new file mode 100644 index 00000000..dfb32785 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_up_arrow_20.xml @@ -0,0 +1,9 @@ + + + From 6b7299c547a51e32d3077d55f699aac074c13ae0 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Tue, 12 Aug 2025 22:04:47 +0900 Subject: [PATCH 02/16] =?UTF-8?q?INIT:=20=EB=A3=A8=ED=8B=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80/?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=97=90=EC=85=8B=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable-hdpi/img_circle_1.png | Bin 0 -> 888 bytes .../src/main/res/drawable-hdpi/img_circle_2.png | Bin 0 -> 1013 bytes .../src/main/res/drawable-hdpi/img_circle_3.png | Bin 0 -> 1079 bytes .../src/main/res/drawable-mdpi/img_circle_1.png | Bin 0 -> 597 bytes .../src/main/res/drawable-mdpi/img_circle_2.png | Bin 0 -> 711 bytes .../src/main/res/drawable-mdpi/img_circle_3.png | Bin 0 -> 724 bytes .../src/main/res/drawable-xhdpi/img_circle_1.png | Bin 0 -> 1153 bytes .../src/main/res/drawable-xhdpi/img_circle_2.png | Bin 0 -> 1339 bytes .../src/main/res/drawable-xhdpi/img_circle_3.png | Bin 0 -> 1331 bytes .../main/res/drawable-xxhdpi/img_circle_1.png | Bin 0 -> 1741 bytes .../main/res/drawable-xxhdpi/img_circle_2.png | Bin 0 -> 1900 bytes .../main/res/drawable-xxhdpi/img_circle_3.png | Bin 0 -> 1993 bytes .../main/res/drawable-xxxhdpi/img_circle_1.png | Bin 0 -> 2189 bytes .../main/res/drawable-xxxhdpi/img_circle_2.png | Bin 0 -> 2521 bytes .../main/res/drawable-xxxhdpi/img_circle_3.png | Bin 0 -> 2524 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_circle_1.png create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_circle_2.png create mode 100644 core/designsystem/src/main/res/drawable-hdpi/img_circle_3.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_circle_1.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_circle_2.png create mode 100644 core/designsystem/src/main/res/drawable-mdpi/img_circle_3.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_circle_1.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_circle_2.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/img_circle_3.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_circle_1.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_circle_2.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/img_circle_3.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_1.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_2.png create mode 100644 core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_3.png diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_circle_1.png b/core/designsystem/src/main/res/drawable-hdpi/img_circle_1.png new file mode 100644 index 0000000000000000000000000000000000000000..cfd013754e5a1bd23a1f2d50d234a2d5df481956 GIT binary patch literal 888 zcmV-;1Bd*HP)9E1fL1Y5$jSI2K~Oyz15K_J**9R`KC$ zE|`&+uCemxPAjkRBpAH&`{3ZmZgVAXF8J;0LYI^~2(Y)(NwhM+X<*_dx=W~6II-2h zS6A*A`i0 z9x`-zp=9#_w*|A{fuo~e4cXgQKub_%I_CnOcfUeSalxYj=&9y8<9G-U$iOoU-H{X1 z@1csIJV9nmymaQ9$2H02zCjfxup^(`vXaM?U>-ve!_XVDml&|QOlWJzG3^CwP-Mt3 z8uEkt_n?MY`|l(llMO;IXcM7Lm=Z&rR@b%F5+{rL!Nqx0-#Gwc7&F27h& zIo0yCwDX;u3G#BWbf%eY+FH}Wn8=gokJ>fzb`3HnN+oTSf@TG($bE$_n_=`al6py>5Z1i5T&qa9oL$CV(6f~o?6w)QwS8BjWsn@qpB-`e20 z6@fsw6MI47Tlh0l;k{DOEq z4zqKX(l-P+C=dEw1u$@2R~bmcU%z@O(+o{bm)-nNRAhPio5GivaW&XsevD39eAoP@ z!YCAWNeWLS5_0DE9}K{|vG;EKt*)D(iYAzhF?z-Jwz+pR?oV@5lT1gRzi?+tDLJnT zg=gHNW(8qL1%Qi2P-J#)4#p=Y_teMZwwpiYI|<96AU>l26U>Vt7gShS__A+(wWnI9 zI07v#rtAB$to5LP7tfoJ4ubtwwLmVNxE=4@-Z^8)?_ZUC?~+TaNau|9iPvx`tMHoeQkBVw*K@9RH7M549$KfI(av@e$PR? zwV{VGDvUsBSt8TJzKZ1`5bOvuA-eIO&wwJKSv#7z5u2J?%-`n_=<38B)>Ft|68#N1 z7+bi|>{u+ac(9U+1o7(Ti1d&UVBk(^%#xM_+BrL^h0hZDI;{HL!cL)VWt?;m4qxQE jL2P{;l9m-oLV^4P)*4(7vfZ&!00000NkvXXu0mjf1n<|u literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_circle_3.png b/core/designsystem/src/main/res/drawable-hdpi/img_circle_3.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d4f19bd2775effb072c0b14baf100bf31aac45 GIT binary patch literal 1079 zcmV-71jze|P)HY&Agv^E1-!~ zDmMh_OiAF53-KwG%5l38uoX}jbSYPy2b$$#@p4&rUJ=w`dwS=DfY=3vAyU>04Jbu~ zh}a8Z>@MZc*?1QMmX+)6+0guhrklWffXGi53Ku8W`yT1km`Y{GjHr8)9=GECu{Dtx zz@tYG7NJ8Rl^z)<@egg`N9Yy=EFvL@LiF&<`<>h5>%Nb0D+diR{4-O7^@7J#1fDR<3^)LFr zS~SS{hI9tpT3W(|i?bcSu~Yh`4%iucTxZM>O7v1}1P-Xxp00W~&A4Zl(dUGxj%lx$71nAsdjAsRzx<5vDH_5+xGFCA zyq6GDVFvQnHyY2{p~$<@vj4>7uiB=N!Z1m<(5_xuV1MxE0e|QXC%0+ML5axDQ?X(= zFzg;Ufdg9Gn@n1?%HLKIU^BF!n~ccq@3543uIB?!NC{^=PCiVA74b7)90@H{Divr! z$rCKRvml)wSx_1$;r@L;AWPY9JsdprFP4^WLqmIcu5jrae)3L3*EJ^DCPBlF9iM53 z8k?YH29}499Mx3LtCr=J=hqnCkbtJjGW)<+`h)yNkt@QoUUh+V=hDiT(k~ODax0wd(({pw#7i&#J-c2)= xT&P7yM9&5NkiIj4wcy;rGAbkGdIqP)Gvye##t(}`YzsE7a4y_J8X*lI+OrzVoEB+0)Zlq*m*J0X7;9c-}3Xp2o0g&e9i>@VLxyHwa9GKPD3c zo=&H*D$8Cw%p4LGe}DhsBR(G-nQlL7Jv3mu$Q3q=2Ii^1*)^6>Nn j%0otWelj}RDQo)&T6VF6d$Ip|00000NkvXXu0mjf=@k%t literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_circle_2.png b/core/designsystem/src/main/res/drawable-mdpi/img_circle_2.png new file mode 100644 index 0000000000000000000000000000000000000000..ace4b5760a40565ceb4e80e669fe087bdbbb674a GIT binary patch literal 711 zcmV;&0yzDNP)0i`!CcHu+{H>k3+~AtO@9&tHM*jA8|qQ~IVku8Z9rS}A}M-POivas2J9gw zYSUvqDOQLe%^q!f*-K6vcbxa)MjCcEX+KD2-|X!7zW3g@vj`!WOlM6M-HQYm2Oxt8 z`kOnrU6R(84wsu5D%V`m|V&Gkz+3*D$YAjYcMd#auIVM%d0gn zGvr+)lV)6vIBR<&l)B=q%s6`D)|and?^3nhY|7aD7yW^U6*hT@!2w#WmW;Zt!To!8CE=&fwZM7;*H94f z8w`MJyte|kZr=&5W4?72`XG@w0yl45m)lTSdeK!L46+ngp<)=}<41WJ7Z#sLJ9+sa z{6YLG&nSIvy5aXW2}0pdp-%h~iG^# z6G0Tl-`m}+LOg5_=4wKO3ii-m>~+N^Mrkn@K~JXis5Hqzp(j&l6}(7;dQeKdDR?k7 z5t2yTJ(_wjLIo8{a@6$T9!#O>X2&;UEcv(FlHVaS^JenRxAW!=f&)`i(>ls*JLp%{Qwh;?OH3dTfaRssq2TuRS9q7pL%ICPQQAhh zD5iUL3B2|%j z&gA3;aIvtaxXi+f=g&l&NZu7KzhayQCCmvMvC_V$*6%?J_&F{wEq2A$H#Q-ie(E5} zYNg`)fJjj&bTx*Olo_EC{t1UhLJU8 zNtqxc6&Q{$`}<0&V}owO;h8KTy9UREYSEBUnJu)RJx*Wtn@u_9IoXN=W4CBW-9^o0 z5+*+saxZ(QTx%&p?<`|hg4|FkvvD44(b-w($r>Hbdie_x^4oo6X^L_H0000^hSOF literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_circle_1.png b/core/designsystem/src/main/res/drawable-xhdpi/img_circle_1.png new file mode 100644 index 0000000000000000000000000000000000000000..22839cdab82290b7d615759ea19a6e65e5f35806 GIT binary patch literal 1153 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_circle_1.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_circle_1.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c6ab19dbed7823d3a7f3fab29c269e16ccdc76 GIT binary patch literal 1741 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 mj>g$!F=@y=%j346u9c?&TDwZWK!e}_0000_M literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_circle_3.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_circle_3.png new file mode 100644 index 0000000000000000000000000000000000000000..8c778b14d75c761d9eb256b383a5793934eca3e8 GIT binary patch literal 1993 zcmV;)2R8VLP)j{00009a7bBm001F4 z001F40Y#QEU;qFB0drDELIAGL9O(c600d`2O+f$vv5yP$gR%Z92n?BHmxn#x&do$65k@zphWU#1N zrVHDX(!aR8J0Ny$EuJ$e)q}dOJLYnDo4o@pp0s28A2i?ZywVAce zir8Pj7pwt?^-uAcSAOnu*>ytm3(0hl zbDWy~6|6*?c`60NAdwfFno^FZr+?8Lnr_DYK0Et|CO9)i*Vn>JrlO((WJ9thV!kFk zH36I;5x3iw%WzE*(*_E##`Mvtr@N1Y7q5dPbd&y`O#H34JGFN{&c>&oeNlv1ui?-t zK2L`a+er(m#MrJ2&$Fq+=DWDKnDKk;C;HEQnUW7oPKIFM{8wNROQzabxuCD-WXuT8 zIl2-;&)fqDAY}uppev>#)%2JUUws zo4hrGYn5KuPK0UALor}8?xL8U2^RW03X4`dKY`GPtsd|FV)p62z4(pcY;&j^IRCX} zGrb-WwpH(`eVk&Q3yJ_cw&Ta%G`tpxL|`W=rMdZ)MA>WY?4!dT1DvsOgSrT?M`t*M zB}V|a+ogp^e(BU})m^!zWiG}y6GZPr5pcU*S`%Y!C}cRsS5-Mv&RJXC;bmJmJh!~3 zy2j6D02ae$%)%RSZ((6wCCK$XF9#!K6^erPqwxqYFsHklRuIqc*H>|7x)9cQ$;Or=6f}m)dq2e?Dn7mNe)bv6kk>MZCPVQuu*sk-C>U$%0 zN){eKy~+P-&5V**%mtECTn<-VUH(Ke!wO)}{rl{!p;;&MZyLuY+w6s+g!w(*%urNm zGyl`%jYLkK{G+?ye=pDbzTn~ zXiVwa@@MYca95TyJWh3W1-;hpfzzi?X&u~TScw-dT+C`QrXj(Fu(t$*LvwtX=-aoo zd2a}im+7&WHd^btRb+#cpbtOlO&c2XLO@?IIC@q;Osw=6CHf$d5z5Sr;(F{fw53?L;m!=%>pv5m7x3*Vc4}6Zvx4r?jgO`7*EQUMDFogHEKa3o)UuWh7cs%H z^&6Gbq&9Nb{zk%nY-PoE#^{=%@SN-OrhnM_++;tNZlAu*=p>i9#q97{0nhmAO36zT z6GMNc8)Hj%;m(~~q1`ogcOQAg=U|H=P&qR>acwHo7*iXMJ?-AJ6e_mNCM6PTAP^YM zx^vV9Cb)C^R%lO6Ei1|en;Z^l!NBO}X2#~U-=6R52@xF>n^XxL(gJ}IbIk*Q+^TqP z{j99mk8Z|Dzz%M02A2&}W}nPviv;}m8Qpz3F&?;n=62FHGc)A~4vLC?stcrTX-mLe z7=#(*C%O=?1jqhwO5}_5$4ME!3smx0j4VR!&D$E-sUojubvOd0p+X)WOot`H>pTjL zJt{y06^V_&{vj@u!}!N=L50KART|Q_h+2uI!l#3k5+kKhoNeIpS%7Fj1%_9b?fq6i bIi1XZB&!JXvnA8800000NkvXXu0mjfh)>J7 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_1.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_1.png new file mode 100644 index 0000000000000000000000000000000000000000..17063bb0596fdbd7ba75dec6a9d0c0591ad83f14 GIT binary patch literal 2189 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_3.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_circle_3.png new file mode 100644 index 0000000000000000000000000000000000000000..8689531d4661bba431c07a4fa6363ebd16c7bb68 GIT binary patch literal 2524 zcmV<22_yE2P)@~0drDELIAGL9O(c600d`2O+f$vv5yP_MQ9?9yz8*1B5~5PPckfTj5CRYsGm;sbq(3^leMA%+uRtBzfP^m5r&$IAZ)|Qx z=AZncRH0PRbo%spKw}Pk#uQfrc(+1d7#bq4%7T3r)EvI_SBB_g4z+82Uco|Zi1Qm5 znaStoi{Jyk2+BSZBXF7i^Av;+hxu83JWO$(EGYX)dLmz083Ghn==IakjR=^zd-u*2=#Y*CRTZ@rQF@Hgqn!@_ zwgu(k9T%HRK}T+H={#6z&?a%Ca%Fdk$sid?xsb_ZuclI|w&Fk(Twp91Vm3y4}=2b*47f zj^^|EhS}BTf~Lo~>#n3SVj<6+{jz*^%0m+Qd*8ndAz+z$sAuZieLK=7?j}(r`z|Pe zg`E86H=w`2-_`s0dbR`>HefzQY30Pg;NiuU_uu~yAK1~p(=?o559&9=!znmRg0>64 zH=8{rU$f7B>E$26+i(930-zVm8kglVH?0XO=g<=s?sFzFz^Py7EQA*+sQC2ovdG732njU8{ zZZEt+miAZRc$rKdf-epqm&e&(t`v%7iKk>H&XIL?B-^zx4zEUZu{!2SFAhU++;vJx~KfS^=4rRx7J6Z`5)wI^4>`GZw@)@@~>EZ$Q1it*6zW00meDBd667i*9Fy14$iJ6FqRSma|JixiSJpn8ApB+=^c z>^SbWNbm;Kxm2fS{2^I-swy>EH2%OGY&wOjtY(D)yM(WPEwHX*Ne+2Lp=7lje^@`o zWP%Ay+qAtDCkR+9J26e%_+xJoCD%)Fg5um(?cU1Qa*%rvVACX&f^uD+OzO3~lrw`? z!N#nz7U&UFuM|PchfYxTe-|%KIKDm%LYg2(W}+jR>~$eTFDk!dsX1JN@}pH?;k4<& z!-ulN6nytyQW9cwoO;PZ58Ik}CvCXf3wNZbArX_2%XXp_gc(hQXL%wwPqNvQEw3|^ zdcNLl+p)l^nc!<9E!neT<=4w1ERDC%J;*t4!TTlf- z$!vUBVXppY1z*FZ=kRdK@|r@Ss76q^?MAG2FH>#x-N3+MBz&K&GJCEHI}(gM*)jUb?=ouTQQdxpZmTGLW%|zyHHL{Pbs6!EeYii<%buLj6*l zn%CsTVzGF+prQn23w!O=Us_tDX<6+A6)rp3I#ob|PA@LL(XbszZOF^jBM3@Tr&fy< zSjcw78YL ztU|u_%FhE?%}fSpZ=nog^a5R}tZM4SO3kiP5DRT(K!WXsw%f-oIFZRt(e#M_p^``@ z^{rXXj~W6J`$L`<<^q7DI?smo^n9Fz{3rZ{hIf!_?73B!YqB?qZEeK2HSfNCMLd9} zjqc`}Z%NcuD@Q`YyH;W{wu0gl%boW+(r;qaw)n9XlxIX?9|?@Q_9 zV@qN$?1Ezhi-{OJo^!B4R!bkcZ>5|{IunzLw7OvB-|rO%o*Mjs`lXMm>qz}?M288U z8eUwy?Km%Y)l%jLN+MCdwO3q#o1t#vDsAEh%Em_Y4D|rJZkE(7JT2^2Os1_#kFC9x zmq5`i;L5Q{+*`l314c%(6PhqC)0~P9y96HQ)Z}V z(=y7pqFDUbdh+m7AERhE3SA8QOlk&QySsSj*U$l;ED0}f#O4x-&B<;Y`Ya^%HD}!J z!f1tOGx72kao(D!3-)?qKUqP%z-wc@{)r3k_pzry1%{Z!5N7~_#AN>uKH&RLSHuof z#zBblVcHefsyXZh%I^Ib85ti!tV{uKu%&Xfw1ASpY8FC6U8lnQM!Pwt6O`Xf!&V!K zS5k6=S3mqM2}thdMp>x}u039l&Ia{kRLuy<{d3lsD mhtDD3myXftoZ{i^g#QEnBfz;cm4M3t0000 Date: Tue, 12 Aug 2025 22:07:01 +0900 Subject: [PATCH 03/16] =?UTF-8?q?REFACTOR:=20=EB=A3=A8=ED=8B=B4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1/=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EC=9E=90=EC=9D=B8=20UI=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writeroutine/WriteRoutineScreen.kt | 390 ++++++++---------- .../writeroutine/WriteRoutineViewModel.kt | 5 - .../component/atom/namefield/NameField.kt | 74 ++-- .../component/atom/selectcell/SelectCell.kt | 67 ++- .../atom/tooltipbutton/TooltipButton.kt | 131 ------ .../tooltipbutton/model/TooltipDirection.kt | 5 - .../model/TooltipPositionProvider.kt | 38 -- .../WriteRoutineButton.kt} | 41 +- .../expandablecontent/ExpandableContent.kt | 161 ++++++++ .../block/labeledcheckbox/LabeledCheckBox.kt | 14 +- .../routinedetailrow/RoutineDetailRow.kt | 60 +++ .../block/subroutinefield/SubRoutineField.kt | 86 ++++ .../component/template/Preview.kt | 17 - .../DatePickerBottomSheet.kt | 240 +++++++++++ .../model/CalendarUtils.kt | 60 +++ .../TimePickerBottomSheet.kt | 16 +- .../presentation/writeroutine/model/Date.kt | 19 + .../model/mvi/WriteRoutineIntent.kt | 1 - .../model/mvi/WriteRoutineState.kt | 22 +- 19 files changed, 987 insertions(+), 460 deletions(-) delete mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/TooltipButton.kt delete mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipDirection.kt delete mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipPositionProvider.kt rename presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/{strokebutton/StrokeButton.kt => writeroutinebutton/WriteRoutineButton.kt} (68%) create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt delete mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/Preview.kt create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt rename presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/{ => timepickerbottomsheet}/TimePickerBottomSheet.kt (91%) create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt index cd49a39b..0404a993 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt @@ -1,6 +1,8 @@ package com.threegap.bitnagil.presentation.writeroutine +import ExpandableContent import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -17,7 +19,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -28,19 +29,20 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R -import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar import com.threegap.bitnagil.presentation.common.flow.collectAsEffect import com.threegap.bitnagil.presentation.writeroutine.component.atom.namefield.NameField import com.threegap.bitnagil.presentation.writeroutine.component.atom.selectcell.SelectCell -import com.threegap.bitnagil.presentation.writeroutine.component.atom.strokebutton.StrokeButton -import com.threegap.bitnagil.presentation.writeroutine.component.atom.tooltipbutton.TooltipButton +import com.threegap.bitnagil.presentation.writeroutine.component.atom.writeroutinebutton.WriteRoutineButton import com.threegap.bitnagil.presentation.writeroutine.component.block.labeledcheckbox.LabeledCheckBox -import com.threegap.bitnagil.presentation.writeroutine.component.template.TimePickerBottomSheet +import com.threegap.bitnagil.presentation.writeroutine.component.block.routinedetailrow.RoutineDetailRow +import com.threegap.bitnagil.presentation.writeroutine.component.block.subroutinefield.SubRoutineField +import com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet.DatePickerBottomSheet +import com.threegap.bitnagil.presentation.writeroutine.component.template.timepickerbottomsheet.TimePickerBottomSheet +import com.threegap.bitnagil.presentation.writeroutine.model.Date import com.threegap.bitnagil.presentation.writeroutine.model.Day import com.threegap.bitnagil.presentation.writeroutine.model.RepeatType -import com.threegap.bitnagil.presentation.writeroutine.model.SelectableDay import com.threegap.bitnagil.presentation.writeroutine.model.Time import com.threegap.bitnagil.presentation.writeroutine.model.WriteRoutineType import com.threegap.bitnagil.presentation.writeroutine.model.mvi.WriteRoutineSideEffect @@ -71,6 +73,24 @@ fun WriteRoutineScreenContainer( ) } + if (state.showStartDatePickerBottomSheet) { + DatePickerBottomSheet( + modifier = Modifier.fillMaxWidth(), + onDateSelected = {}, + date = state.startDate ?: Date.now(), + onDismiss = {}, + ) + } + + if (state.showEndDatePickerBottomSheet) { + DatePickerBottomSheet( + modifier = Modifier.fillMaxWidth(), + onDateSelected = {}, + date = state.endDate ?: Date.now(), + onDismiss = {}, + ) + } + WriteRoutineScreen( state = state, setRoutineName = viewModel::setRoutineName, @@ -119,213 +139,198 @@ private fun WriteRoutineScreen( Column( modifier = Modifier - .padding(start = 16.dp, end = 16.dp, top = 32.dp) + .padding(start = 16.dp, end = 16.dp) .weight(1f) .verticalScroll(state = scrollState), ) { - Column { - Row { - Text( - "루틴 이름", - style = BitnagilTheme.typography.body1SemiBold, - ) - - Spacer(modifier = Modifier.width(2.dp)) - - Text( - "*", - style = BitnagilTheme.typography.body1SemiBold.copy(color = BitnagilTheme.colors.error), - ) - } + Spacer(modifier = Modifier.height(50.dp)) - Spacer(modifier = Modifier.height(14.dp)) - - NameField( - value = state.routineName, - onValueChange = setRoutineName, - placeholder = "ex) 아침에 개운하게 일어나기", - onClickRemove = null, - ) - } + NameField( + value = state.routineName, + onValueChange = setRoutineName, + placeholder = "ex) 아침에 개운하게 일어나기", + onClickRemove = null, + ) Spacer(modifier = Modifier.height(40.dp)) - Column { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - "세부 루틴", - color = BitnagilTheme.colors.coolGray10, - style = BitnagilTheme.typography.body1SemiBold, - ) - - Spacer(modifier = Modifier.width(2.dp)) + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + ExpandableContent( + expand = state.subRoutineUiExpanded, + required = false, + iconResourceId = R.drawable.img_subroutines, + title = "세부루틴", + placeHolder = "ex) 일어나자마자 이불 개기", + valueText = "", + onClick = { - TooltipButton("어려운 루틴이라면 단계별로 나눠보세요!") - } - - Spacer(modifier = Modifier.height(14.dp)) - - StrokeButton.Custom( - isSelected = false, - onClick = addSubRoutine, - enabled = state.addSubRoutineButtonEnabled, + } ) { - Row( - modifier = Modifier.height(52.dp).fillMaxWidth().padding(start = 24.dp, end = 16.dp), - verticalAlignment = Alignment.CenterVertically, + Column( + modifier = Modifier.fillMaxWidth().padding(horizontal = 18.dp, vertical = 20.dp), + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.End ) { - BitnagilIcon( - id = R.drawable.ic_plus, - tint = BitnagilTheme.colors.navy400, + SubRoutineField( + resourceId = R.drawable.img_circle_1, + placeHolder = getSubRoutinePlaceHolder(0), + value = state.subRoutineNames.getOrNull(0) ?: "", + onValueChange = { setSubRoutineName(0, it) }, + enabled = true, ) - Spacer(modifier = Modifier.width(10.dp)) + SubRoutineField( + resourceId = R.drawable.img_circle_2, + placeHolder = getSubRoutinePlaceHolder(1), + value = state.subRoutineNames.getOrNull(1) ?: "", + onValueChange = { setSubRoutineName(1, it) }, + enabled = true, + ) - Text( - "세부 루틴 추가", - style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray40), + SubRoutineField( + resourceId = R.drawable.img_circle_3, + placeHolder = getSubRoutinePlaceHolder(2), + value = state.subRoutineNames.getOrNull(2) ?: "", + onValueChange = { setSubRoutineName(2, it) }, + enabled = true, ) - } - } - state.subRoutineNames.forEachIndexed { index, subRoutineName -> - Spacer(modifier = Modifier.height(10.dp)) - NameField( - value = subRoutineName, - onValueChange = { - setSubRoutineName(index, it) - }, - placeholder = getSubRoutinePlaceHolder(index), - onClickRemove = { - removeSubRoutine(index) - }, - ) + LabeledCheckBox( + label = "세부 루틴 설정 안함", + checked = true, + onClick = { + addSubRoutine() + } + ) + } } - } - Spacer(modifier = Modifier.height(40.dp)) + ExpandableContent( + expand = state.repeatDaysUiExpanded, + required = false, + iconResourceId = R.drawable.img_repeat_days, + title = "반복 요일", + placeHolder = "ex) 매주 월,화,수.목,금", + valueText = "", + onClick = { - Column { - Row( - verticalAlignment = Alignment.CenterVertically, + } ) { - Text( - "루틴 반복", - style = BitnagilTheme.typography.body1SemiBold, - ) - - Spacer(modifier = Modifier.width(2.dp)) - - TooltipButton( - "선택하지 않을 경우, 당일 루틴으로만 자동 설정돼요.", - ) - } - - Spacer(modifier = Modifier.height(14.dp)) - - Row { - StrokeButton.Text( - modifier = Modifier.height(52.dp).weight(1f), - text = "매일", - isSelected = state.repeatType == RepeatType.DAILY, - onClick = { - selectRepeatTime(RepeatType.DAILY) - }, - ) - - Spacer(modifier = Modifier.width(12.dp)) - - StrokeButton.Text( - modifier = Modifier.height(52.dp).weight(1f), - text = "요일 선택", - isSelected = state.repeatType == RepeatType.DAY, - onClick = { - selectRepeatTime(RepeatType.DAY) - }, - ) - } + Column( + modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp) + ) { + Row { + WriteRoutineButton.Text( + modifier = Modifier.height(52.dp).weight(1f), + text = "매일", + isSelected = state.repeatType == RepeatType.DAILY, + onClick = { + selectRepeatTime(RepeatType.DAILY) + }, + ) - if (state.repeatType == RepeatType.DAY) { - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.width(12.dp)) - Row { - state.repeatDays.forEachIndexed { index, selectableDay -> - SelectCell( - modifier = Modifier.weight(1f), - text = selectableDay.day.text, + WriteRoutineButton.Text( + modifier = Modifier.height(52.dp).weight(1f), + text = "요일 선택", + isSelected = state.repeatType == RepeatType.DAY, onClick = { - selectDay(selectableDay.day) + selectRepeatTime(RepeatType.DAY) }, - selected = selectableDay.selected, ) - if (index != state.repeatDays.lastIndex) { - Spacer(modifier = Modifier.width(8.dp)) + } + + if (state.repeatType == RepeatType.DAY) { + Spacer(modifier = Modifier.height(16.dp)) + + Row { + state.repeatDays.forEachIndexed { index, selectableDay -> + SelectCell( + modifier = Modifier.weight(1f), + text = selectableDay.day.text, + onClick = { + selectDay(selectableDay.day) + }, + selected = selectableDay.selected, + ) + if (index != state.repeatDays.lastIndex) { + Spacer(modifier = Modifier.width(8.dp)) + } + } } } } } - } - - Spacer(modifier = Modifier.height(40.dp)) - - Column { - Row { - Text( - "시작 시간", - style = BitnagilTheme.typography.body1SemiBold, - ) - Spacer(modifier = Modifier.width(2.dp)) + ExpandableContent( + expand = state.periodUiExpanded, + required = false, + iconResourceId = R.drawable.img_routine_period, + title = "목표 기간", + placeHolder = "ex) 25.08.06 - 25.08.06", + valueText = "", + onClick = { - Text( - "*", - style = BitnagilTheme.typography.body1SemiBold.copy(color = BitnagilTheme.colors.error), - ) - - Spacer(modifier = Modifier.weight(1f)) + } + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + RoutineDetailRow( + title = "시작일", + placeHolder = "눌러서 선택", + value = "", + onClick = {}, + ) - LabeledCheckBox( - label = "하루종일", - checked = state.selectAllTime, - onClick = selectAllTime, - ) + RoutineDetailRow( + title = "종료일", + placeHolder = "눌러서 선택", + value = "", + onClick = {}, + ) + } } - Spacer(modifier = Modifier.height(14.dp)) + ExpandableContent( + expand = state.startTimeUiExpanded, + required = true, + iconResourceId = R.drawable.img_start_time, + title = "시간", + placeHolder = "ex) 오전 9:40부터 시작", + valueText = "", + onClick = { - StrokeButton.Custom( - isSelected = false, - onClick = showTimePickerBottomSheet, + } ) { - Row( - modifier = Modifier.height(52.dp).fillMaxWidth().padding(start = 24.dp, end = 20.dp), - verticalAlignment = Alignment.CenterVertically, + Column( + modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), + horizontalAlignment = Alignment.End ) { - Text( - if (state.startTime == null) { - "시간 선택" - } else { - "${state.startTime.hour}".padStart(2, '0') + ":" + "${state.startTime.minute}".padStart( - 2, - '0', - ) - }, - style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray40), + RoutineDetailRow( + title = "시작 시간", + placeHolder = "눌러서 선택", + value = "", + onClick = {}, ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(20.dp)) + + LabeledCheckBox( + label = "하루 종일", + checked = state.selectAllTime, + onClick = { - BitnagilIcon( - id = R.drawable.ic_down_arrow, - tint = BitnagilTheme.colors.navy400, + } ) } + } } - - Spacer(modifier = Modifier.height(54.dp)) } BitnagilTextButton( @@ -353,50 +358,7 @@ private fun getSubRoutinePlaceHolder(index: Int): String { fun WriteRoutineScreenPreview() { BitnagilTheme { WriteRoutineScreen( - state = WriteRoutineState( - routineName = "이름", - subRoutineNames = listOf( - "1", - "2", - ), - repeatType = RepeatType.DAILY, - repeatDays = listOf( - SelectableDay( - day = Day.MON, - selected = true, - ), - SelectableDay( - day = Day.TUE, - selected = false, - ), - SelectableDay( - day = Day.WED, - selected = false, - ), - SelectableDay( - day = Day.THU, - selected = false, - ), - SelectableDay( - day = Day.FRI, - selected = false, - ), - SelectableDay( - day = Day.SAT, - selected = false, - ), - SelectableDay( - day = Day.SUN, - selected = false, - ), - ), - periodWeek = null, - startTime = null, - selectAllTime = false, - loading = false, - showTimePickerBottomSheet = false, - writeRoutineType = WriteRoutineType.ADD, - ), + state = WriteRoutineState.Init.copy(periodUiExpanded = true, startTimeUiExpanded = true), setRoutineName = {}, setSubRoutineName = { _, _ -> }, addSubRoutine = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index 72c9ef82..bcd75e14 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -155,11 +155,6 @@ class WriteRoutineViewModel @AssistedInject constructor( }, ) } - is WriteRoutineIntent.SetPeriodWeek -> { - return state.copy( - periodWeek = intent.periodWeek, - ) - } is WriteRoutineIntent.SetRepeatType -> { return state.copy( repeatType = intent.repeatType, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt index d22be270..45839d3e 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt @@ -1,17 +1,20 @@ package com.threegap.bitnagil.presentation.writeroutine.component.atom.namefield -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R @@ -30,37 +33,58 @@ fun NameField( value = value, onValueChange = onValueChange, modifier = modifier - .background(BitnagilTheme.colors.coolGray99, RoundedCornerShape(12.dp)) - .fillMaxWidth() - .height(52.dp), + .fillMaxWidth(), singleLine = true, textStyle = BitnagilTheme.typography.body2SemiBold, decorationBox = { innerTextField -> - Row( - modifier = Modifier.padding(start = 24.dp, end = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Box(modifier = Modifier.weight(1f)) { - if (value.isEmpty()) { - Text( - placeholder, - style = BitnagilTheme.typography.body2SemiBold.copy(color = BitnagilTheme.colors.coolGray90), - ) + Column { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { + if (value.isEmpty()) { + Text( + placeholder, + style = BitnagilTheme.typography.title3SemiBold.copy(color = BitnagilTheme.colors.coolGray90), + ) + } + innerTextField() } - innerTextField() - } - if (onClickRemove != null) { - Box( - modifier = Modifier.clickableWithoutRipple(onClick = onClickRemove), - ) { - BitnagilIcon( - id = R.drawable.ic_close, - tint = BitnagilTheme.colors.coolGray80, - ) + if (onClickRemove != null) { + Box( + modifier = Modifier.clickableWithoutRipple(onClick = onClickRemove), + contentAlignment = Alignment.Center + ) { + BitnagilIcon( + id = R.drawable.ic_close_circle, + tint = null, + modifier = Modifier.size(24.dp).padding(top = 4.dp, bottom = 4.dp, start = 8.dp) + ) + } } } + + Spacer(modifier = Modifier.height(12.dp)) + + HorizontalDivider( + thickness = 2.dp, + modifier = Modifier.height(2.dp), + color = BitnagilTheme.colors.coolGray90, + ) } }, ) } + +@Composable +@Preview(showBackground = true, widthDp = 300, heightDp = 300) +fun NameFieldPreview() { + BitnagilTheme{ + NameField( + value = "value", + onValueChange = {}, + onClickRemove = {}, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt index 5659e162..87e800bc 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt @@ -1,14 +1,17 @@ package com.threegap.bitnagil.presentation.writeroutine.component.atom.selectcell import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple @@ -24,16 +27,74 @@ fun SelectCell( modifier = modifier .height(48.dp) .background( - color = if (selected) BitnagilTheme.colors.lightBlue100 else Color.Transparent, + color = if (selected) BitnagilTheme.colors.orange500 else BitnagilTheme.colors.white, shape = RoundedCornerShape(12.dp), ) + .border( + width = 1.dp, + shape = RoundedCornerShape(12.dp), + color = if (selected) BitnagilTheme.colors.orange500 else BitnagilTheme.colors.coolGray96, + ) .clickableWithoutRipple(onClick = onClick), contentAlignment = Alignment.Center, ) { Text( text = text, - color = BitnagilTheme.colors.navy400, + color = if (selected) BitnagilTheme.colors.white else BitnagilTheme.colors.coolGray30, style = BitnagilTheme.typography.body2Medium, ) } } + +@Composable +@Preview(showBackground = true, widthDp = 300, heightDp = 300) +private fun SelectCellPreview() { + BitnagilTheme { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + SelectCell( + text = "월", + selected = true, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SelectCell( + text = "화", + selected = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SelectCell( + text = "수", + selected = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SelectCell( + text = "목", + selected = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SelectCell( + text = "금", + selected = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SelectCell( + text = "토", + selected = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SelectCell( + text = "일", + selected = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/TooltipButton.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/TooltipButton.kt deleted file mode 100644 index 28ce9368..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/TooltipButton.kt +++ /dev/null @@ -1,131 +0,0 @@ -package com.threegap.bitnagil.presentation.writeroutine.component.atom.tooltipbutton - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TooltipBox -import androidx.compose.material3.rememberTooltipState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.dp -import com.threegap.bitnagil.designsystem.BitnagilTheme -import com.threegap.bitnagil.designsystem.R -import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon -import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple -import com.threegap.bitnagil.presentation.writeroutine.component.atom.tooltipbutton.model.TooltipDirection -import com.threegap.bitnagil.presentation.writeroutine.component.atom.tooltipbutton.model.TooltipPositionProvider -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun TooltipButton( - tooltipText: String, - tooltipDirection: TooltipDirection = TooltipDirection.Left, -) { - val tooltipState = rememberTooltipState() - val coroutineScope = rememberCoroutineScope() - val tooltipBoxColor = BitnagilTheme.colors.navy400 - var buttonSize by remember { mutableStateOf(IntSize.Zero) } - val tooltipPositionX = with(LocalDensity.current) { 16.dp.toPx().toInt() } - - TooltipBox( - positionProvider = remember { - TooltipPositionProvider( - direction = tooltipDirection, - addPositionX = tooltipPositionX, - ) - }, - tooltip = { - Column( - modifier = Modifier.padding(bottom = 2.dp), - ) { - Text( - tooltipText, - modifier = Modifier - .background( - color = BitnagilTheme.colors.navy400, - shape = RoundedCornerShape(8.dp), - ) - .clickable { - tooltipState.dismiss() - } - .padding(10.dp), - style = BitnagilTheme.typography.caption1Medium.copy(color = BitnagilTheme.colors.white), - ) - - Canvas( - modifier = Modifier - .height(8.dp) - .width(16.dp) - .offset { - IntOffset(x = tooltipPositionX + (buttonSize.width / 2) - 8.dp.toPx().toInt(), y = 0) - }, - ) { - drawTooltipArrow(this, tooltipBoxColor) - } - } - }, - state = tooltipState, - ) { - BitnagilIcon( - id = R.drawable.ic_tooltip, - tint = BitnagilTheme.colors.navy200, - modifier = Modifier - .clickableWithoutRipple { - coroutineScope.launch { - tooltipState.show() - } - } - .onGloballyPositioned { coordinate -> - buttonSize = coordinate.size - }, - ) - } -} - -private fun DrawScope.drawTooltipArrow(scope: DrawScope, color: Color) { - val path = Path().apply { - moveTo(0f, 0f) - lineTo(size.width / 2, size.height) - lineTo(size.width, 0f) - close() - } - scope.drawPath( - path = path, - color = color, - ) -} - -@Preview(showBackground = true, widthDp = 300, heightDp = 300) -@Composable -fun ToolTipButtonPreview() { - Box( - contentAlignment = Alignment.Center, - ) { - TooltipButton(tooltipText = "이것은 툴팁입니다.") - } -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipDirection.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipDirection.kt deleted file mode 100644 index 10e0eda2..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipDirection.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.threegap.bitnagil.presentation.writeroutine.component.atom.tooltipbutton.model - -enum class TooltipDirection { - Left, Center, Right -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipPositionProvider.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipPositionProvider.kt deleted file mode 100644 index d3eacccb..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/tooltipbutton/model/TooltipPositionProvider.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.threegap.bitnagil.presentation.writeroutine.component.atom.tooltipbutton.model - -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntRect -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.window.PopupPositionProvider - -class TooltipPositionProvider( - private val direction: TooltipDirection, - private val addPositionX: Int = 0, -) : PopupPositionProvider { - override fun calculatePosition( - anchorBounds: IntRect, - windowSize: IntSize, - layoutDirection: LayoutDirection, - popupContentSize: IntSize, - ): IntOffset { - val x: Int - val y: Int - - when (direction) { - TooltipDirection.Center -> { - x = anchorBounds.left + (anchorBounds.width / 2) - (popupContentSize.width / 2) - y = anchorBounds.top - popupContentSize.height - } - TooltipDirection.Left -> { - x = anchorBounds.left - addPositionX - y = anchorBounds.top - popupContentSize.height - } - TooltipDirection.Right -> { - x = anchorBounds.right + addPositionX - y = anchorBounds.top - popupContentSize.height - } - } - return IntOffset(x, y) - } -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/strokebutton/StrokeButton.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/writeroutinebutton/WriteRoutineButton.kt similarity index 68% rename from presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/strokebutton/StrokeButton.kt rename to presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/writeroutinebutton/WriteRoutineButton.kt index 270f6e63..76a41675 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/strokebutton/StrokeButton.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/writeroutinebutton/WriteRoutineButton.kt @@ -1,7 +1,9 @@ -package com.threegap.bitnagil.presentation.writeroutine.component.atom.strokebutton +package com.threegap.bitnagil.presentation.writeroutine.component.atom.writeroutinebutton +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text @@ -9,11 +11,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple -class StrokeButton { +class WriteRoutineButton { companion object { @Composable fun Text( @@ -23,9 +26,9 @@ class StrokeButton { modifier: Modifier = Modifier, enabled: Boolean = true, ) { - val textColor = if (isSelected) BitnagilTheme.colors.navy500 else BitnagilTheme.colors.navy100 + val textColor = if (isSelected) BitnagilTheme.colors.white else BitnagilTheme.colors.coolGray30 - StrokeButtonFrame( + WriteRoutineButtonFrame( modifier = modifier, isSelected = isSelected, onClick = onClick, @@ -51,7 +54,7 @@ class StrokeButton { enabled: Boolean = true, content: @Composable () -> Unit, ) { - StrokeButtonFrame( + WriteRoutineButtonFrame( modifier = modifier, isSelected = isSelected, onClick = onClick, @@ -64,18 +67,23 @@ class StrokeButton { } @Composable -private fun StrokeButtonFrame( +private fun WriteRoutineButtonFrame( isSelected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, content: @Composable () -> Unit, ) { - val borderColor = if (isSelected) BitnagilTheme.colors.navy500 else BitnagilTheme.colors.navy100 + val borderColor = if (isSelected) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray97 + val backgroundColor = if (isSelected) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.white Box( modifier = modifier .clip(RoundedCornerShape(12.dp)) + .background( + color = backgroundColor, + shape = RoundedCornerShape(12.dp), + ) .border( width = 1.dp, color = borderColor, @@ -87,3 +95,22 @@ private fun StrokeButtonFrame( content() } } + +@Composable +@Preview(showBackground = true, widthDp = 300, heightDp = 300) +private fun WriteRoutineButtonPreview() { + BitnagilTheme { + Row { + WriteRoutineButton.Text( + text = "월", + isSelected = true, + onClick = {}, + ) + WriteRoutineButton.Text( + text = "화", + isSelected = false, + onClick = {}, + ) + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt new file mode 100644 index 00000000..8e9f53bc --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt @@ -0,0 +1,161 @@ +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon +import com.threegap.bitnagil.designsystem.component.atom.BitnagilIconButton + +@Composable +fun ExpandableContent( + expand: Boolean, + required: Boolean, + iconResourceId: Int, + title: String, + placeHolder: String, + valueText: String?, + onClick: () -> Unit, + content: @Composable () -> Unit, +) { + val showValueText = !valueText.isNullOrEmpty() + val mainTextStyle = BitnagilTheme.typography.body1SemiBold.copy(color = BitnagilTheme.colors.coolGray10) + val subTextStyle = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray70) + + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(color = BitnagilTheme.colors.coolGray99) + .animateContentSize(), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 18.dp, end = 12.dp, top = 18.dp, bottom = 18.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(iconResourceId), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + + Spacer(modifier = Modifier.width(15.dp)) + + Column(modifier = Modifier.weight(1f)) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = title, + style = if (showValueText && !expand) subTextStyle else mainTextStyle, + ) + + if (required) + BitnagilIcon( + id = R.drawable.ic_routine_success, + tint = null, + modifier = Modifier.size(12.dp), + ) + } + if (!expand) { + Text( + text = if (showValueText) valueText ?: placeHolder else placeHolder, + maxLines = 1, + style = if (showValueText) mainTextStyle else subTextStyle, + ) + } + } + + BitnagilIconButton( + id = if (expand) R.drawable.ic_up_arrow_20 else R.drawable.ic_down_arrow_20, + tint = BitnagilTheme.colors.coolGray30, + modifier = Modifier + .size(36.dp) + .padding(8.dp), + onClick = onClick, + ) + } + + AnimatedVisibility(visible = expand) { + Column{ + HorizontalDivider( + modifier = Modifier.fillMaxWidth().padding(horizontal = 18.dp), + color = BitnagilTheme.colors.coolGray96, + thickness = 1.dp, + ) + + content() + } + } + } + +} + +@Preview(heightDp = 300) +@Composable +fun ExpandableContentPreview() { + var isExpanded by remember { mutableStateOf(true) } + var isChecked by remember { mutableStateOf(false) } + + Column { + ExpandableContent( + expand = isExpanded, + iconResourceId = R.drawable.img_subroutines, + title = "세부루틴", + placeHolder = "세부루틴을 설정해주세요.", + valueText = "", + onClick = { isExpanded = !isExpanded }, + required = true, + content = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clickable { isChecked = !isChecked }, + ) { + Checkbox( + checked = isChecked, + onCheckedChange = { isChecked = it }, + colors = CheckboxDefaults.colors( + checkedColor = Color.Gray, + uncheckedColor = Color.Gray, + ), + ) + Text( + text = "세부루틴 설정 안 함", + color = Color.Gray, + fontSize = 14.sp, + ) + } + }, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/labeledcheckbox/LabeledCheckBox.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/labeledcheckbox/LabeledCheckBox.kt index 573a6172..496c84b5 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/labeledcheckbox/LabeledCheckBox.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/labeledcheckbox/LabeledCheckBox.kt @@ -29,17 +29,21 @@ fun LabeledCheckBox( modifier = modifier.clickableWithoutRipple(onClick = onClick), verticalAlignment = Alignment.CenterVertically, ) { + Text(label, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30)) + + Spacer(modifier = Modifier.width(6.dp)) + Box( modifier = Modifier - .size(20.dp) + .size(18.dp) .background( shape = RoundedCornerShape(4.dp), - color = if (checked) BitnagilTheme.colors.navy500 else BitnagilTheme.colors.coolGray99, + color = if (checked) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray99, ) .border( width = 1.dp, shape = RoundedCornerShape(4.dp), - color = if (checked) BitnagilTheme.colors.navy500 else BitnagilTheme.colors.coolGray95, + color = if (checked) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray95, ), contentAlignment = Alignment.Center, ) { @@ -50,9 +54,5 @@ fun LabeledCheckBox( ) } } - - Spacer(modifier = Modifier.width(6.dp)) - - Text(label, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.navy400)) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt new file mode 100644 index 00000000..05101bf9 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt @@ -0,0 +1,60 @@ +package com.threegap.bitnagil.presentation.writeroutine.component.block.routinedetailrow + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.presentation.writeroutine.component.atom.writeroutinebutton.WriteRoutineButton + +@Composable +fun RoutineDetailRow( + title: String, + placeHolder: String, + value: String, + onClick: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(title, style = BitnagilTheme.typography.body2SemiBold.copy(color = BitnagilTheme.colors.coolGray30)) + + WriteRoutineButton.Custom( + modifier = Modifier.width(120.dp), + onClick = onClick, + isSelected = false, + ) { + Box( + modifier = Modifier.padding(vertical = 10.dp) + ) { + if (value.isEmpty()) + Text(placeHolder, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90)) + + Text(value, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30)) + } + } + } +} + +@Composable +@Preview(showBackground = true, widthDp = 300, heightDp = 300) +fun RoutineDetailRowPreview() { + BitnagilTheme { + RoutineDetailRow( + title = "시작일", + placeHolder = "눌러서 선택", + value = "", + onClick = {}, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt new file mode 100644 index 00000000..fd9cc6b3 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt @@ -0,0 +1,86 @@ +package com.threegap.bitnagil.presentation.writeroutine.component.block.subroutinefield + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme + +@Composable +fun SubRoutineField( + resourceId: Int, + placeHolder: String, + value: String, + onValueChange: (String) -> Unit, + enabled: Boolean, +) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(resourceId), + contentDescription = null, + modifier = Modifier.size(21.dp), + ) + + Spacer(modifier = Modifier.width(12.dp)) + + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier.weight(1f), + singleLine = true, + enabled = enabled, + textStyle = BitnagilTheme.typography.body2Medium.copy(color = if (enabled) BitnagilTheme.colors.coolGray30 else BitnagilTheme.colors.coolGray90), + decorationBox = { innerTextField -> + Column { + Box { + if (value.isEmpty()) + Text( + text = placeHolder, + style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90), + ) + innerTextField() + } + + Spacer(modifier = Modifier.height(6.dp)) + + HorizontalDivider( + thickness = 1.dp, + modifier = Modifier.height(1.dp), + color = BitnagilTheme.colors.coolGray90, + ) + } + } + ) + } +} + +@Composable +@Preview(showBackground = true, widthDp = 300, heightDp = 300) +fun NameFieldPreview() { + BitnagilTheme{ + SubRoutineField( + resourceId = com.threegap.bitnagil.designsystem.R.drawable.img_circle_1, + placeHolder = "세부루틴을 설정해주세요.", + value = "TEXT", + onValueChange = {}, + enabled = true + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/Preview.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/Preview.kt deleted file mode 100644 index e1f2fcdf..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/Preview.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.threegap.bitnagil.presentation.writeroutine.component.template - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview - -@Preview -@Composable -fun TimePickerBottomSheetContentPreview() { - TimePickerBottomSheetContent( - modifier = Modifier.fillMaxWidth(), - onTimeSelected = { _, _ -> }, - hour = 12, - minute = 30, - ) -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt new file mode 100644 index 00000000..e2685c1a --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt @@ -0,0 +1,240 @@ +package com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.component.atom.BitnagilIconButton +import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet.model.CalendarUtils +import com.threegap.bitnagil.presentation.writeroutine.model.Date +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DatePickerBottomSheet( + modifier: Modifier = Modifier, + onDateSelected: (Date) -> Unit, + date: Date, + onDismiss: () -> Unit, +) { + val sheetState = rememberModalBottomSheetState() + val coroutineScope = rememberCoroutineScope() + + ModalBottomSheet( + sheetState = sheetState, + onDismissRequest = onDismiss, + containerColor = BitnagilTheme.colors.white, + ){ + DatePickerBottomSheetContent( + modifier = modifier, + onDateSelected = { date -> + onDateSelected(date) + coroutineScope + .launch { sheetState.hide() } + .invokeOnCompletion { + if (!sheetState.isVisible) { + onDismiss() + } + } + }, + initDate = date, + ) + } +} + +@Composable +private fun DatePickerBottomSheetContent( + modifier: Modifier = Modifier, + onDateSelected: (Date) -> Unit, + initDate: Date, +) { + var currentYear by remember { mutableIntStateOf(initDate.year) } + var currentMonth by remember { mutableIntStateOf(initDate.month) } + var selectedDate by remember { mutableStateOf(initDate) } + + val lastDaysOfPrevMonth = remember(initDate) { + CalendarUtils.lastDaysOfPrevMonth(initDate.year, initDate.month) + } + val firstDaysOfNextMonth = remember(currentYear, currentMonth) { + CalendarUtils.firstDaysOfNextMonth(initDate.year, initDate.month) + } + val currentDaysOfMonth = remember(currentYear, currentMonth) { + CalendarUtils.getDayAmountOfMonth(initDate.year, initDate.month) + } + + Column( + modifier = modifier.background( + color = BitnagilTheme.colors.white, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("${currentYear}년 ${currentMonth}월", style = BitnagilTheme.typography.title2Bold.copy(color = BitnagilTheme.colors.coolGray10)) + + Row { + BitnagilIconButton( + id = R.drawable.ic_back_arrow_20, + onClick = { + if (currentMonth == 1) { + currentMonth = 12 + currentYear -= 1 + } else { + currentMonth -= 1 + } + }, + modifier = Modifier + .size(48.dp), + enabled = true, + paddingValues = PaddingValues(14.dp), + tint = BitnagilTheme.colors.coolGray10, + ) + + BitnagilIconButton( + id = R.drawable.ic_right_arrow_20, + onClick = { + if (currentMonth == 12) { + currentMonth = 1 + currentYear += 1 + } else { + currentMonth += 1 + } + }, + modifier = Modifier + .size(48.dp), + enabled = true, + paddingValues = PaddingValues(14.dp), + tint = BitnagilTheme.colors.coolGray10, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Box( + modifier = Modifier.padding(horizontal = 20.dp) + ){ + LazyVerticalGrid( + columns = GridCells.Fixed(7), + verticalArrangement = Arrangement.spacedBy(2.dp), + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + items(CalendarUtils.dateStringList.size) { index -> + Text( + CalendarUtils.dateStringList[index], + modifier = Modifier.padding(bottom = 16.dp), + style = BitnagilTheme.typography.body2SemiBold.copy(color = BitnagilTheme.colors.coolGray50), + textAlign = TextAlign.Center + ) + } + + itemsIndexed(lastDaysOfPrevMonth) { _, day -> + Box( + modifier = Modifier.aspectRatio(1f), + contentAlignment = Alignment.Center + ) { + Text( + "$day", + style = BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray80), + ) + } + } + + items(currentDaysOfMonth) { index -> + val selected = (selectedDate.year == currentYear) && (selectedDate.month == currentMonth) && (selectedDate.day == index + 1) + Box( + modifier = Modifier.aspectRatio(1f).background( + color = if (selected) { BitnagilTheme.colors.orange50 } else { Color.Transparent }, + shape = if (selected) { RoundedCornerShape(12.dp) } else { RectangleShape } + ), + contentAlignment = Alignment.Center, + ) { + Text( + "${index + 1}", + style = if (selected) { + BitnagilTheme.typography.subtitle1SemiBold.copy(color = BitnagilTheme.colors.orange500) + } else { + BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray10) + }, + ) + } + } + + itemsIndexed(firstDaysOfNextMonth) { _, day -> + Box( + modifier = Modifier.aspectRatio(1f), + contentAlignment = Alignment.Center + ) { + Text( + "$day", + style = BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray80), + ) + } + } + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + BitnagilTextButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 14.dp) + .fillMaxWidth(), + text = "확인", + onClick = { + onDateSelected(selectedDate) + }, + enabled = true, + ) + } +} + +@Preview +@Composable +private fun DatePickerBottomSheetContentPreview(){ + BitnagilTheme{ + DatePickerBottomSheetContent( + modifier = Modifier, + onDateSelected = { _ -> }, + initDate = Date(year = 2025, month = 8, day = 1) + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt new file mode 100644 index 00000000..86607079 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt @@ -0,0 +1,60 @@ +package com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet.model + +import java.util.Calendar + +class CalendarUtils { + companion object { + val dateStringList = listOf( + "일", "월", "화", "수", "목", "금", "토" + ) + + fun getDayAmountOfMonth(year : Int, month : Int) : Int { + return when (month) { + 2 -> { + if (year % 4 != 0 || (year % 100 == 0 && year % 400 != 0)) { 28 } + else { 29 } + } + in listOf(1,3,5,7,8,10,12) -> { 31 } + else -> { 30 } + } + } + + /** + * 달력 상에서 이전달에 해당하는 요일의 리스트를 리턴합니다. + */ + fun lastDaysOfPrevMonth(year: Int, month: Int): List { + val calendar = Calendar.getInstance() + val prevMonthIndex = month - 1 + + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, prevMonthIndex) + calendar.set(Calendar.DAY_OF_MONTH, 1) + + val prevMonth = if (prevMonthIndex == 0) { + 12 + } else { + month - 1 + } + + val startDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) + val amountOfDayOfPrevMonth = getDayAmountOfMonth(year, prevMonth) + + return List(startDayOfWeek - 1) { i -> amountOfDayOfPrevMonth - (startDayOfWeek - 2 - i) } + } + + /** + * 달력 상에서 다음달에 해당하는 요일의 리스트를 리턴합니다. + */ + fun firstDaysOfNextMonth(year : Int, month : Int) : List { + val calendar = Calendar.getInstance() + + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, month) + calendar.set(Calendar.DAY_OF_MONTH, 1) + + val startDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) + + return List(8 - startDayOfWeek) { it + 1 } + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/TimePickerBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt similarity index 91% rename from presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/TimePickerBottomSheet.kt rename to presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt index 86384f8d..0e79fc2e 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/TimePickerBottomSheet.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt @@ -1,4 +1,4 @@ -package com.threegap.bitnagil.presentation.writeroutine.component.template +package com.threegap.bitnagil.presentation.writeroutine.component.template.timepickerbottomsheet import android.view.View import android.widget.TimePicker @@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.threegap.bitnagil.designsystem.BitnagilTheme @@ -60,7 +61,7 @@ fun TimePickerBottomSheet( } @Composable -fun TimePickerBottomSheetContent( +private fun TimePickerBottomSheetContent( modifier: Modifier = Modifier, onTimeSelected: (Int, Int) -> Unit, hour: Int, @@ -117,3 +118,14 @@ fun TimePickerBottomSheetContent( ) } } + +@Preview +@Composable +private fun TimePickerBottomSheetContentPreview() { + TimePickerBottomSheetContent( + modifier = Modifier.fillMaxWidth(), + onTimeSelected = { _, _ -> }, + hour = 12, + minute = 30, + ) +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt new file mode 100644 index 00000000..504660d5 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt @@ -0,0 +1,19 @@ +package com.threegap.bitnagil.presentation.writeroutine.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Date( + val year: Int, + val month: Int, + val day: Int +) : Parcelable { + companion object { + fun now() = Date( + year = java.time.LocalDate.now().year, + month = java.time.LocalDate.now().monthValue, + day = java.time.LocalDate.now().dayOfMonth + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt index c7164c40..cb085399 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt @@ -13,7 +13,6 @@ sealed class WriteRoutineIntent : MviIntent { data class SetSubRoutineName(val index: Int, val name: String) : WriteRoutineIntent() data class SetRepeatType(val repeatType: RepeatType) : WriteRoutineIntent() data class SelectDay(val day: Day) : WriteRoutineIntent() - data class SetPeriodWeek(val periodWeek: Int) : WriteRoutineIntent() data class SetStartTime(val time: Time) : WriteRoutineIntent() data class SetWriteRoutineType(val writeRoutineType: WriteRoutineType) : WriteRoutineIntent() data class SetRoutine(val name: String, val repeatDays: List, val startTime: Time, val subRoutines: List) : WriteRoutineIntent() diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt index 0029cf6a..e47f3349 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt @@ -1,6 +1,7 @@ package com.threegap.bitnagil.presentation.writeroutine.model.mvi import com.threegap.bitnagil.presentation.common.mviviewmodel.MviState +import com.threegap.bitnagil.presentation.writeroutine.model.Date import com.threegap.bitnagil.presentation.writeroutine.model.Day import com.threegap.bitnagil.presentation.writeroutine.model.RepeatType import com.threegap.bitnagil.presentation.writeroutine.model.SelectableDay @@ -14,12 +15,19 @@ data class WriteRoutineState( val subRoutineNames: List, val repeatType: RepeatType?, val repeatDays: List, - val periodWeek: Int?, val startTime: Time?, + val startDate: Date?, + val endDate: Date?, val selectAllTime: Boolean, val loading: Boolean, val showTimePickerBottomSheet: Boolean, + val showStartDatePickerBottomSheet: Boolean, + val showEndDatePickerBottomSheet: Boolean, val writeRoutineType: WriteRoutineType, + val subRoutineUiExpanded: Boolean, + val repeatDaysUiExpanded: Boolean, + val periodUiExpanded: Boolean, + val startTimeUiExpanded: Boolean, ) : MviState { companion object { val Init = WriteRoutineState( @@ -56,12 +64,19 @@ data class WriteRoutineState( selected = false, ), ), - periodWeek = null, startTime = null, selectAllTime = false, loading = false, + showStartDatePickerBottomSheet = false, + showEndDatePickerBottomSheet = false, showTimePickerBottomSheet = false, writeRoutineType = WriteRoutineType.ADD, + subRoutineUiExpanded = false, + repeatDaysUiExpanded = false, + periodUiExpanded = false, + startTimeUiExpanded = false, + startDate = null, + endDate = null, ) } @@ -69,7 +84,4 @@ data class WriteRoutineState( get() = routineName.isNotEmpty() && (repeatType == RepeatType.DAILY || (repeatType == RepeatType.DAY && repeatDays.any { it.selected })) && startTime != null && !loading - - val addSubRoutineButtonEnabled: Boolean - get() = subRoutineNames.size < 3 && !loading } From 490e179f050572db0ff5128dac3cc41ae98308dc Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Wed, 13 Aug 2025 21:28:02 +0900 Subject: [PATCH 04/16] =?UTF-8?q?REFACTOR:=20=EB=A6=AC=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=EB=90=9C=20=EB=A3=A8=ED=8B=B4=20=EC=9E=91=EC=84=B1/?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20ViewModel=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writeroutine/WriteRoutineScreen.kt | 95 +++++----- .../writeroutine/WriteRoutineViewModel.kt | 162 +++++++++++++++--- .../component/atom/selectcell/SelectCell.kt | 2 +- .../expandablecontent/ExpandableContent.kt | 10 +- .../DatePickerBottomSheet.kt | 52 ++++-- .../presentation/writeroutine/model/Date.kt | 38 ++++ .../presentation/writeroutine/model/Time.kt | 21 +++ .../model/mvi/WriteRoutineIntent.kt | 14 +- .../model/mvi/WriteRoutineState.kt | 22 ++- 9 files changed, 327 insertions(+), 89 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt index 0404a993..589e2595 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt @@ -76,18 +76,22 @@ fun WriteRoutineScreenContainer( if (state.showStartDatePickerBottomSheet) { DatePickerBottomSheet( modifier = Modifier.fillMaxWidth(), - onDateSelected = {}, - date = state.startDate ?: Date.now(), - onDismiss = {}, + onDateSelected = viewModel::setStartDate, + date = state.startDate ?: state.endDate?.let { Date.min(it, Date.now()) } ?: Date.now(), + onDismiss = viewModel::hideStartDatePickerBottomSheet, + availableStartDate = null, + availableEndDate = state.endDate, ) } if (state.showEndDatePickerBottomSheet) { DatePickerBottomSheet( modifier = Modifier.fillMaxWidth(), - onDateSelected = {}, - date = state.endDate ?: Date.now(), - onDismiss = {}, + onDateSelected = viewModel::setEndDate, + date = state.endDate ?: state.startDate?.let { Date.max(it, Date.now()) } ?: Date.now(), + onDismiss = viewModel::hideEndDatePickerBottomSheet, + availableStartDate = state.startDate, + availableEndDate = null, ) } @@ -95,14 +99,19 @@ fun WriteRoutineScreenContainer( state = state, setRoutineName = viewModel::setRoutineName, setSubRoutineName = viewModel::setSubRoutineName, - addSubRoutine = viewModel::addSubRoutine, + selectNotUseSubRoutines = viewModel::selectNotUseSubRoutines, selectRepeatTime = viewModel::selectRepeatType, selectDay = viewModel::selectDay, selectAllTime = viewModel::selectAllTime, showTimePickerBottomSheet = viewModel::showTimePickerBottomSheet, onClickRegister = viewModel::registerRoutine, - removeSubRoutine = viewModel::removeSubRoutine, onClickBack = navigateToBack, + onClickSubRoutineExpand = viewModel::toggleSubRoutineUiExpanded, + onClickRepeatDaysExpand = viewModel::toggleRepeatDaysUiExpanded, + onClickStartTimeExpand = viewModel::toggleStartTimeUiExpanded, + onClickPeriodExpand = viewModel::togglePeriodUiExpanded, + showStartDatePickerBottomSheet = viewModel::showStartDatePickerBottomSheet, + showEndDatePickerBottomSheet = viewModel::showEndDatePickerBottomSheet, ) } @@ -111,14 +120,19 @@ private fun WriteRoutineScreen( state: WriteRoutineState, setRoutineName: (String) -> Unit, setSubRoutineName: (Int, String) -> Unit, - addSubRoutine: () -> Unit, + selectNotUseSubRoutines: () -> Unit, selectRepeatTime: (RepeatType) -> Unit, selectDay: (Day) -> Unit, selectAllTime: () -> Unit, showTimePickerBottomSheet: () -> Unit, onClickRegister: () -> Unit, - removeSubRoutine: (Int) -> Unit, onClickBack: () -> Unit, + onClickSubRoutineExpand: () -> Unit, + onClickRepeatDaysExpand: () -> Unit, + onClickStartTimeExpand: () -> Unit, + onClickPeriodExpand: () -> Unit, + showStartDatePickerBottomSheet: () -> Unit, + showEndDatePickerBottomSheet: () -> Unit, ) { val scrollState = rememberScrollState() @@ -163,10 +177,8 @@ private fun WriteRoutineScreen( iconResourceId = R.drawable.img_subroutines, title = "세부루틴", placeHolder = "ex) 일어나자마자 이불 개기", - valueText = "", - onClick = { - - } + valueText = state.subRoutinesText, + onClick = onClickSubRoutineExpand ) { Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 18.dp, vertical = 20.dp), @@ -178,7 +190,7 @@ private fun WriteRoutineScreen( placeHolder = getSubRoutinePlaceHolder(0), value = state.subRoutineNames.getOrNull(0) ?: "", onValueChange = { setSubRoutineName(0, it) }, - enabled = true, + enabled = !state.selectNotUseSUbRoutines, ) SubRoutineField( @@ -186,7 +198,7 @@ private fun WriteRoutineScreen( placeHolder = getSubRoutinePlaceHolder(1), value = state.subRoutineNames.getOrNull(1) ?: "", onValueChange = { setSubRoutineName(1, it) }, - enabled = true, + enabled = !state.selectNotUseSUbRoutines, ) SubRoutineField( @@ -194,15 +206,13 @@ private fun WriteRoutineScreen( placeHolder = getSubRoutinePlaceHolder(2), value = state.subRoutineNames.getOrNull(2) ?: "", onValueChange = { setSubRoutineName(2, it) }, - enabled = true, + enabled = !state.selectNotUseSUbRoutines, ) LabeledCheckBox( label = "세부 루틴 설정 안함", - checked = true, - onClick = { - addSubRoutine() - } + checked = state.selectNotUseSUbRoutines, + onClick = selectNotUseSubRoutines ) } } @@ -213,10 +223,8 @@ private fun WriteRoutineScreen( iconResourceId = R.drawable.img_repeat_days, title = "반복 요일", placeHolder = "ex) 매주 월,화,수.목,금", - valueText = "", - onClick = { - - } + valueText = state.repeatDaysText, + onClick = onClickRepeatDaysExpand ) { Column( modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp) @@ -271,10 +279,8 @@ private fun WriteRoutineScreen( iconResourceId = R.drawable.img_routine_period, title = "목표 기간", placeHolder = "ex) 25.08.06 - 25.08.06", - valueText = "", - onClick = { - - } + valueText = state.periodText, + onClick = onClickPeriodExpand ) { Column( modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), @@ -283,15 +289,15 @@ private fun WriteRoutineScreen( RoutineDetailRow( title = "시작일", placeHolder = "눌러서 선택", - value = "", - onClick = {}, + value = state.startDate?.toFormattedString() ?: "", + onClick = showStartDatePickerBottomSheet, ) RoutineDetailRow( title = "종료일", placeHolder = "눌러서 선택", - value = "", - onClick = {}, + value = state.endDate?.toFormattedString() ?: "", + onClick = showEndDatePickerBottomSheet, ) } } @@ -302,10 +308,8 @@ private fun WriteRoutineScreen( iconResourceId = R.drawable.img_start_time, title = "시간", placeHolder = "ex) 오전 9:40부터 시작", - valueText = "", - onClick = { - - } + valueText = state.startTimeText, + onClick = onClickStartTimeExpand ) { Column( modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), @@ -314,8 +318,8 @@ private fun WriteRoutineScreen( RoutineDetailRow( title = "시작 시간", placeHolder = "눌러서 선택", - value = "", - onClick = {}, + value = state.startTime?.toAmPmFormattedString() ?: "", + onClick = showTimePickerBottomSheet, ) Spacer(modifier = Modifier.height(20.dp)) @@ -323,9 +327,7 @@ private fun WriteRoutineScreen( LabeledCheckBox( label = "하루 종일", checked = state.selectAllTime, - onClick = { - - } + onClick = selectAllTime ) } @@ -361,14 +363,19 @@ fun WriteRoutineScreenPreview() { state = WriteRoutineState.Init.copy(periodUiExpanded = true, startTimeUiExpanded = true), setRoutineName = {}, setSubRoutineName = { _, _ -> }, - addSubRoutine = {}, selectRepeatTime = {}, selectDay = {}, selectAllTime = {}, showTimePickerBottomSheet = {}, onClickRegister = {}, - removeSubRoutine = {}, onClickBack = {}, + onClickSubRoutineExpand = {}, + onClickRepeatDaysExpand = {}, + onClickStartTimeExpand = {}, + onClickPeriodExpand = {}, + showStartDatePickerBottomSheet = {}, + showEndDatePickerBottomSheet = {}, + selectNotUseSubRoutines = {} ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index bcd75e14..8f48c285 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -9,6 +9,7 @@ import com.threegap.bitnagil.domain.writeroutine.usecase.EditRoutineUseCase import com.threegap.bitnagil.domain.writeroutine.usecase.GetChangedSubRoutinesUseCase import com.threegap.bitnagil.domain.writeroutine.usecase.RegisterRoutineUseCase import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel +import com.threegap.bitnagil.presentation.writeroutine.model.Date import com.threegap.bitnagil.presentation.writeroutine.model.Day import com.threegap.bitnagil.presentation.writeroutine.model.RepeatType import com.threegap.bitnagil.presentation.writeroutine.model.SelectableDay @@ -88,7 +89,11 @@ class WriteRoutineViewModel @AssistedInject constructor( name = routine.routineName, repeatDays = routine.repeatDay.map { Day.fromDayOfWeek(it) }, startTime = Time.fromDomainTimeString(routine.executionTime), - subRoutines = routine.subRoutines.map { it.subRoutineName }, + subRoutines = listOf( + oldSubRoutines.getOrNull(0)?.name ?: "", + oldSubRoutines.getOrNull(1)?.name ?: "", + oldSubRoutines.getOrNull(2)?.name ?: "", + ), ), ) }, @@ -110,7 +115,11 @@ class WriteRoutineViewModel @AssistedInject constructor( name = routine.name, repeatDays = listOf(), startTime = Time.fromDomainTimeString(routine.executionTime), - subRoutines = oldSubRoutines.map { it.name }, + subRoutines = listOf( + oldSubRoutines.getOrNull(0)?.name ?: "", + oldSubRoutines.getOrNull(1)?.name ?: "", + oldSubRoutines.getOrNull(2)?.name ?: "", + ), ), ) }, @@ -126,18 +135,6 @@ class WriteRoutineViewModel @AssistedInject constructor( state: WriteRoutineState, ): WriteRoutineState { when (intent) { - WriteRoutineIntent.AddSubRoutine -> { - return state.copy( - subRoutineNames = state.subRoutineNames + "", - ) - } - is WriteRoutineIntent.RemoveSubRoutine -> { - return state.copy( - subRoutineNames = state.subRoutineNames.filterIndexed { index, _ -> - index != intent.index - }, - ) - } WriteRoutineIntent.SelectAllTime -> { return state.copy( selectAllTime = !state.selectAllTime, @@ -262,6 +259,62 @@ class WriteRoutineViewModel @AssistedInject constructor( loading = true, ) } + WriteRoutineIntent.ShowStartDatePickerBottomSheet -> { + return state.copy( + showStartDatePickerBottomSheet = true, + ) + } + WriteRoutineIntent.HideEndDatePickerBottomSheet -> { + return state.copy( + showEndDatePickerBottomSheet = false, + ) + } + WriteRoutineIntent.ShowEndDatePickerBottomSheet -> { + return state.copy( + showEndDatePickerBottomSheet = true, + ) + } + WriteRoutineIntent.HideStartDatePickerBottomSheet -> { + return state.copy( + showStartDatePickerBottomSheet = false, + ) + } + is WriteRoutineIntent.SetPeriodUiExpanded -> { + return state.copy( + periodUiExpanded = intent.expanded, + ) + } + is WriteRoutineIntent.SetRepeatDaysUiExpanded -> { + return state.copy( + repeatDaysUiExpanded = intent.expanded, + ) + } + is WriteRoutineIntent.SetStartTimeUiExpanded -> { + return state.copy( + startTimeUiExpanded = intent.expanded, + ) + } + is WriteRoutineIntent.SetSubRoutineUiExpanded -> { + return state.copy( + subRoutineUiExpanded = intent.expanded, + ) + } + is WriteRoutineIntent.SetEndDate -> { + return state.copy( + endDate = intent.date, + ) + } + is WriteRoutineIntent.SetStartDate -> { + return state.copy( + startDate = intent.date, + ) + } + + WriteRoutineIntent.SelectNotUseSubRoutines -> { + return state.copy( + selectNotUseSUbRoutines = !state.selectNotUseSUbRoutines, + ) + } } } @@ -277,15 +330,9 @@ class WriteRoutineViewModel @AssistedInject constructor( } } - fun addSubRoutine() { - viewModelScope.launch { - sendIntent(WriteRoutineIntent.AddSubRoutine) - } - } - - fun removeSubRoutine(index: Int) { + fun selectNotUseSubRoutines() { viewModelScope.launch { - sendIntent(WriteRoutineIntent.RemoveSubRoutine(index)) + sendIntent(WriteRoutineIntent.SelectNotUseSubRoutines) } } @@ -326,6 +373,70 @@ class WriteRoutineViewModel @AssistedInject constructor( } } + fun showStartDatePickerBottomSheet() { + viewModelScope.launch { + sendIntent(WriteRoutineIntent.ShowStartDatePickerBottomSheet) + } + } + + fun hideStartDatePickerBottomSheet() { + viewModelScope.launch { + sendIntent(WriteRoutineIntent.HideStartDatePickerBottomSheet) + } + } + + fun showEndDatePickerBottomSheet() { + viewModelScope.launch { + sendIntent(WriteRoutineIntent.ShowEndDatePickerBottomSheet) + } + } + + fun hideEndDatePickerBottomSheet() { + viewModelScope.launch { + sendIntent(WriteRoutineIntent.HideEndDatePickerBottomSheet) + } + } + + fun setStartDate(date: Date) { + viewModelScope.launch { + sendIntent(WriteRoutineIntent.SetStartDate(date)) + } + } + + fun setEndDate(date: Date) { + viewModelScope.launch { + sendIntent(WriteRoutineIntent.SetEndDate(date)) + } + } + + fun toggleSubRoutineUiExpanded() { + val currentSubRoutineUiExpanded = stateFlow.value.subRoutineUiExpanded + viewModelScope.launch { + sendIntent(WriteRoutineIntent.SetSubRoutineUiExpanded(!currentSubRoutineUiExpanded)) + } + } + + fun toggleRepeatDaysUiExpanded() { + val currentRepeatDaysUiExpanded = stateFlow.value.repeatDaysUiExpanded + viewModelScope.launch { + sendIntent(WriteRoutineIntent.SetRepeatDaysUiExpanded(!currentRepeatDaysUiExpanded)) + } + } + + fun togglePeriodUiExpanded() { + val currentPeriodUiExpanded = stateFlow.value.periodUiExpanded + viewModelScope.launch { + sendIntent(WriteRoutineIntent.SetPeriodUiExpanded(!currentPeriodUiExpanded)) + } + } + + fun toggleStartTimeUiExpanded() { + val currentStartTimeUiExpanded = stateFlow.value.startTimeUiExpanded + viewModelScope.launch { + sendIntent(WriteRoutineIntent.SetStartTimeUiExpanded(!currentStartTimeUiExpanded)) + } + } + fun registerRoutine() { viewModelScope.launch { val currentState = stateFlow.value @@ -354,11 +465,13 @@ class WriteRoutineViewModel @AssistedInject constructor( when (currentState.writeRoutineType) { WriteRoutineType.ADD -> { sendIntent(WriteRoutineIntent.RegisterRoutineLoading) + val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames + val registerRoutineResult = registerRoutineUseCase( currentState.routineName, repeatDay, startTime.toDomainTime(), - currentState.subRoutineNames, + subRoutines, ) if (registerRoutineResult.isSuccess) { @@ -369,6 +482,7 @@ class WriteRoutineViewModel @AssistedInject constructor( } WriteRoutineType.EDIT -> { val currentRoutineId = routineId ?: return@launch + val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames val subRoutineDiffs = getChangedSubRoutinesUseCase( oldSubRoutines = oldSubRoutines.mapIndexed { index, subRoutine -> @@ -378,7 +492,7 @@ class WriteRoutineViewModel @AssistedInject constructor( sort = index + 1, ) }, - newSubRoutineNames = currentState.subRoutineNames, + newSubRoutineNames = subRoutines.filter { it.isNotEmpty() }, ) sendIntent(WriteRoutineIntent.EditRoutineLoading) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt index 87e800bc..e86dbe54 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt @@ -25,7 +25,7 @@ fun SelectCell( ) { Box( modifier = modifier - .height(48.dp) + .height(38.dp) .background( color = if (selected) BitnagilTheme.colors.orange500 else BitnagilTheme.colors.white, shape = RoundedCornerShape(12.dp), diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt index 8e9f53bc..033cbbce 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -54,12 +55,12 @@ fun ExpandableContent( .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(color = BitnagilTheme.colors.coolGray99) - .animateContentSize(), ) { Row( modifier = Modifier .fillMaxWidth() - .padding(start = 18.dp, end = 12.dp, top = 18.dp, bottom = 18.dp), + .padding(start = 18.dp, end = 12.dp, top = 18.dp, bottom = 18.dp) + .animateContentSize(), verticalAlignment = Alignment.CenterVertically, ) { Image( @@ -90,7 +91,6 @@ fun ExpandableContent( if (!expand) { Text( text = if (showValueText) valueText ?: placeHolder else placeHolder, - maxLines = 1, style = if (showValueText) mainTextStyle else subTextStyle, ) } @@ -133,13 +133,13 @@ fun ExpandableContentPreview() { iconResourceId = R.drawable.img_subroutines, title = "세부루틴", placeHolder = "세부루틴을 설정해주세요.", - valueText = "", + valueText = "이거\n길면\n어떻게될까", onClick = { isExpanded = !isExpanded }, required = true, content = { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.clickable { isChecked = !isChecked }, + modifier = Modifier.height(100.dp).clickable { isChecked = !isChecked }, ) { Checkbox( checked = isChecked, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt index e2685c1a..e5b2ae83 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -38,6 +39,7 @@ import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.component.atom.BitnagilIconButton import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple import com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet.model.CalendarUtils import com.threegap.bitnagil.presentation.writeroutine.model.Date import kotlinx.coroutines.launch @@ -49,8 +51,12 @@ fun DatePickerBottomSheet( onDateSelected: (Date) -> Unit, date: Date, onDismiss: () -> Unit, + availableStartDate: Date?, + availableEndDate: Date?, ) { - val sheetState = rememberModalBottomSheetState() + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) val coroutineScope = rememberCoroutineScope() ModalBottomSheet( @@ -71,6 +77,8 @@ fun DatePickerBottomSheet( } }, initDate = date, + availableStartDate = availableStartDate, + availableEndDate = availableEndDate ) } } @@ -80,6 +88,8 @@ private fun DatePickerBottomSheetContent( modifier: Modifier = Modifier, onDateSelected: (Date) -> Unit, initDate: Date, + availableStartDate: Date?, + availableEndDate: Date? ) { var currentYear by remember { mutableIntStateOf(initDate.year) } var currentMonth by remember { mutableIntStateOf(initDate.month) } @@ -95,6 +105,17 @@ private fun DatePickerBottomSheetContent( CalendarUtils.getDayAmountOfMonth(initDate.year, initDate.month) } + val prevMonthButtonEnabled by remember(availableStartDate) { + derivedStateOf { + (availableStartDate == null) || (availableStartDate.year < currentYear) || (availableStartDate.month < currentMonth) + } + } + val nextMonthButtonEnabled by remember(availableEndDate) { + derivedStateOf { + (availableEndDate == null) || (availableEndDate.year > currentYear) || (availableEndDate.month > currentMonth) + } + } + Column( modifier = modifier.background( color = BitnagilTheme.colors.white, @@ -121,11 +142,10 @@ private fun DatePickerBottomSheetContent( currentMonth -= 1 } }, - modifier = Modifier - .size(48.dp), - enabled = true, + modifier = Modifier.size(48.dp), + enabled = prevMonthButtonEnabled, paddingValues = PaddingValues(14.dp), - tint = BitnagilTheme.colors.coolGray10, + tint = if (prevMonthButtonEnabled) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray95, ) BitnagilIconButton( @@ -138,11 +158,10 @@ private fun DatePickerBottomSheetContent( currentMonth += 1 } }, - modifier = Modifier - .size(48.dp), - enabled = true, + modifier = Modifier.size(48.dp), + enabled = nextMonthButtonEnabled, paddingValues = PaddingValues(14.dp), - tint = BitnagilTheme.colors.coolGray10, + tint = if (nextMonthButtonEnabled) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray95, ) } } @@ -180,19 +199,26 @@ private fun DatePickerBottomSheetContent( items(currentDaysOfMonth) { index -> val selected = (selectedDate.year == currentYear) && (selectedDate.month == currentMonth) && (selectedDate.day == index + 1) + val currentDate = Date(year = currentYear, month = currentMonth, day = index + 1) + val available = currentDate.checkInRange(startDate = availableStartDate, endDate = availableEndDate) Box( modifier = Modifier.aspectRatio(1f).background( color = if (selected) { BitnagilTheme.colors.orange50 } else { Color.Transparent }, shape = if (selected) { RoundedCornerShape(12.dp) } else { RectangleShape } - ), + ).clickableWithoutRipple { + if (!available) return@clickableWithoutRipple + selectedDate = currentDate + }, contentAlignment = Alignment.Center, ) { Text( "${index + 1}", style = if (selected) { BitnagilTheme.typography.subtitle1SemiBold.copy(color = BitnagilTheme.colors.orange500) - } else { + } else if (available) { BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray10) + } else { + BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.coolGray80) }, ) } @@ -234,7 +260,9 @@ private fun DatePickerBottomSheetContentPreview(){ DatePickerBottomSheetContent( modifier = Modifier, onDateSelected = { _ -> }, - initDate = Date(year = 2025, month = 8, day = 1) + initDate = Date(year = 2025, month = 8, day = 1), + availableStartDate = null, + availableEndDate = null ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt index 504660d5..5bc65a7f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt @@ -15,5 +15,43 @@ data class Date( month = java.time.LocalDate.now().monthValue, day = java.time.LocalDate.now().dayOfMonth ) + + fun min(d1: Date, d2: Date): Date { + if (d1.year < d2.year) return d1 + if (d1.year > d2.year) return d2 + + if (d1.month < d2.month) return d1 + if (d1.month > d2.month) return d2 + + if (d1.day < d2.day) return d1 + + return d2 + } + + fun max(d1: Date, d2: Date): Date { + if (d1.year > d2.year) return d1 + if (d1.year < d2.year) return d2 + + if (d1.month > d2.month) return d1 + if (d1.month < d2.month) return d2 + + if (d1.day > d2.day) return d1 + + return d2 + } + } + + fun toFormattedString(): String = "%04d.%02d.%02d".format(year, month, day) + fun toYearShrinkageFormattedString(): String = "%02d.%02d.%02d".format((year % 100), month, day) + + fun checkInRange(startDate: Date?, endDate: Date?) : Boolean { + val appliedStartDate = startDate ?: Date(year = 2000, month = 1, day = 1) + val appliedEndDate = endDate ?: Date(year = 2999, month = 12, day = 31) + + val startValue = appliedStartDate.year * 10000 + appliedStartDate.month * 100 + appliedStartDate.day + val endValue = appliedEndDate.year * 10000 + appliedEndDate.month * 100 + appliedEndDate.day + val targetValue = year * 10000 + month * 100 + day + + return targetValue in startValue..endValue } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt index 13521129..00df1415 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt @@ -2,6 +2,7 @@ package com.threegap.bitnagil.presentation.writeroutine.model import android.os.Parcelable import kotlinx.parcelize.Parcelize +import kotlin.text.format import com.threegap.bitnagil.domain.writeroutine.model.Time as DomainTime @Parcelize @@ -31,4 +32,24 @@ data class Time( fun toDomainTime(): DomainTime { return DomainTime(hour = hour, minute = minute) } + + fun toAmPmFormattedString(): String { + val amPm: String + var displayHour = hour + + if (hour == 0) { // 자정 (오전 12시) + amPm = "오전" + displayHour = 12 + } else if (hour == 12) { // 정오 (오후 12시) + amPm = "오후" + displayHour = 12 + } else if (hour > 12) { + amPm = "오후" + displayHour -= 12 + } else { + amPm = "오전" + } + + return "%s %d:%02d".format(amPm, displayHour, minute) + } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt index cb085399..f3e02062 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt @@ -1,6 +1,7 @@ package com.threegap.bitnagil.presentation.writeroutine.model.mvi import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent +import com.threegap.bitnagil.presentation.writeroutine.model.Date import com.threegap.bitnagil.presentation.writeroutine.model.Day import com.threegap.bitnagil.presentation.writeroutine.model.RepeatType import com.threegap.bitnagil.presentation.writeroutine.model.Time @@ -8,9 +9,8 @@ import com.threegap.bitnagil.presentation.writeroutine.model.WriteRoutineType sealed class WriteRoutineIntent : MviIntent { data class SetRoutineName(val name: String) : WriteRoutineIntent() - data object AddSubRoutine : WriteRoutineIntent() - data class RemoveSubRoutine(val index: Int) : WriteRoutineIntent() data class SetSubRoutineName(val index: Int, val name: String) : WriteRoutineIntent() + data object SelectNotUseSubRoutines : WriteRoutineIntent() data class SetRepeatType(val repeatType: RepeatType) : WriteRoutineIntent() data class SelectDay(val day: Day) : WriteRoutineIntent() data class SetStartTime(val time: Time) : WriteRoutineIntent() @@ -19,6 +19,16 @@ sealed class WriteRoutineIntent : MviIntent { data object SelectAllTime : WriteRoutineIntent() data object ShowTimePickerBottomSheet : WriteRoutineIntent() data object HideTimePickerBottomSheet : WriteRoutineIntent() + data object ShowStartDatePickerBottomSheet : WriteRoutineIntent() + data object HideStartDatePickerBottomSheet : WriteRoutineIntent() + data object ShowEndDatePickerBottomSheet : WriteRoutineIntent() + data object HideEndDatePickerBottomSheet : WriteRoutineIntent() + data class SetSubRoutineUiExpanded(val expanded: Boolean) : WriteRoutineIntent() + data class SetRepeatDaysUiExpanded(val expanded: Boolean) : WriteRoutineIntent() + data class SetPeriodUiExpanded(val expanded: Boolean) : WriteRoutineIntent() + data class SetStartTimeUiExpanded(val expanded: Boolean) : WriteRoutineIntent() + data class SetStartDate(val date: Date) : WriteRoutineIntent() + data class SetEndDate(val date: Date) : WriteRoutineIntent() data object RegisterRoutineLoading : WriteRoutineIntent() data object RegisterRoutineSuccess : WriteRoutineIntent() data object RegisterRoutineFailure : WriteRoutineIntent() diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt index e47f3349..eb609aa5 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt @@ -13,6 +13,7 @@ import kotlinx.parcelize.Parcelize data class WriteRoutineState( val routineName: String, val subRoutineNames: List, + val selectNotUseSUbRoutines: Boolean, val repeatType: RepeatType?, val repeatDays: List, val startTime: Time?, @@ -32,7 +33,8 @@ data class WriteRoutineState( companion object { val Init = WriteRoutineState( routineName = "", - subRoutineNames = emptyList(), + subRoutineNames = listOf("", "", ""), + selectNotUseSUbRoutines = false, repeatType = null, repeatDays = listOf( SelectableDay( @@ -84,4 +86,22 @@ data class WriteRoutineState( get() = routineName.isNotEmpty() && (repeatType == RepeatType.DAILY || (repeatType == RepeatType.DAY && repeatDays.any { it.selected })) && startTime != null && !loading + + val subRoutinesText: String get() = subRoutineNames.filter { it.isNotEmpty() }.joinToString(separator = "\n") + + val repeatDaysText: String + get() = when(repeatType) { + RepeatType.DAILY -> "매일" + RepeatType.DAY -> "매주 ${repeatDays.filter { it.selected }.joinToString { it.day.text }}" + null -> "" + } + + val periodText: String get() { + if (startDate == null && endDate == null) return "" + return "${startDate?.toYearShrinkageFormattedString() ?: ""} ~ ${endDate?.toYearShrinkageFormattedString() ?: ""}" + } + + val startTimeText: String + get() = if (selectAllTime) "하루종일" else startTime?.let { "${it.toAmPmFormattedString()}부터 시작" } ?: "" + } From 62254c688c808364315746fef8ed88c5c2d101cd Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Wed, 13 Aug 2025 21:29:54 +0900 Subject: [PATCH 05/16] =?UTF-8?q?CHORE:=20ktlint=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writeroutine/WriteRoutineScreen.kt | 25 +++++++-------- .../component/atom/namefield/NameField.kt | 6 ++-- .../component/atom/selectcell/SelectCell.kt | 16 +++++----- .../expandablecontent/ExpandableContent.kt | 8 ++--- .../routinedetailrow/RoutineDetailRow.kt | 7 ++-- .../block/subroutinefield/SubRoutineField.kt | 11 ++++--- .../DatePickerBottomSheet.kt | 32 +++++++++---------- .../model/CalendarUtils.kt | 17 ++++++---- .../presentation/writeroutine/model/Date.kt | 6 ++-- .../presentation/writeroutine/model/Time.kt | 4 +-- .../model/mvi/WriteRoutineState.kt | 3 +- 11 files changed, 70 insertions(+), 65 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt index 589e2595..b3fc33d4 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt @@ -169,7 +169,7 @@ private fun WriteRoutineScreen( Spacer(modifier = Modifier.height(40.dp)) Column( - verticalArrangement = Arrangement.spacedBy(12.dp) + verticalArrangement = Arrangement.spacedBy(12.dp), ) { ExpandableContent( expand = state.subRoutineUiExpanded, @@ -178,12 +178,12 @@ private fun WriteRoutineScreen( title = "세부루틴", placeHolder = "ex) 일어나자마자 이불 개기", valueText = state.subRoutinesText, - onClick = onClickSubRoutineExpand + onClick = onClickSubRoutineExpand, ) { Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 18.dp, vertical = 20.dp), verticalArrangement = Arrangement.spacedBy(24.dp), - horizontalAlignment = Alignment.End + horizontalAlignment = Alignment.End, ) { SubRoutineField( resourceId = R.drawable.img_circle_1, @@ -212,7 +212,7 @@ private fun WriteRoutineScreen( LabeledCheckBox( label = "세부 루틴 설정 안함", checked = state.selectNotUseSUbRoutines, - onClick = selectNotUseSubRoutines + onClick = selectNotUseSubRoutines, ) } } @@ -224,10 +224,10 @@ private fun WriteRoutineScreen( title = "반복 요일", placeHolder = "ex) 매주 월,화,수.목,금", valueText = state.repeatDaysText, - onClick = onClickRepeatDaysExpand + onClick = onClickRepeatDaysExpand, ) { Column( - modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp) + modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), ) { Row { WriteRoutineButton.Text( @@ -280,11 +280,11 @@ private fun WriteRoutineScreen( title = "목표 기간", placeHolder = "ex) 25.08.06 - 25.08.06", valueText = state.periodText, - onClick = onClickPeriodExpand + onClick = onClickPeriodExpand, ) { Column( modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) + verticalArrangement = Arrangement.spacedBy(12.dp), ) { RoutineDetailRow( title = "시작일", @@ -309,11 +309,11 @@ private fun WriteRoutineScreen( title = "시간", placeHolder = "ex) 오전 9:40부터 시작", valueText = state.startTimeText, - onClick = onClickStartTimeExpand + onClick = onClickStartTimeExpand, ) { Column( modifier = Modifier.fillMaxWidth().padding(top = 14.dp, bottom = 18.dp, start = 18.dp, end = 18.dp), - horizontalAlignment = Alignment.End + horizontalAlignment = Alignment.End, ) { RoutineDetailRow( title = "시작 시간", @@ -327,10 +327,9 @@ private fun WriteRoutineScreen( LabeledCheckBox( label = "하루 종일", checked = state.selectAllTime, - onClick = selectAllTime + onClick = selectAllTime, ) } - } } } @@ -375,7 +374,7 @@ fun WriteRoutineScreenPreview() { onClickPeriodExpand = {}, showStartDatePickerBottomSheet = {}, showEndDatePickerBottomSheet = {}, - selectNotUseSubRoutines = {} + selectNotUseSubRoutines = {}, ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt index 45839d3e..ccef1de8 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt @@ -54,12 +54,12 @@ fun NameField( if (onClickRemove != null) { Box( modifier = Modifier.clickableWithoutRipple(onClick = onClickRemove), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { BitnagilIcon( id = R.drawable.ic_close_circle, tint = null, - modifier = Modifier.size(24.dp).padding(top = 4.dp, bottom = 4.dp, start = 8.dp) + modifier = Modifier.size(24.dp).padding(top = 4.dp, bottom = 4.dp, start = 8.dp), ) } } @@ -80,7 +80,7 @@ fun NameField( @Composable @Preview(showBackground = true, widthDp = 300, heightDp = 300) fun NameFieldPreview() { - BitnagilTheme{ + BitnagilTheme { NameField( value = "value", onValueChange = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt index e86dbe54..d9f6ac14 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt @@ -51,49 +51,49 @@ fun SelectCell( private fun SelectCellPreview() { BitnagilTheme { Row( - horizontalArrangement = Arrangement.spacedBy(4.dp) + horizontalArrangement = Arrangement.spacedBy(4.dp), ) { SelectCell( text = "월", selected = true, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) SelectCell( text = "화", selected = false, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) SelectCell( text = "수", selected = false, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) SelectCell( text = "목", selected = false, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) SelectCell( text = "금", selected = false, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) SelectCell( text = "토", selected = false, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) SelectCell( text = "일", selected = false, onClick = {}, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt index 033cbbce..32f85769 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt @@ -54,7 +54,7 @@ fun ExpandableContent( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) - .background(color = BitnagilTheme.colors.coolGray99) + .background(color = BitnagilTheme.colors.coolGray99), ) { Row( modifier = Modifier @@ -81,12 +81,13 @@ fun ExpandableContent( style = if (showValueText && !expand) subTextStyle else mainTextStyle, ) - if (required) + if (required) { BitnagilIcon( id = R.drawable.ic_routine_success, tint = null, modifier = Modifier.size(12.dp), ) + } } if (!expand) { Text( @@ -107,7 +108,7 @@ fun ExpandableContent( } AnimatedVisibility(visible = expand) { - Column{ + Column { HorizontalDivider( modifier = Modifier.fillMaxWidth().padding(horizontal = 18.dp), color = BitnagilTheme.colors.coolGray96, @@ -118,7 +119,6 @@ fun ExpandableContent( } } } - } @Preview(heightDp = 300) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt index 05101bf9..4dab6f02 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt @@ -25,7 +25,7 @@ fun RoutineDetailRow( Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Text(title, style = BitnagilTheme.typography.body2SemiBold.copy(color = BitnagilTheme.colors.coolGray30)) @@ -35,10 +35,11 @@ fun RoutineDetailRow( isSelected = false, ) { Box( - modifier = Modifier.padding(vertical = 10.dp) + modifier = Modifier.padding(vertical = 10.dp), ) { - if (value.isEmpty()) + if (value.isEmpty()) { Text(placeHolder, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90)) + } Text(value, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30)) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt index fd9cc6b3..a9a976f0 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt @@ -30,7 +30,7 @@ fun SubRoutineField( ) { Row( modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Image( painter = painterResource(resourceId), @@ -50,11 +50,12 @@ fun SubRoutineField( decorationBox = { innerTextField -> Column { Box { - if (value.isEmpty()) + if (value.isEmpty()) { Text( text = placeHolder, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90), ) + } innerTextField() } @@ -66,7 +67,7 @@ fun SubRoutineField( color = BitnagilTheme.colors.coolGray90, ) } - } + }, ) } } @@ -74,13 +75,13 @@ fun SubRoutineField( @Composable @Preview(showBackground = true, widthDp = 300, heightDp = 300) fun NameFieldPreview() { - BitnagilTheme{ + BitnagilTheme { SubRoutineField( resourceId = com.threegap.bitnagil.designsystem.R.drawable.img_circle_1, placeHolder = "세부루틴을 설정해주세요.", value = "TEXT", onValueChange = {}, - enabled = true + enabled = true, ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt index e5b2ae83..872e7606 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt @@ -36,9 +36,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilIconButton import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton -import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple import com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet.model.CalendarUtils import com.threegap.bitnagil.presentation.writeroutine.model.Date @@ -55,7 +55,7 @@ fun DatePickerBottomSheet( availableEndDate: Date?, ) { val sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true + skipPartiallyExpanded = true, ) val coroutineScope = rememberCoroutineScope() @@ -63,7 +63,7 @@ fun DatePickerBottomSheet( sheetState = sheetState, onDismissRequest = onDismiss, containerColor = BitnagilTheme.colors.white, - ){ + ) { DatePickerBottomSheetContent( modifier = modifier, onDateSelected = { date -> @@ -78,7 +78,7 @@ fun DatePickerBottomSheet( }, initDate = date, availableStartDate = availableStartDate, - availableEndDate = availableEndDate + availableEndDate = availableEndDate, ) } } @@ -89,7 +89,7 @@ private fun DatePickerBottomSheetContent( onDateSelected: (Date) -> Unit, initDate: Date, availableStartDate: Date?, - availableEndDate: Date? + availableEndDate: Date?, ) { var currentYear by remember { mutableIntStateOf(initDate.year) } var currentMonth by remember { mutableIntStateOf(initDate.month) } @@ -127,7 +127,7 @@ private fun DatePickerBottomSheetContent( .fillMaxWidth() .padding(start = 20.dp, end = 16.dp), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Text("${currentYear}년 ${currentMonth}월", style = BitnagilTheme.typography.title2Bold.copy(color = BitnagilTheme.colors.coolGray10)) @@ -169,26 +169,26 @@ private fun DatePickerBottomSheetContent( Spacer(modifier = Modifier.height(16.dp)) Box( - modifier = Modifier.padding(horizontal = 20.dp) - ){ + modifier = Modifier.padding(horizontal = 20.dp), + ) { LazyVerticalGrid( columns = GridCells.Fixed(7), verticalArrangement = Arrangement.spacedBy(2.dp), - horizontalArrangement = Arrangement.spacedBy(2.dp) + horizontalArrangement = Arrangement.spacedBy(2.dp), ) { items(CalendarUtils.dateStringList.size) { index -> Text( CalendarUtils.dateStringList[index], modifier = Modifier.padding(bottom = 16.dp), style = BitnagilTheme.typography.body2SemiBold.copy(color = BitnagilTheme.colors.coolGray50), - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) } itemsIndexed(lastDaysOfPrevMonth) { _, day -> Box( modifier = Modifier.aspectRatio(1f), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { Text( "$day", @@ -204,7 +204,7 @@ private fun DatePickerBottomSheetContent( Box( modifier = Modifier.aspectRatio(1f).background( color = if (selected) { BitnagilTheme.colors.orange50 } else { Color.Transparent }, - shape = if (selected) { RoundedCornerShape(12.dp) } else { RectangleShape } + shape = if (selected) { RoundedCornerShape(12.dp) } else { RectangleShape }, ).clickableWithoutRipple { if (!available) return@clickableWithoutRipple selectedDate = currentDate @@ -227,7 +227,7 @@ private fun DatePickerBottomSheetContent( itemsIndexed(firstDaysOfNextMonth) { _, day -> Box( modifier = Modifier.aspectRatio(1f), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { Text( "$day", @@ -255,14 +255,14 @@ private fun DatePickerBottomSheetContent( @Preview @Composable -private fun DatePickerBottomSheetContentPreview(){ - BitnagilTheme{ +private fun DatePickerBottomSheetContentPreview() { + BitnagilTheme { DatePickerBottomSheetContent( modifier = Modifier, onDateSelected = { _ -> }, initDate = Date(year = 2025, month = 8, day = 1), availableStartDate = null, - availableEndDate = null + availableEndDate = null, ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt index 86607079..cd7f4d2a 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt @@ -5,16 +5,21 @@ import java.util.Calendar class CalendarUtils { companion object { val dateStringList = listOf( - "일", "월", "화", "수", "목", "금", "토" + "일", + "월", + "화", + "수", + "목", + "금", + "토", ) - fun getDayAmountOfMonth(year : Int, month : Int) : Int { + fun getDayAmountOfMonth(year: Int, month: Int): Int { return when (month) { 2 -> { - if (year % 4 != 0 || (year % 100 == 0 && year % 400 != 0)) { 28 } - else { 29 } + if (year % 4 != 0 || (year % 100 == 0 && year % 400 != 0)) { 28 } else { 29 } } - in listOf(1,3,5,7,8,10,12) -> { 31 } + in listOf(1, 3, 5, 7, 8, 10, 12) -> { 31 } else -> { 30 } } } @@ -45,7 +50,7 @@ class CalendarUtils { /** * 달력 상에서 다음달에 해당하는 요일의 리스트를 리턴합니다. */ - fun firstDaysOfNextMonth(year : Int, month : Int) : List { + fun firstDaysOfNextMonth(year: Int, month: Int): List { val calendar = Calendar.getInstance() calendar.set(Calendar.YEAR, year) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt index 5bc65a7f..94eff5f9 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt @@ -7,13 +7,13 @@ import kotlinx.parcelize.Parcelize data class Date( val year: Int, val month: Int, - val day: Int + val day: Int, ) : Parcelable { companion object { fun now() = Date( year = java.time.LocalDate.now().year, month = java.time.LocalDate.now().monthValue, - day = java.time.LocalDate.now().dayOfMonth + day = java.time.LocalDate.now().dayOfMonth, ) fun min(d1: Date, d2: Date): Date { @@ -44,7 +44,7 @@ data class Date( fun toFormattedString(): String = "%04d.%02d.%02d".format(year, month, day) fun toYearShrinkageFormattedString(): String = "%02d.%02d.%02d".format((year % 100), month, day) - fun checkInRange(startDate: Date?, endDate: Date?) : Boolean { + fun checkInRange(startDate: Date?, endDate: Date?): Boolean { val appliedStartDate = startDate ?: Date(year = 2000, month = 1, day = 1) val appliedEndDate = endDate ?: Date(year = 2999, month = 12, day = 31) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt index 00df1415..d5bdc8a0 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt @@ -41,13 +41,13 @@ data class Time( amPm = "오전" displayHour = 12 } else if (hour == 12) { // 정오 (오후 12시) - amPm = "오후" + amPm = "오후" displayHour = 12 } else if (hour > 12) { amPm = "오후" displayHour -= 12 } else { - amPm = "오전" + amPm = "오전" } return "%s %d:%02d".format(amPm, displayHour, minute) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt index eb609aa5..832da7d1 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt @@ -90,7 +90,7 @@ data class WriteRoutineState( val subRoutinesText: String get() = subRoutineNames.filter { it.isNotEmpty() }.joinToString(separator = "\n") val repeatDaysText: String - get() = when(repeatType) { + get() = when (repeatType) { RepeatType.DAILY -> "매일" RepeatType.DAY -> "매주 ${repeatDays.filter { it.selected }.joinToString { it.day.text }}" null -> "" @@ -103,5 +103,4 @@ data class WriteRoutineState( val startTimeText: String get() = if (selectAllTime) "하루종일" else startTime?.let { "${it.toAmPmFormattedString()}부터 시작" } ?: "" - } From 03b1bd2b0369038fd1170aa126e82deffa438dee Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Wed, 13 Aug 2025 21:36:56 +0900 Subject: [PATCH 06/16] =?UTF-8?q?FIX:=20ExpandableContent=EC=9D=98=20?= =?UTF-8?q?=EC=A0=91=EC=97=88=EB=8B=A4=20=ED=8E=B4=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=ED=96=89=20=ED=84=B0=EC=B9=98=20=EC=98=81?= =?UTF-8?q?=EC=97=AD=EC=9D=84=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=97=90=EC=84=9C=20=EC=83=81=EB=8B=A8=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=98=81=EC=97=AD=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/block/expandablecontent/ExpandableContent.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt index 32f85769..05e71436 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.unit.sp import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon -import com.threegap.bitnagil.designsystem.component.atom.BitnagilIconButton +import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple @Composable fun ExpandableContent( @@ -59,6 +59,7 @@ fun ExpandableContent( Row( modifier = Modifier .fillMaxWidth() + .clickableWithoutRipple(onClick = onClick) .padding(start = 18.dp, end = 12.dp, top = 18.dp, bottom = 18.dp) .animateContentSize(), verticalAlignment = Alignment.CenterVertically, @@ -97,13 +98,12 @@ fun ExpandableContent( } } - BitnagilIconButton( + BitnagilIcon( id = if (expand) R.drawable.ic_up_arrow_20 else R.drawable.ic_down_arrow_20, tint = BitnagilTheme.colors.coolGray30, modifier = Modifier .size(36.dp) .padding(8.dp), - onClick = onClick, ) } From 46b315a80a4ccaa4a08ca4f635fc2fc4fea90df4 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Sun, 17 Aug 2025 22:12:07 +0900 Subject: [PATCH 07/16] =?UTF-8?q?REFACTOR:=20=EB=A3=A8=ED=8B=B4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1/=EC=88=98=EC=A0=95=20=EA=B4=80=EB=A0=A8=20AP?= =?UTF-8?q?I=20v2=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/threegap/bitnagil/MainNavHost.kt | 2 +- .../main/java/com/threegap/bitnagil/Route.kt | 1 + .../data/writeroutine/model/dto/RoutineDto.kt | 51 ------ .../writeroutine/model/dto/SubRoutineDto.kt | 23 --- .../model/dto/SubRoutineInfosDiffDto.kt | 25 --- .../model/request/EditRoutineRequest.kt | 11 +- .../model/request/RegisterRoutineRequest.kt | 4 + .../WriteRoutineRepositoryImpl.kt | 27 +++- .../service/WriteRoutineService.kt | 4 +- .../domain/writeroutine/model/Date.kt | 4 + .../writeroutine/model/RoutineUpdateType.kt | 5 + .../writeroutine/model/SubRoutineDiff.kt | 7 - .../repository/WriteRoutineRepository.kt | 10 +- .../usecase/EditRoutineUseCase.kt | 11 +- .../usecase/GetChangedSubRoutinesUseCase.kt | 67 -------- .../usecase/RegisterRoutineUseCase.kt | 5 + .../GetChangedSubRoutinesUseCaseTest.kt | 146 ------------------ .../writeroutine/WriteRoutineScreen.kt | 2 +- .../writeroutine/WriteRoutineViewModel.kt | 47 +++--- .../presentation/writeroutine/model/Date.kt | 9 ++ .../writeroutine/model/WriteRoutineType.kt | 12 +- .../model/mvi/WriteRoutineState.kt | 2 +- .../model/navarg/WriteRoutineScreenArg.kt | 2 +- 23 files changed, 111 insertions(+), 366 deletions(-) delete mode 100644 data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/RoutineDto.kt delete mode 100644 data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineDto.kt delete mode 100644 data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineInfosDiffDto.kt create mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/RoutineUpdateType.kt delete mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/SubRoutineDiff.kt delete mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCase.kt delete mode 100644 domain/src/test/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCaseTest.kt diff --git a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt index 5716453b..c6a9e0d8 100644 --- a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt +++ b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt @@ -197,7 +197,7 @@ fun MainNavHost( val writeScreenNavArg = if (arg.isRegister) { WriteRoutineScreenArg.Add(baseRoutineId = arg.routineId) } else { - WriteRoutineScreenArg.Edit(routineId = arg.routineId!!) + WriteRoutineScreenArg.Edit(routineId = arg.routineId!!, updateRoutineFromNowDate = arg.isUpdateRoutineFromNowDate) } val viewModel = hiltViewModel { factory -> diff --git a/app/src/main/java/com/threegap/bitnagil/Route.kt b/app/src/main/java/com/threegap/bitnagil/Route.kt index ced10832..565b0fc3 100644 --- a/app/src/main/java/com/threegap/bitnagil/Route.kt +++ b/app/src/main/java/com/threegap/bitnagil/Route.kt @@ -34,6 +34,7 @@ sealed interface Route { data class WriteRoutine( val routineId: String? = null, val isRegister: Boolean = true, + val isUpdateRoutineFromNowDate: Boolean = true, ) : Route @Serializable diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/RoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/RoutineDto.kt deleted file mode 100644 index a00814cc..00000000 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/RoutineDto.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.threegap.bitnagil.data.writeroutine.model.dto - -import com.threegap.bitnagil.domain.writeroutine.model.Date -import com.threegap.bitnagil.domain.writeroutine.model.RepeatDay -import com.threegap.bitnagil.domain.writeroutine.model.Routine -import com.threegap.bitnagil.domain.writeroutine.model.Time -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class RoutineDto( - @SerialName("routineId") - val routineId: String, - @SerialName("routineName") - val routineName: String, - @SerialName("repeatDay") - val repeatDay: List, - @SerialName("executionTime") - val executionTime: String, - @SerialName("subRoutineInfos") - val subRoutineInfos: List, -) { - fun toRoutine(): Routine { - val dividedTimeStrings = executionTime.split(":") - require(dividedTimeStrings.size >= 2) { "Invalid time format: $executionTime Expected format: HH:mm / HH:mm:ss" } - - val startTime = Time( - hour = dividedTimeStrings[0].toIntOrNull() ?: throw IllegalArgumentException("Invalid hour: ${dividedTimeStrings[0]}"), - minute = dividedTimeStrings[1].toIntOrNull() ?: throw IllegalArgumentException("Invalid minute: ${dividedTimeStrings[1]}"), - ) - - return Routine( - id = routineId, - name = routineName, - subRoutines = subRoutineInfos.map { it.toSubRoutine() }, - repeatDays = repeatDay.mapNotNull { - try { - RepeatDay.valueOf(it) - } catch (e: IllegalArgumentException) { - null - } - }, - startTime = startTime, - endDate = Date( - year = 2099, - month = 12, - day = 31, - ), - ) - } -} diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineDto.kt deleted file mode 100644 index 6af214e3..00000000 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineDto.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.threegap.bitnagil.data.writeroutine.model.dto - -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutine -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class SubRoutineDto( - @SerialName("subRoutineId") - val subRoutineId: String, - @SerialName("subRoutineName") - val subRoutineName: String, - @SerialName("sortOrder") - val sortOrder: Int, -) { - fun toSubRoutine(): SubRoutine { - return SubRoutine( - id = subRoutineId, - name = subRoutineName, - sort = sortOrder, - ) - } -} diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineInfosDiffDto.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineInfosDiffDto.kt deleted file mode 100644 index 448d59df..00000000 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/dto/SubRoutineInfosDiffDto.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.threegap.bitnagil.data.writeroutine.model.dto - -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutineDiff -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class SubRoutineInfosDiffDto( - @SerialName("subRoutineId") - val subRoutineId: String?, - @SerialName("subRoutineName") - val subRoutineName: String?, - @SerialName("sortOrder") - val sortOrder: Int?, -) { - companion object { - fun fromSubRoutineDiff(subRoutineDiff: SubRoutineDiff): SubRoutineInfosDiffDto { - return SubRoutineInfosDiffDto( - subRoutineId = subRoutineDiff.id, - subRoutineName = subRoutineDiff.name, - sortOrder = subRoutineDiff.sort, - ) - } - } -} diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt index 3a2bbf8a..e743de3e 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt @@ -1,6 +1,5 @@ package com.threegap.bitnagil.data.writeroutine.model.request -import com.threegap.bitnagil.data.writeroutine.model.dto.SubRoutineInfosDiffDto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -8,12 +7,18 @@ import kotlinx.serialization.Serializable data class EditRoutineRequest( @SerialName("routineId") val routineId: String, + @SerialName("updateApplyDate") + val updateApplyDate: String, @SerialName("routineName") val routineName: String, @SerialName("repeatDay") val repeatDay: List, + @SerialName("routineStartDate") + val routineStartDate: String?, + @SerialName("routineEndDate") + val routineEndDate: String?, @SerialName("executionTime") val executionTime: String, - @SerialName("subRoutineInfos") - val subRoutineInfos: List, + @SerialName("subRoutineName") + val subRoutineName: List, ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt index 4f64826d..9425c5f3 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt @@ -9,6 +9,10 @@ data class RegisterRoutineRequest( val routineName: String, @SerialName("repeatDay") val repeatDay: List, + @SerialName("routineStartDate") + val routineStartDate: String?, + @SerialName("routineEndDate") + val routineEndDate: String?, @SerialName("executionTime") val executionTime: String, @SerialName("subRoutineName") diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt index 942ac2a2..d4afe39c 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt @@ -1,11 +1,11 @@ package com.threegap.bitnagil.data.writeroutine.repositoryImpl import com.threegap.bitnagil.data.writeroutine.datasource.WriteRoutineDataSource -import com.threegap.bitnagil.data.writeroutine.model.dto.SubRoutineInfosDiffDto import com.threegap.bitnagil.data.writeroutine.model.request.EditRoutineRequest import com.threegap.bitnagil.data.writeroutine.model.request.RegisterRoutineRequest +import com.threegap.bitnagil.domain.writeroutine.model.Date import com.threegap.bitnagil.domain.writeroutine.model.RepeatDay -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutineDiff +import com.threegap.bitnagil.domain.writeroutine.model.RoutineUpdateType import com.threegap.bitnagil.domain.writeroutine.model.Time import com.threegap.bitnagil.domain.writeroutine.model.WriteRoutineEvent import com.threegap.bitnagil.domain.writeroutine.repository.WriteRoutineRepository @@ -17,11 +17,20 @@ import javax.inject.Inject class WriteRoutineRepositoryImpl @Inject constructor( private val writeRoutineDataSource: WriteRoutineDataSource, ) : WriteRoutineRepository { - override suspend fun registerRoutine(name: String, repeatDay: List, startTime: Time, subRoutines: List): Result { + override suspend fun registerRoutine( + name: String, + repeatDay: List, + startTime: Time, + startDate: Date?, + endDate: Date?, + subRoutines: List + ): Result { val request = RegisterRoutineRequest( routineName = name, repeatDay = repeatDay.map { it.fullName }, executionTime = startTime.toFormattedString(), + routineStartDate = startDate?.toFormattedString(), + routineEndDate = endDate?.toFormattedString(), subRoutineName = subRoutines, ) return writeRoutineDataSource.registerRoutine(request).also { @@ -33,19 +42,23 @@ class WriteRoutineRepositoryImpl @Inject constructor( override suspend fun editRoutine( routineId: String, + routineUpdateType: RoutineUpdateType, name: String, repeatDay: List, startTime: Time, - subRoutines: List, + startDate: Date?, + endDate: Date?, + subRoutines: List, ): Result { val request = EditRoutineRequest( routineId = routineId, + updateApplyDate = routineUpdateType.value, routineName = name, repeatDay = repeatDay.map { it.fullName }, executionTime = startTime.toFormattedString(), - subRoutineInfos = subRoutines.map { - SubRoutineInfosDiffDto.fromSubRoutineDiff(it) - }, + routineStartDate = startDate?.toFormattedString(), + routineEndDate = endDate?.toFormattedString(), + subRoutineName = subRoutines, ) return writeRoutineDataSource.editRoutine(request).also { diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/service/WriteRoutineService.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/service/WriteRoutineService.kt index bb8be610..7aada97c 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/service/WriteRoutineService.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/service/WriteRoutineService.kt @@ -8,12 +8,12 @@ import retrofit2.http.PATCH import retrofit2.http.POST interface WriteRoutineService { - @POST("/api/v1/routines") + @POST("/api/v2/routines") suspend fun postRoutine( @Body registerRoutineRequest: RegisterRoutineRequest, ): BaseResponse - @PATCH("/api/v1/routines") + @PATCH("/api/v2/routines") suspend fun patchRoutine( @Body editRoutineRequest: EditRoutineRequest, ): BaseResponse diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt index 3d659359..af87da62 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt @@ -10,4 +10,8 @@ data class Date( require(month in 1..12) { "Month must be in range 1..12, but was $month" } require(day in 1..31) { "Day must be in range 1..31, but was $day" } } + + fun toFormattedString(): String { + return "%04d.%02d.%02d".format(year, month, day) + } } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/RoutineUpdateType.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/RoutineUpdateType.kt new file mode 100644 index 00000000..e2a4620d --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/RoutineUpdateType.kt @@ -0,0 +1,5 @@ +package com.threegap.bitnagil.domain.writeroutine.model + +enum class RoutineUpdateType(val value: String) { + Today("TODAY"), Tomorrow("TOMORROW") +} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/SubRoutineDiff.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/SubRoutineDiff.kt deleted file mode 100644 index 102459f6..00000000 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/SubRoutineDiff.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.threegap.bitnagil.domain.writeroutine.model - -data class SubRoutineDiff( - val id: String?, - val name: String?, - val sort: Int?, -) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt index 65ac5b63..5d0658b2 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt @@ -1,7 +1,8 @@ package com.threegap.bitnagil.domain.writeroutine.repository +import com.threegap.bitnagil.domain.writeroutine.model.Date import com.threegap.bitnagil.domain.writeroutine.model.RepeatDay -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutineDiff +import com.threegap.bitnagil.domain.writeroutine.model.RoutineUpdateType import com.threegap.bitnagil.domain.writeroutine.model.Time import com.threegap.bitnagil.domain.writeroutine.model.WriteRoutineEvent import kotlinx.coroutines.flow.Flow @@ -11,15 +12,20 @@ interface WriteRoutineRepository { name: String, repeatDay: List, startTime: Time, + startDate: Date?, + endDate: Date?, subRoutines: List, ): Result suspend fun editRoutine( routineId: String, + routineUpdateType: RoutineUpdateType, name: String, repeatDay: List, startTime: Time, - subRoutines: List, + startDate: Date?, + endDate: Date?, + subRoutines: List, ): Result suspend fun getWriteRoutineEventFlow(): Flow diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt index c25ffc98..0212a5c6 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt @@ -1,7 +1,8 @@ package com.threegap.bitnagil.domain.writeroutine.usecase +import com.threegap.bitnagil.domain.writeroutine.model.Date import com.threegap.bitnagil.domain.writeroutine.model.RepeatDay -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutineDiff +import com.threegap.bitnagil.domain.writeroutine.model.RoutineUpdateType import com.threegap.bitnagil.domain.writeroutine.model.Time import com.threegap.bitnagil.domain.writeroutine.repository.WriteRoutineRepository import javax.inject.Inject @@ -11,16 +12,22 @@ class EditRoutineUseCase @Inject constructor( ) { suspend operator fun invoke( routineId: String, + routineUpdateType: RoutineUpdateType, name: String, repeatDay: List, startTime: Time, - subRoutines: List, + startDate: Date?, + endDate: Date?, + subRoutines: List, ): Result { return writeRoutineRepository.editRoutine( routineId = routineId, + routineUpdateType = routineUpdateType, name = name, repeatDay = repeatDay, startTime = startTime, + startDate = startDate, + endDate = endDate, subRoutines = subRoutines, ) } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCase.kt deleted file mode 100644 index 663bfa98..00000000 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCase.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.threegap.bitnagil.domain.writeroutine.usecase - -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutine -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutineDiff -import javax.inject.Inject - -class GetChangedSubRoutinesUseCase @Inject constructor() { - operator fun invoke( - oldSubRoutines: List, - newSubRoutineNames: List, - ): List { - val oldSubRoutineMap = oldSubRoutines.groupBy { it.name }.toMutableMap() - val changedSubRoutines = mutableListOf() - - val newSubRoutineNameCountMap = newSubRoutineNames - .groupingBy { it } - .eachCount() - .toMutableMap() - - for (oldSubRoutine in oldSubRoutines) { - val name = oldSubRoutine.name - val count = newSubRoutineNameCountMap[name] ?: 0 - - if (count > 0) { - newSubRoutineNameCountMap[name] = count - 1 - continue - } else { - changedSubRoutines.add( - SubRoutineDiff( - id = oldSubRoutine.id, - name = null, - sort = null, - ), - ) - } - } - - for ((index, name) in newSubRoutineNames.withIndex()) { - val sort = index + 1 - - val oldSubRoutinesWithSameName = oldSubRoutineMap[name] - val oldSubRoutine = oldSubRoutinesWithSameName?.firstOrNull() - if (oldSubRoutine == null) { - changedSubRoutines.add( - SubRoutineDiff( - id = null, - name = name, - sort = sort, - ), - ) - } else if (oldSubRoutine.sort != sort) { - changedSubRoutines.add( - SubRoutineDiff( - id = oldSubRoutine.id, - name = oldSubRoutine.name, - sort = sort, - ), - ) - oldSubRoutineMap[name] = oldSubRoutinesWithSameName.drop(1) - } else { - oldSubRoutineMap[name] = oldSubRoutinesWithSameName.drop(1) - } - } - - return changedSubRoutines - } -} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt index 16764bc4..bac169af 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt @@ -1,5 +1,6 @@ package com.threegap.bitnagil.domain.writeroutine.usecase +import com.threegap.bitnagil.domain.writeroutine.model.Date import com.threegap.bitnagil.domain.writeroutine.model.RepeatDay import com.threegap.bitnagil.domain.writeroutine.model.Time import com.threegap.bitnagil.domain.writeroutine.repository.WriteRoutineRepository @@ -12,12 +13,16 @@ class RegisterRoutineUseCase @Inject constructor( name: String, repeatDay: List, startTime: Time, + startDate: Date?, + endDate: Date?, subRoutines: List, ): Result { return writeRoutineRepository.registerRoutine( name = name, repeatDay = repeatDay, startTime = startTime, + startDate = startDate, + endDate = endDate, subRoutines = subRoutines, ) } diff --git a/domain/src/test/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCaseTest.kt b/domain/src/test/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCaseTest.kt deleted file mode 100644 index bf9bca5e..00000000 --- a/domain/src/test/java/com/threegap/bitnagil/domain/writeroutine/usecase/GetChangedSubRoutinesUseCaseTest.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.threegap.bitnagil.domain.writeroutine.usecase - -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutine -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutineDiff -import org.junit.Assert.assertEquals -import org.junit.Test - -class GetChangedSubRoutinesUseCaseTest { - private val getChangedSubRoutinesUseCase = GetChangedSubRoutinesUseCase() - - @Test - fun `세부 루틴을 변경하지 않은 경우 빈 문자열이 리턴되어야 한다`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - ) - - val newSubRoutineNames = listOf("AAA", "BBB") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - assertEquals(emptyList(), changedSubRoutines) - } - - @Test - fun `세부 루틴의 순서가 변경된 경우 변경된 순서를 반영해야 한다`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - SubRoutine(id = "3", name = "CCC", sort = 3), - ) - - val newSubRoutineNames = listOf("BBB", "CCC", "AAA") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - val answer = listOf( - SubRoutineDiff(id = "2", name = "BBB", sort = 1), - SubRoutineDiff(id = "3", name = "CCC", sort = 2), - SubRoutineDiff(id = "1", name = "AAA", sort = 3), - ) - - assertEquals(answer, changedSubRoutines) - } - - @Test - fun `새로운 세부 루틴이 추가될 경우, 추가된 세부 루틴에 대해 루틴 이름과 순서를 지정해야 한다`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - ) - - val newSubRoutineNames = listOf("AAA", "BBB", "CCC") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - val answer = listOf( - SubRoutineDiff(id = null, name = "CCC", sort = 3), - ) - - assertEquals(answer, changedSubRoutines) - } - - @Test - fun `세부 루틴을 제거하는 경우, 제거한 세부 루틴의 id를 제외한 나머지를 null로 지정해야 한다`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - ) - - val newSubRoutineNames = listOf("AAA") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - val answer = listOf( - SubRoutineDiff(id = "2", name = null, sort = null), - ) - - assertEquals(answer, changedSubRoutines) - } - - @Test - fun `세부 루틴 제거로 인해 기존 세부 루틴의 순서가 변경된 경우, 해당 세부 루틴의 순서를 변경해야 한다`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - ) - - val newSubRoutineNames = listOf("BBB") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - val answer = listOf( - SubRoutineDiff(id = "1", name = null, sort = null), - SubRoutineDiff(id = "2", name = "BBB", sort = 1), - ) - - assertEquals(answer, changedSubRoutines) - } - - @Test - fun `심화 케이스 - 세부 루틴의 전체 내용이 제거되고 새롭게 추가된 경우`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - SubRoutine(id = "3", name = "CCC", sort = 3), - ) - - val newSubRoutineNames = listOf("DDD", "EEE", "FFF") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - val answer = listOf( - SubRoutineDiff(id = "1", name = null, sort = null), - SubRoutineDiff(id = "2", name = null, sort = null), - SubRoutineDiff(id = "3", name = null, sort = null), - SubRoutineDiff(id = null, name = "DDD", sort = 1), - SubRoutineDiff(id = null, name = "EEE", sort = 2), - SubRoutineDiff(id = null, name = "FFF", sort = 3), - ) - - assertEquals(answer, changedSubRoutines) - } - - @Test - fun `심화 케이스 - 동일한 세부 루틴명을 가지는 경우`() { - val oldSubRoutines = listOf( - SubRoutine(id = "1", name = "AAA", sort = 1), - SubRoutine(id = "2", name = "BBB", sort = 2), - SubRoutine(id = "3", name = "AAA", sort = 3), - SubRoutine(id = "4", name = "AAA", sort = 4), - ) - - val newSubRoutineNames = listOf("AAA", "AAA") - - val changedSubRoutines = getChangedSubRoutinesUseCase(oldSubRoutines, newSubRoutineNames) - - val answer = listOf( - SubRoutineDiff(id = "2", name = null, sort = null), - SubRoutineDiff(id = "4", name = null, sort = null), - SubRoutineDiff(id = "3", name = "AAA", sort = 2), - ) - - assertEquals(answer, changedSubRoutines) - } -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt index b3fc33d4..945e50ca 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt @@ -146,7 +146,7 @@ private fun WriteRoutineScreen( ), ) { BitnagilTopBar( - title = if (state.writeRoutineType == WriteRoutineType.ADD) "루틴 등록" else "루틴 수정", + title = if (state.writeRoutineType == WriteRoutineType.Add) "루틴 등록" else "루틴 수정", showBackButton = true, onBackClick = onClickBack, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index 8f48c285..ad76f3a2 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -5,8 +5,8 @@ import androidx.lifecycle.viewModelScope import com.threegap.bitnagil.domain.recommendroutine.usecase.GetRecommendRoutineUseCase import com.threegap.bitnagil.domain.routine.usecase.GetRoutineUseCase import com.threegap.bitnagil.domain.writeroutine.model.RepeatDay +import com.threegap.bitnagil.domain.writeroutine.model.RoutineUpdateType import com.threegap.bitnagil.domain.writeroutine.usecase.EditRoutineUseCase -import com.threegap.bitnagil.domain.writeroutine.usecase.GetChangedSubRoutinesUseCase import com.threegap.bitnagil.domain.writeroutine.usecase.RegisterRoutineUseCase import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel import com.threegap.bitnagil.presentation.writeroutine.model.Date @@ -26,12 +26,10 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import org.orbitmvi.orbit.syntax.simple.SimpleSyntax -import com.threegap.bitnagil.domain.writeroutine.model.SubRoutine as DomainSubRoutine @HiltViewModel(assistedFactory = WriteRoutineViewModel.Factory::class) class WriteRoutineViewModel @AssistedInject constructor( savedStateHandle: SavedStateHandle, - private val getChangedSubRoutinesUseCase: GetChangedSubRoutinesUseCase, private val registerRoutineUseCase: RegisterRoutineUseCase, private val editRoutineUseCase: EditRoutineUseCase, private val getRoutineUseCase: GetRoutineUseCase, @@ -57,7 +55,7 @@ class WriteRoutineViewModel @AssistedInject constructor( when (navigationArg) { is WriteRoutineScreenArg.Add -> { viewModelScope.launch { - sendIntent(WriteRoutineIntent.SetWriteRoutineType(WriteRoutineType.ADD)) + sendIntent(WriteRoutineIntent.SetWriteRoutineType(WriteRoutineType.Add)) } navigationArg.baseRoutineId?.let { @@ -67,7 +65,7 @@ class WriteRoutineViewModel @AssistedInject constructor( } is WriteRoutineScreenArg.Edit -> { viewModelScope.launch { - sendIntent(WriteRoutineIntent.SetWriteRoutineType(WriteRoutineType.EDIT)) + sendIntent(WriteRoutineIntent.SetWriteRoutineType(WriteRoutineType.Edit(updateRoutineFromNowDate = navigationArg.updateRoutineFromNowDate))) } navigationArg.routineId.also { @@ -462,16 +460,18 @@ class WriteRoutineViewModel @AssistedInject constructor( null -> return@launch } - when (currentState.writeRoutineType) { - WriteRoutineType.ADD -> { + when (val writeRoutineType = currentState.writeRoutineType) { + WriteRoutineType.Add -> { sendIntent(WriteRoutineIntent.RegisterRoutineLoading) val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames val registerRoutineResult = registerRoutineUseCase( - currentState.routineName, - repeatDay, - startTime.toDomainTime(), - subRoutines, + name = currentState.routineName, + repeatDay = repeatDay, + startTime = startTime.toDomainTime(), + startDate = currentState.startDate?.toDomainDate(), + endDate = currentState.endDate?.toDomainDate(), + subRoutines = subRoutines, ) if (registerRoutineResult.isSuccess) { @@ -480,28 +480,25 @@ class WriteRoutineViewModel @AssistedInject constructor( sendIntent(WriteRoutineIntent.RegisterRoutineFailure) } } - WriteRoutineType.EDIT -> { + is WriteRoutineType.Edit -> { val currentRoutineId = routineId ?: return@launch val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames - - val subRoutineDiffs = getChangedSubRoutinesUseCase( - oldSubRoutines = oldSubRoutines.mapIndexed { index, subRoutine -> - DomainSubRoutine( - id = subRoutine.id, - name = subRoutine.name, - sort = index + 1, - ) - }, - newSubRoutineNames = subRoutines.filter { it.isNotEmpty() }, - ) + val routineUpdateType = if (writeRoutineType.updateRoutineFromNowDate) { + RoutineUpdateType.Today + } else { + RoutineUpdateType.Tomorrow + } sendIntent(WriteRoutineIntent.EditRoutineLoading) val editRoutineResult = editRoutineUseCase( routineId = currentRoutineId, + routineUpdateType = routineUpdateType, name = currentState.routineName, - repeatDay, + repeatDay = repeatDay, startTime = startTime.toDomainTime(), - subRoutines = subRoutineDiffs, + startDate = currentState.startDate?.toDomainDate(), + endDate = currentState.endDate?.toDomainDate(), + subRoutines = subRoutines, ) if (editRoutineResult.isSuccess) { diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt index 94eff5f9..c487dfc9 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt @@ -2,6 +2,7 @@ package com.threegap.bitnagil.presentation.writeroutine.model import android.os.Parcelable import kotlinx.parcelize.Parcelize +import com.threegap.bitnagil.domain.writeroutine.model.Date as DomainDate @Parcelize data class Date( @@ -54,4 +55,12 @@ data class Date( return targetValue in startValue..endValue } + + fun toDomainDate(): DomainDate { + return DomainDate( + year = year, + month = month, + day = day, + ) + } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt index aa95a380..b5711a6f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt @@ -1,5 +1,13 @@ package com.threegap.bitnagil.presentation.writeroutine.model -enum class WriteRoutineType { - ADD, EDIT +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class WriteRoutineType : Parcelable { + @Parcelize + data object Add : WriteRoutineType() + + @Parcelize + data class Edit(val updateRoutineFromNowDate: Boolean): WriteRoutineType() } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt index 832da7d1..040dff3b 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt @@ -72,7 +72,7 @@ data class WriteRoutineState( showStartDatePickerBottomSheet = false, showEndDatePickerBottomSheet = false, showTimePickerBottomSheet = false, - writeRoutineType = WriteRoutineType.ADD, + writeRoutineType = WriteRoutineType.Add, subRoutineUiExpanded = false, repeatDaysUiExpanded = false, periodUiExpanded = false, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/navarg/WriteRoutineScreenArg.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/navarg/WriteRoutineScreenArg.kt index b459d743..5498f86e 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/navarg/WriteRoutineScreenArg.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/navarg/WriteRoutineScreenArg.kt @@ -8,5 +8,5 @@ sealed class WriteRoutineScreenArg { data class Add(val baseRoutineId: String?) : WriteRoutineScreenArg() @Serializable - data class Edit(val routineId: String) : WriteRoutineScreenArg() + data class Edit(val routineId: String, val updateRoutineFromNowDate: Boolean) : WriteRoutineScreenArg() } From cced6a191b7c13e6a1569dd4623c0e6ed1eb0331 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Sun, 17 Aug 2025 22:14:06 +0900 Subject: [PATCH 08/16] =?UTF-8?q?CHORE:=20ktlint=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt | 2 +- .../presentation/writeroutine/model/WriteRoutineType.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt index d4afe39c..0d61678b 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt @@ -23,7 +23,7 @@ class WriteRoutineRepositoryImpl @Inject constructor( startTime: Time, startDate: Date?, endDate: Date?, - subRoutines: List + subRoutines: List, ): Result { val request = RegisterRoutineRequest( routineName = name, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt index b5711a6f..903bfda7 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/WriteRoutineType.kt @@ -9,5 +9,5 @@ sealed class WriteRoutineType : Parcelable { data object Add : WriteRoutineType() @Parcelize - data class Edit(val updateRoutineFromNowDate: Boolean): WriteRoutineType() + data class Edit(val updateRoutineFromNowDate: Boolean) : WriteRoutineType() } From a4e9670b3b5f64ad4fab42d5877c079bd9312ab4 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 18 Aug 2025 20:57:35 +0900 Subject: [PATCH 09/16] =?UTF-8?q?FIX:=20=EB=A3=A8=ED=8B=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=20API=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=82=A0=EC=A7=9C=20String=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B9=88=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=A3=A8=ED=8B=B4=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=80=20=EC=9A=94=EC=B2=AD=EC=97=90=20=EB=AF=B8=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/threegap/bitnagil/domain/writeroutine/model/Date.kt | 2 +- .../presentation/writeroutine/WriteRoutineViewModel.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt index af87da62..976d2205 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt @@ -12,6 +12,6 @@ data class Date( } fun toFormattedString(): String { - return "%04d.%02d.%02d".format(year, month, day) + return "%04d-%02d-%02d".format(year, month, day) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index ad76f3a2..46ac257d 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -463,7 +463,7 @@ class WriteRoutineViewModel @AssistedInject constructor( when (val writeRoutineType = currentState.writeRoutineType) { WriteRoutineType.Add -> { sendIntent(WriteRoutineIntent.RegisterRoutineLoading) - val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames + val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames.filter { it.isNotEmpty() } val registerRoutineResult = registerRoutineUseCase( name = currentState.routineName, @@ -482,7 +482,7 @@ class WriteRoutineViewModel @AssistedInject constructor( } is WriteRoutineType.Edit -> { val currentRoutineId = routineId ?: return@launch - val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames + val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames.filter { it.isNotEmpty() } val routineUpdateType = if (writeRoutineType.updateRoutineFromNowDate) { RoutineUpdateType.Today } else { From 6e1cc47e4e9bf0be8d31718ea9151bd89878d79e Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 18 Aug 2025 22:20:55 +0900 Subject: [PATCH 10/16] =?UTF-8?q?FIX:=20=EB=A3=A8=ED=8B=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=EC=8B=9C=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=EC=9A=94=EC=9D=BC=EC=9D=84=20=EC=84=A4=EC=A0=95=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=95=84=EB=8F=84=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=EC=9D=B4=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EB=B0=98=EB=B3=B5=20=EC=9A=94=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EB=93=B1=EB=A1=9D=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=84=20=EC=8B=9C=20startDate=EC=99=80=20endDate=EB=A5=BC?= =?UTF-8?q?=20=EB=AA=A8=EB=91=90=20=ED=98=84=EC=9E=AC=20=EB=82=A0=EC=9E=90?= =?UTF-8?q?=EB=A1=9C=20=EC=84=9C=EB=B2=84=EC=97=90=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writeroutine/WriteRoutineScreen.kt | 10 +++++----- .../writeroutine/WriteRoutineViewModel.kt | 11 ++++++----- .../writeroutine/model/mvi/WriteRoutineState.kt | 17 +++++++---------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt index 945e50ca..76c0de88 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt @@ -77,9 +77,9 @@ fun WriteRoutineScreenContainer( DatePickerBottomSheet( modifier = Modifier.fillMaxWidth(), onDateSelected = viewModel::setStartDate, - date = state.startDate ?: state.endDate?.let { Date.min(it, Date.now()) } ?: Date.now(), + date = state.startDate, onDismiss = viewModel::hideStartDatePickerBottomSheet, - availableStartDate = null, + availableStartDate = Date.now(), availableEndDate = state.endDate, ) } @@ -88,7 +88,7 @@ fun WriteRoutineScreenContainer( DatePickerBottomSheet( modifier = Modifier.fillMaxWidth(), onDateSelected = viewModel::setEndDate, - date = state.endDate ?: state.startDate?.let { Date.max(it, Date.now()) } ?: Date.now(), + date = state.endDate, onDismiss = viewModel::hideEndDatePickerBottomSheet, availableStartDate = state.startDate, availableEndDate = null, @@ -289,14 +289,14 @@ private fun WriteRoutineScreen( RoutineDetailRow( title = "시작일", placeHolder = "눌러서 선택", - value = state.startDate?.toFormattedString() ?: "", + value = state.startDate.toFormattedString(), onClick = showStartDatePickerBottomSheet, ) RoutineDetailRow( title = "종료일", placeHolder = "눌러서 선택", - value = state.endDate?.toFormattedString() ?: "", + value = state.endDate.toFormattedString(), onClick = showEndDatePickerBottomSheet, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index 46ac257d..905da1bd 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -457,20 +457,21 @@ class WriteRoutineViewModel @AssistedInject constructor( .filter { it.selected } .map { it.day.toRepeatDay() } - null -> return@launch + null -> listOf() } when (val writeRoutineType = currentState.writeRoutineType) { WriteRoutineType.Add -> { sendIntent(WriteRoutineIntent.RegisterRoutineLoading) val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames.filter { it.isNotEmpty() } + val noRepeatRoutine = repeatDay.isEmpty() val registerRoutineResult = registerRoutineUseCase( name = currentState.routineName, repeatDay = repeatDay, startTime = startTime.toDomainTime(), - startDate = currentState.startDate?.toDomainDate(), - endDate = currentState.endDate?.toDomainDate(), + startDate = if (noRepeatRoutine) Date.now().toDomainDate() else currentState.startDate.toDomainDate(), + endDate = if (noRepeatRoutine) Date.now().toDomainDate() else currentState.endDate.toDomainDate(), subRoutines = subRoutines, ) @@ -496,8 +497,8 @@ class WriteRoutineViewModel @AssistedInject constructor( name = currentState.routineName, repeatDay = repeatDay, startTime = startTime.toDomainTime(), - startDate = currentState.startDate?.toDomainDate(), - endDate = currentState.endDate?.toDomainDate(), + startDate = currentState.startDate.toDomainDate(), + endDate = currentState.endDate.toDomainDate(), subRoutines = subRoutines, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt index 040dff3b..a3fac0b1 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt @@ -17,8 +17,8 @@ data class WriteRoutineState( val repeatType: RepeatType?, val repeatDays: List, val startTime: Time?, - val startDate: Date?, - val endDate: Date?, + val startDate: Date, + val endDate: Date, val selectAllTime: Boolean, val loading: Boolean, val showTimePickerBottomSheet: Boolean, @@ -77,28 +77,25 @@ data class WriteRoutineState( repeatDaysUiExpanded = false, periodUiExpanded = false, startTimeUiExpanded = false, - startDate = null, - endDate = null, + startDate = Date.now(), + endDate = Date.now(), ) } val registerButtonEnabled: Boolean - get() = routineName.isNotEmpty() && - (repeatType == RepeatType.DAILY || (repeatType == RepeatType.DAY && repeatDays.any { it.selected })) && - startTime != null && !loading + get() = routineName.isNotEmpty() && startTime != null && !loading val subRoutinesText: String get() = subRoutineNames.filter { it.isNotEmpty() }.joinToString(separator = "\n") val repeatDaysText: String get() = when (repeatType) { RepeatType.DAILY -> "매일" - RepeatType.DAY -> "매주 ${repeatDays.filter { it.selected }.joinToString { it.day.text }}" + RepeatType.DAY -> if (repeatDays.none { it.selected }) "반복 안함" else "매주 ${repeatDays.filter { it.selected }.joinToString { it.day.text }}" null -> "" } val periodText: String get() { - if (startDate == null && endDate == null) return "" - return "${startDate?.toYearShrinkageFormattedString() ?: ""} ~ ${endDate?.toYearShrinkageFormattedString() ?: ""}" + return "${startDate.toYearShrinkageFormattedString()} ~ ${endDate.toYearShrinkageFormattedString()}" } val startTimeText: String From 4efe10280c807577a669168af7898dd3416b100d Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 18 Aug 2025 23:59:10 +0900 Subject: [PATCH 11/16] =?UTF-8?q?FIX:=20=EB=A3=A8=ED=8B=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A3=A8=ED=8B=B4=20=EC=9E=91=EC=84=B1/=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20=EB=A3=A8?= =?UTF-8?q?=ED=8B=B4=20DTO=EC=97=90=20=EC=8B=9C=EC=9E=91/=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EB=82=A0=EC=9E=90=20=EB=B3=80=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EB=A3=A8=ED=8B=B4=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20v2=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/threegap/bitnagil/MainNavHost.kt | 12 ++++++++++++ .../data/routine/model/response/RoutineDto.kt | 8 ++++++++ .../bitnagil/data/routine/service/RoutineService.kt | 2 +- .../bitnagil/domain/routine/model/Routine.kt | 2 ++ .../presentation/routinelist/RoutineListScreen.kt | 13 +++++++------ .../routinelist/RoutineListViewModel.kt | 13 +++++-------- .../routinelist/model/RoutineListSideEffect.kt | 3 ++- .../writeroutine/WriteRoutineViewModel.kt | 11 +++++++---- .../presentation/writeroutine/model/Date.kt | 5 +++++ .../writeroutine/model/mvi/WriteRoutineIntent.kt | 9 ++++++++- 10 files changed, 57 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt index d1e7f45d..eaf72d83 100644 --- a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt +++ b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt @@ -263,6 +263,18 @@ fun MainNavHost( navigator.navController.popBackStack() } }, + navigateToAddRoutine = { + navigator.navController.navigate(Route.WriteRoutine()) + }, + navigateToEditRoutine = { routineId, updateRoutineFromNowDate -> + navigator.navController.navigate( + Route.WriteRoutine( + routineId = routineId, + isRegister = false, + isUpdateRoutineFromNowDate = updateRoutineFromNowDate, + ), + ) + }, ) } diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt index c7deb42b..fe69ef9c 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt @@ -26,6 +26,12 @@ data class RoutineDto( val subRoutineCompleteYn: List, @SerialName("recommendedRoutineType") val recommendedRoutineType: String?, + @SerialName("routineDeletedYn") + val routineDeletedYn: Boolean, + @SerialName("routineStartDate") + val routineStartDate: String, + @SerialName("routineEndDate") + val routineEndDate: String, ) fun RoutineDto.toDomain(): Routine = @@ -39,4 +45,6 @@ fun RoutineDto.toDomain(): Routine = subRoutineNames = this.subRoutineNames, subRoutineCompleteYn = this.subRoutineCompleteYn, recommendedRoutineType = RecommendedRoutineType.fromString(this.recommendedRoutineType), + startDate = this.routineStartDate, + endDate = this.routineEndDate, ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/service/RoutineService.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/service/RoutineService.kt index 2d5d20ef..23b6c35e 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/service/RoutineService.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/service/RoutineService.kt @@ -18,7 +18,7 @@ interface RoutineService { @Query("endDate") endDate: String, ): BaseResponse - @GET("/api/v1/routines/{routineId}") + @GET("/api/v2/routines/{routineId}") suspend fun getRoutine( @Path("routineId") routineId: String, ): BaseResponse diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt index 4cc43d29..d01261c2 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt @@ -5,6 +5,8 @@ data class Routine( val routineName: String, val repeatDay: List, val executionTime: String, + val startDate: String, + val endDate: String, val routineDate: String, val routineCompleteYn: Boolean, val subRoutineNames: List, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt index 54c3e427..f0a911c5 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt @@ -36,6 +36,8 @@ import java.time.LocalDate @Composable fun RoutineListScreenContainer( navigateToBack: () -> Unit, + navigateToEditRoutine: (String, Boolean) -> Unit, + navigateToAddRoutine: () -> Unit, viewModel: RoutineListViewModel = hiltViewModel(), ) { val uiState by viewModel.stateFlow.collectAsStateWithLifecycle() @@ -43,12 +45,11 @@ fun RoutineListScreenContainer( viewModel.sideEffectFlow.collectAsEffect { sideEffect -> when (sideEffect) { is RoutineListSideEffect.NavigateToBack -> navigateToBack() - is RoutineListSideEffect.NavigateToWriteRoutine -> { -// TODO: 네비게이션 연결하기 -// navigateToWriteRoutine( -// routineId = sideEffect.routineId, -// applyDate = sideEffect.applyDate, -// ) + is RoutineListSideEffect.NavigateToAddRoutine -> { + navigateToAddRoutine() + } + is RoutineListSideEffect.NavigateToEditRoutine -> { + navigateToEditRoutine(sideEffect.routineId, sideEffect.updateRoutineFromNowDate) } } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt index 9e661e53..c350b862 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt @@ -73,10 +73,7 @@ class RoutineListViewModel @Inject constructor( is RoutineListIntent.OnRegisterRoutineClick -> { sendSideEffect( - RoutineListSideEffect.NavigateToWriteRoutine( - routineId = null, - applyDate = null, - ), + RoutineListSideEffect.NavigateToAddRoutine, ) null } @@ -85,9 +82,9 @@ class RoutineListViewModel @Inject constructor( val selectedRoutine = state.selectedRoutine if (selectedRoutine != null) { sendSideEffect( - RoutineListSideEffect.NavigateToWriteRoutine( + RoutineListSideEffect.NavigateToEditRoutine( routineId = selectedRoutine.routineId, - applyDate = "TODAY", + updateRoutineFromNowDate = true, ), ) } @@ -98,9 +95,9 @@ class RoutineListViewModel @Inject constructor( val selectedRoutine = state.selectedRoutine if (selectedRoutine != null) { sendSideEffect( - RoutineListSideEffect.NavigateToWriteRoutine( + RoutineListSideEffect.NavigateToEditRoutine( routineId = selectedRoutine.routineId, - applyDate = "TOMORROW", + updateRoutineFromNowDate = false, ), ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt index bd645702..0e137f93 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt @@ -4,5 +4,6 @@ import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect sealed interface RoutineListSideEffect : MviSideEffect { data object NavigateToBack : RoutineListSideEffect - data class NavigateToWriteRoutine(val routineId: String?, val applyDate: String?) : RoutineListSideEffect + data object NavigateToAddRoutine : RoutineListSideEffect + data class NavigateToEditRoutine(val routineId: String, val updateRoutineFromNowDate: Boolean) : RoutineListSideEffect } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index 905da1bd..fccff248 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -81,17 +81,18 @@ class WriteRoutineViewModel @AssistedInject constructor( sendIntent(WriteRoutineIntent.GetRoutineLoading) getRoutineUseCase(routineId).fold( onSuccess = { routine -> - oldSubRoutines = routine.subRoutines.map { SubRoutine.fromDomainSubRoutine(it) } sendIntent( WriteRoutineIntent.SetRoutine( name = routine.routineName, repeatDays = routine.repeatDay.map { Day.fromDayOfWeek(it) }, startTime = Time.fromDomainTimeString(routine.executionTime), subRoutines = listOf( - oldSubRoutines.getOrNull(0)?.name ?: "", - oldSubRoutines.getOrNull(1)?.name ?: "", - oldSubRoutines.getOrNull(2)?.name ?: "", + routine.subRoutineNames.getOrNull(0) ?: "", + routine.subRoutineNames.getOrNull(1) ?: "", + routine.subRoutineNames.getOrNull(2) ?: "", ), + startDate = Date.fromString(routine.startDate), + endDate = Date.fromString(routine.endDate), ), ) }, @@ -118,6 +119,8 @@ class WriteRoutineViewModel @AssistedInject constructor( oldSubRoutines.getOrNull(1)?.name ?: "", oldSubRoutines.getOrNull(2)?.name ?: "", ), + startDate = Date.now(), + endDate = Date.now(), ), ) }, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt index c487dfc9..09ed8c5b 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt @@ -40,6 +40,11 @@ data class Date( return d2 } + + fun fromString(dateString: String): Date { + val (year, month, day) = dateString.split("-").map { it.toInt() } + return Date(year, month, day) + } } fun toFormattedString(): String = "%04d.%02d.%02d".format(year, month, day) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt index f3e02062..90bc0789 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt @@ -15,7 +15,14 @@ sealed class WriteRoutineIntent : MviIntent { data class SelectDay(val day: Day) : WriteRoutineIntent() data class SetStartTime(val time: Time) : WriteRoutineIntent() data class SetWriteRoutineType(val writeRoutineType: WriteRoutineType) : WriteRoutineIntent() - data class SetRoutine(val name: String, val repeatDays: List, val startTime: Time, val subRoutines: List) : WriteRoutineIntent() + data class SetRoutine( + val name: String, + val repeatDays: List, + val startTime: Time, + val subRoutines: List, + val startDate: Date, + val endDate: Date, + ) : WriteRoutineIntent() data object SelectAllTime : WriteRoutineIntent() data object ShowTimePickerBottomSheet : WriteRoutineIntent() data object HideTimePickerBottomSheet : WriteRoutineIntent() From 647d4ed3316bacb25ca3b6e328d184992a8f4827 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Tue, 19 Aug 2025 00:53:33 +0900 Subject: [PATCH 12/16] =?UTF-8?q?FIX:=20=EB=A3=A8=ED=8B=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95/=EC=9E=91=EC=84=B1=20useCase=EC=99=80=20repository?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=8B=9C=EC=9E=91/=EC=A2=85=EB=A3=8C?= =?UTF-8?q?=EC=9D=BC=EC=9E=90=EB=A5=BC=20nullable=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,=20=EB=82=A0?= =?UTF-8?q?=EC=9E=90=20=EC=84=A0=ED=83=9D=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=9B=94=EC=9D=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=B4=EB=8F=84=20=EC=B4=88=EA=B8=B0=20=EB=8B=AC?= =?UTF-8?q?=EB=A0=A5=EC=9D=B4=20=ED=95=AD=EC=83=81=20=EC=9C=A0=EC=A7=80?= =?UTF-8?q?=EB=90=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repositoryImpl/WriteRoutineRepositoryImpl.kt | 16 ++++++++-------- .../repository/WriteRoutineRepository.kt | 8 ++++---- .../writeroutine/usecase/EditRoutineUseCase.kt | 4 ++-- .../usecase/RegisterRoutineUseCase.kt | 4 ++-- .../DatePickerBottomSheet.kt | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt index 0d61678b..7fd9cc74 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt @@ -21,16 +21,16 @@ class WriteRoutineRepositoryImpl @Inject constructor( name: String, repeatDay: List, startTime: Time, - startDate: Date?, - endDate: Date?, + startDate: Date, + endDate: Date, subRoutines: List, ): Result { val request = RegisterRoutineRequest( routineName = name, repeatDay = repeatDay.map { it.fullName }, executionTime = startTime.toFormattedString(), - routineStartDate = startDate?.toFormattedString(), - routineEndDate = endDate?.toFormattedString(), + routineStartDate = startDate.toFormattedString(), + routineEndDate = endDate.toFormattedString(), subRoutineName = subRoutines, ) return writeRoutineDataSource.registerRoutine(request).also { @@ -46,8 +46,8 @@ class WriteRoutineRepositoryImpl @Inject constructor( name: String, repeatDay: List, startTime: Time, - startDate: Date?, - endDate: Date?, + startDate: Date, + endDate: Date, subRoutines: List, ): Result { val request = EditRoutineRequest( @@ -56,8 +56,8 @@ class WriteRoutineRepositoryImpl @Inject constructor( routineName = name, repeatDay = repeatDay.map { it.fullName }, executionTime = startTime.toFormattedString(), - routineStartDate = startDate?.toFormattedString(), - routineEndDate = endDate?.toFormattedString(), + routineStartDate = startDate.toFormattedString(), + routineEndDate = endDate.toFormattedString(), subRoutineName = subRoutines, ) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt index 5d0658b2..7b5ec078 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt @@ -12,8 +12,8 @@ interface WriteRoutineRepository { name: String, repeatDay: List, startTime: Time, - startDate: Date?, - endDate: Date?, + startDate: Date, + endDate: Date, subRoutines: List, ): Result @@ -23,8 +23,8 @@ interface WriteRoutineRepository { name: String, repeatDay: List, startTime: Time, - startDate: Date?, - endDate: Date?, + startDate: Date, + endDate: Date, subRoutines: List, ): Result diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt index 0212a5c6..41d3ab1f 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt @@ -16,8 +16,8 @@ class EditRoutineUseCase @Inject constructor( name: String, repeatDay: List, startTime: Time, - startDate: Date?, - endDate: Date?, + startDate: Date, + endDate: Date, subRoutines: List, ): Result { return writeRoutineRepository.editRoutine( diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt index bac169af..2b6369e2 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt @@ -13,8 +13,8 @@ class RegisterRoutineUseCase @Inject constructor( name: String, repeatDay: List, startTime: Time, - startDate: Date?, - endDate: Date?, + startDate: Date, + endDate: Date, subRoutines: List, ): Result { return writeRoutineRepository.registerRoutine( diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt index 872e7606..d9322c9e 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt @@ -95,14 +95,14 @@ private fun DatePickerBottomSheetContent( var currentMonth by remember { mutableIntStateOf(initDate.month) } var selectedDate by remember { mutableStateOf(initDate) } - val lastDaysOfPrevMonth = remember(initDate) { - CalendarUtils.lastDaysOfPrevMonth(initDate.year, initDate.month) + val lastDaysOfPrevMonth = remember(currentYear, currentMonth) { + CalendarUtils.lastDaysOfPrevMonth(currentYear, currentMonth) } val firstDaysOfNextMonth = remember(currentYear, currentMonth) { - CalendarUtils.firstDaysOfNextMonth(initDate.year, initDate.month) + CalendarUtils.firstDaysOfNextMonth(currentYear, currentMonth) } val currentDaysOfMonth = remember(currentYear, currentMonth) { - CalendarUtils.getDayAmountOfMonth(initDate.year, initDate.month) + CalendarUtils.getDayAmountOfMonth(currentYear, currentMonth) } val prevMonthButtonEnabled by remember(availableStartDate) { From 16cbba8b8fd34e0fd271a8da6edd808cb5cfb0d0 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Tue, 19 Aug 2025 00:59:23 +0900 Subject: [PATCH 13/16] =?UTF-8?q?FIX:=20=EB=A3=A8=ED=8B=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95/=EC=9E=91=EC=84=B1=20API=20request=EC=9D=98=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91/=EC=A2=85=EB=A3=8C=EC=9D=BC=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20nullable=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/writeroutine/model/request/EditRoutineRequest.kt | 4 ++-- .../data/writeroutine/model/request/RegisterRoutineRequest.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt index e743de3e..08ba222d 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt @@ -14,9 +14,9 @@ data class EditRoutineRequest( @SerialName("repeatDay") val repeatDay: List, @SerialName("routineStartDate") - val routineStartDate: String?, + val routineStartDate: String, @SerialName("routineEndDate") - val routineEndDate: String?, + val routineEndDate: String, @SerialName("executionTime") val executionTime: String, @SerialName("subRoutineName") diff --git a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt index 9425c5f3..feba22e1 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt @@ -10,9 +10,9 @@ data class RegisterRoutineRequest( @SerialName("repeatDay") val repeatDay: List, @SerialName("routineStartDate") - val routineStartDate: String?, + val routineStartDate: String, @SerialName("routineEndDate") - val routineEndDate: String?, + val routineEndDate: String, @SerialName("executionTime") val executionTime: String, @SerialName("subRoutineName") From a0af44eb347ae3d8917c982b042412d99c664ddd Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Tue, 19 Aug 2025 19:51:21 +0900 Subject: [PATCH 14/16] =?UTF-8?q?FIX:=20=EC=84=B8=EB=B6=80=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=88=98=EC=A0=95=20-=20"=EC=84=B8=EB=B6=80?= =?UTF-8?q?=EB=A3=A8=ED=8B=B4=20=EC=84=A4=EC=A0=95=20=EC=95=88=20=ED=95=A8?= =?UTF-8?q?"=20=EC=84=A0=ED=83=9D=EC=8B=9C=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=ED=95=9C=20=EC=84=B8=EB=B6=80=EB=A3=A8?= =?UTF-8?q?=ED=8B=B4=EC=9D=B4=20=EC=B4=88=EA=B8=B0=ED=99=94=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-=20=EB=B0=98?= =?UTF-8?q?=EB=B3=B5=EC=9A=94=EC=9D=BC,=20=EB=AA=A9=ED=91=9C=20=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=EC=9D=84=20=ED=95=84=EC=88=98=20=ED=95=AD=EB=AA=A9?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20-=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=EC=9D=BC/=EC=A2=85=EB=A3=8C=EC=9D=BC=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EC=8B=9C=20=EC=84=A0=ED=83=9D=20=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C=20-=20ExpandableContent=EC=9D=98=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20Routine=20=EB=82=B4=20routineDeleteYn?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/routine/model/response/RoutineDto.kt | 1 + .../bitnagil/domain/routine/model/Routine.kt | 1 + .../presentation/writeroutine/WriteRoutineScreen.kt | 13 ++++++------- .../writeroutine/WriteRoutineViewModel.kt | 7 ++++++- .../block/expandablecontent/ExpandableContent.kt | 4 +++- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt index fe69ef9c..d78c6a20 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt @@ -45,6 +45,7 @@ fun RoutineDto.toDomain(): Routine = subRoutineNames = this.subRoutineNames, subRoutineCompleteYn = this.subRoutineCompleteYn, recommendedRoutineType = RecommendedRoutineType.fromString(this.recommendedRoutineType), + routineDeletedYn = routineDeletedYn, startDate = this.routineStartDate, endDate = this.routineEndDate, ) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt index d01261c2..3f9eee0d 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt @@ -9,6 +9,7 @@ data class Routine( val endDate: String, val routineDate: String, val routineCompleteYn: Boolean, + val routineDeletedYn: Boolean, val subRoutineNames: List, val subRoutineCompleteYn: List, val recommendedRoutineType: RecommendedRoutineType?, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt index d5098eff..358a68d2 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt @@ -1,6 +1,5 @@ package com.threegap.bitnagil.presentation.writeroutine -import ExpandableContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -33,12 +32,12 @@ import com.threegap.bitnagil.presentation.common.flow.collectAsEffect import com.threegap.bitnagil.presentation.writeroutine.component.atom.namefield.NameField import com.threegap.bitnagil.presentation.writeroutine.component.atom.selectcell.SelectCell import com.threegap.bitnagil.presentation.writeroutine.component.atom.writeroutinebutton.WriteRoutineButton +import com.threegap.bitnagil.presentation.writeroutine.component.block.expandablecontent.ExpandableContent import com.threegap.bitnagil.presentation.writeroutine.component.block.labeledcheckbox.LabeledCheckBox import com.threegap.bitnagil.presentation.writeroutine.component.block.routinedetailrow.RoutineDetailRow import com.threegap.bitnagil.presentation.writeroutine.component.block.subroutinefield.SubRoutineField import com.threegap.bitnagil.presentation.writeroutine.component.template.datepickerbottomsheet.DatePickerBottomSheet import com.threegap.bitnagil.presentation.writeroutine.component.template.timepickerbottomsheet.TimePickerBottomSheet -import com.threegap.bitnagil.presentation.writeroutine.model.Date import com.threegap.bitnagil.presentation.writeroutine.model.Day import com.threegap.bitnagil.presentation.writeroutine.model.RepeatType import com.threegap.bitnagil.presentation.writeroutine.model.Time @@ -77,8 +76,8 @@ fun WriteRoutineScreenContainer( onDateSelected = viewModel::setStartDate, date = state.startDate, onDismiss = viewModel::hideStartDatePickerBottomSheet, - availableStartDate = Date.now(), - availableEndDate = state.endDate, + availableStartDate = null, + availableEndDate = null, ) } @@ -88,7 +87,7 @@ fun WriteRoutineScreenContainer( onDateSelected = viewModel::setEndDate, date = state.endDate, onDismiss = viewModel::hideEndDatePickerBottomSheet, - availableStartDate = state.startDate, + availableStartDate = null, availableEndDate = null, ) } @@ -215,7 +214,7 @@ private fun WriteRoutineScreen( ExpandableContent( expand = state.repeatDaysUiExpanded, - required = false, + required = true, iconResourceId = R.drawable.img_repeat_days, title = "반복 요일", placeHolder = "ex) 매주 월,화,수.목,금", @@ -271,7 +270,7 @@ private fun WriteRoutineScreen( ExpandableContent( expand = state.periodUiExpanded, - required = false, + required = true, iconResourceId = R.drawable.img_routine_period, title = "목표 기간", placeHolder = "ex) 25.08.06 - 25.08.06", diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index fccff248..1c3abdaf 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -302,18 +302,23 @@ class WriteRoutineViewModel @AssistedInject constructor( } is WriteRoutineIntent.SetEndDate -> { return state.copy( + startDate = Date.min(intent.date, state.startDate), endDate = intent.date, ) } is WriteRoutineIntent.SetStartDate -> { return state.copy( startDate = intent.date, + endDate = Date.max(intent.date, state.endDate), ) } WriteRoutineIntent.SelectNotUseSubRoutines -> { + val toggledSelectNotUseSubRoutines = !state.selectNotUseSUbRoutines + return state.copy( - selectNotUseSUbRoutines = !state.selectNotUseSUbRoutines, + selectNotUseSUbRoutines = toggledSelectNotUseSubRoutines, + subRoutineNames = if (toggledSelectNotUseSubRoutines) listOf("", "", "") else state.subRoutineNames, ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt index 05e71436..9b3c530f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt @@ -1,3 +1,5 @@ +package com.threegap.bitnagil.presentation.writeroutine.component.block.expandablecontent + import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image @@ -123,7 +125,7 @@ fun ExpandableContent( @Preview(heightDp = 300) @Composable -fun ExpandableContentPreview() { +private fun ExpandableContentPreview() { var isExpanded by remember { mutableStateOf(true) } var isChecked by remember { mutableStateOf(false) } From b09b684c9eca32b48898aae43284c8dc4d84ef1d Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Tue, 19 Aug 2025 20:06:39 +0900 Subject: [PATCH 15/16] =?UTF-8?q?FIX:=20=EC=84=B8=EB=B6=80=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=88=98=EC=A0=95=20-=20NameField=20TextStyle=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EB=A3=A8=ED=8B=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EB=A3=A8=ED=8B=B4=EC=9D=98=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=EC=9D=BC/=EC=A2=85=EB=A3=8C=EC=9D=BC=EC=9D=84=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt | 2 ++ .../writeroutine/component/atom/namefield/NameField.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index 1c3abdaf..99cf16a5 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -250,6 +250,8 @@ class WriteRoutineViewModel @AssistedInject constructor( repeatDays = repeatDays, repeatType = repeatType, startTime = intent.startTime, + startDate = intent.startDate, + endDate = intent.endDate, subRoutineNames = intent.subRoutines, loading = false, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt index ccef1de8..96ea17eb 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt @@ -35,7 +35,7 @@ fun NameField( modifier = modifier .fillMaxWidth(), singleLine = true, - textStyle = BitnagilTheme.typography.body2SemiBold, + textStyle = BitnagilTheme.typography.title3SemiBold, decorationBox = { innerTextField -> Column { Row( From 5ec59407b6a58647ed04a2f821b37c584221c262 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Tue, 19 Aug 2025 20:57:23 +0900 Subject: [PATCH 16/16] =?UTF-8?q?FIX:=20=EC=84=B8=EB=B6=80=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=88=98=EC=A0=95=20-=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20"selectNotUseSUbRoutines"=20->=20"selectNo?= =?UTF-8?q?tUseSubRoutines"=20-=20TimePicker=20=EA=B5=AC=EB=B6=84=EC=84=A0?= =?UTF-8?q?=20=EC=83=89=EC=83=81=EC=9D=84=20=EC=A3=BC=ED=99=A9=EC=83=89?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values/themes.xml | 2 +- .../presentation/writeroutine/WriteRoutineScreen.kt | 8 ++++---- .../presentation/writeroutine/WriteRoutineViewModel.kt | 8 ++++---- .../timepickerbottomsheet/TimePickerBottomSheet.kt | 4 ---- .../writeroutine/model/mvi/WriteRoutineState.kt | 4 ++-- presentation/src/main/res/values/styles.xml | 4 ++-- 6 files changed, 13 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 1d6f30b7..f525cee5 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,7 +1,7 @@ - \ No newline at end of file