From b0edd55e93e0a8c55286f15ec9f1589249f1226b Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 09:49:11 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20asset=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Entity/Enum/RepeatType.swift | 0 .../Skyblue.colorset/Contents.json | 3 +++ .../RoutineCreation/Contents.json | 6 +++++ .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_date@1x.png | Bin 0 -> 914 bytes .../routine_creation_date@2x.png | Bin 0 -> 1715 bytes .../routine_creation_date@3x.png | Bin 0 -> 2577 bytes .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_list_icon@1x.png | Bin 0 -> 837 bytes .../routine_creation_list_icon@2x.png | Bin 0 -> 1681 bytes .../routine_creation_list_icon@3x.png | Bin 0 -> 2499 bytes .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_one_icon@1x.png | Bin 0 -> 597 bytes .../routine_creation_one_icon@2x.png | Bin 0 -> 1072 bytes .../routine_creation_one_icon@3x.png | Bin 0 -> 1537 bytes .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_repeat_icon@1x.png | Bin 0 -> 734 bytes .../routine_creation_repeat_icon@2x.png | Bin 0 -> 1489 bytes .../routine_creation_repeat_icon@3x.png | Bin 0 -> 2120 bytes .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_three_icon@1x.png | Bin 0 -> 694 bytes .../routine_creation_three_icon@2x.png | Bin 0 -> 1320 bytes .../routine_creation_three_icon@3x.png | Bin 0 -> 1849 bytes .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_time_icon@1x.png | Bin 0 -> 733 bytes .../routine_creation_time_icon@2x.png | Bin 0 -> 1305 bytes .../routine_creation_time_icon@3x.png | Bin 0 -> 1869 bytes .../Contents.json | 23 ++++++++++++++++++ .../routine_creation_two_icon@1x.png | Bin 0 -> 672 bytes .../routine_creation_two_icon@2x.png | Bin 0 -> 1238 bytes .../routine_creation_two_icon@3x.png | Bin 0 -> 1732 bytes .../asterisk_icon@1x.png | Bin 238 -> 327 bytes .../asterisk_icon@2x.png | Bin 370 -> 571 bytes .../asterisk_icon@3x.png | Bin 497 -> 794 bytes .../clear_icon.imageset/Contents.json | 23 ++++++++++++++++++ .../clear_icon.imageset/clear_icon@1x.png | Bin 0 -> 470 bytes .../clear_icon.imageset/clear_icon@2x.png | Bin 0 -> 695 bytes .../clear_icon.imageset/clear_icon@3x.png | Bin 0 -> 1103 bytes .../divide_line_icon.imageset/Contents.json | 23 ++++++++++++++++++ .../divide_line_icon@1x.png | Bin 0 -> 1947 bytes .../divide_line_icon@2x.png | Bin 0 -> 5501 bytes .../divide_line_icon@3x.png | Bin 0 -> 7776 bytes .../Common/DesignSystem/BitnagilIcon.swift | 9 +++++++ 43 files changed, 225 insertions(+) create mode 100644 Projects/Domain/Sources/Entity/Enum/RepeatType.swift create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/routine_creation_date@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/routine_creation_date@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/routine_creation_date@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_one_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_one_icon.imageset/routine_creation_one_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_one_icon.imageset/routine_creation_one_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_one_icon.imageset/routine_creation_one_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_three_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_three_icon.imageset/routine_creation_three_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_three_icon.imageset/routine_creation_three_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_three_icon.imageset/routine_creation_three_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_two_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_two_icon.imageset/routine_creation_two_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_two_icon.imageset/routine_creation_two_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_two_icon.imageset/routine_creation_two_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@3x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/divide_line_icon.imageset/Contents.json create mode 100644 Projects/Presentation/Resources/Images.xcassets/divide_line_icon.imageset/divide_line_icon@1x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/divide_line_icon.imageset/divide_line_icon@2x.png create mode 100644 Projects/Presentation/Resources/Images.xcassets/divide_line_icon.imageset/divide_line_icon@3x.png diff --git a/Projects/Domain/Sources/Entity/Enum/RepeatType.swift b/Projects/Domain/Sources/Entity/Enum/RepeatType.swift new file mode 100644 index 00000000..e69de29b diff --git a/Projects/Presentation/Resources/Colors.xcassets/Skyblue.colorset/Contents.json b/Projects/Presentation/Resources/Colors.xcassets/Skyblue.colorset/Contents.json index dc8f7e62..dc347a17 100644 --- a/Projects/Presentation/Resources/Colors.xcassets/Skyblue.colorset/Contents.json +++ b/Projects/Presentation/Resources/Colors.xcassets/Skyblue.colorset/Contents.json @@ -16,5 +16,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "localizable" : true } } diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/Contents.json b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/Contents.json new file mode 100644 index 00000000..918fbc7e --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "routine_creation_date@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "routine_creation_date@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "routine_creation_date@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/routine_creation_date@1x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_date.imageset/routine_creation_date@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..97b7e05f30bed7a20a3dc7663a0a34b75921fd19 GIT binary patch literal 914 zcmV;D18w|?P)d716gTYu`^)>G1`Tk2VRi;IjB?G!)~^g@ z8?EbHoPDFUmhpJuj?+8lhV2|0LT7+!3Ea2MbZSD@EP4La)(X-%N_$?yuIGi7N0U!x zOgLp`nRCMfl3SMCS@`Vit*%MD4Fzz3F$eI(xYK4r$d2Jbvmw8{;^dc{Z1HS*6`JDH zFh1c5n0hS1Z+?Mf=E!;RYL;bwgPmO)otJ=PZ5N&3$MZq^K42hFg?;4p!}77c!ULFm zHYUKcMol_G$S;2ZW(hdlbm33fo?nDWECzdP-@+z5j6W)X^FdI$W@CP71x|v4^^L|x z&PF%{n!#xSYbCH~E^iV!nk&yY`LNnr(mWc)A`1xi4_xheWP_lvCu2Pq%-G+_8$R08 zC8-t!T8nEyBwIn{3zFudE4B5aY>zp~v*&LmZR8iH=;l#Fj%Ne*F zU*W5IM!NpLLTi>4ob#&~q{^_HTYv(}^i1X^SRH2#hdG6xuipYs#0^gpChUC7!NJ$> zz?v**AG%%Uk8Y5r8`KD$oDmLxY*yV!(HCvXJ1p}Qka&g0nX25Ab~38Hon zw4_tj{u#9j2FY*$^McV4b;=SbAaSP4vg4HcTid3{q`K0JE`fob7npvBg}PA!PttNN z1-znYl>hdj0ke$?T|%s{*UXDb3TcM%Bo7v+P?sKL3|Qk)Tjyg7XW{O^??vT zL&2E%m2{if5^G|oib7jTr`vXSyEAjI=iX(e+hwLZvqkCtl6H1)&pmVQy>mY9of3#h z@13hU=gLI@j#tnL;iu;M-@5<}(sSq9N5Ei+5Mpvd#^w*aeJ)CW3K0>;bI!BE_(v*^ z!8RbAWbu*_I)SmXJyYKdMd{zb06?p0(%?zLolF#Q6(YhpyM@c`-Gl?*Sn~5{H7FOd zKvV-xf1C?c^2r%}Lm$q36|oo@g$R(xT7(gHVHp3^2P*jzAZdbC8p5?6L~He~>}#6R z(vo$>*YC}bEsy^yEYVP#4jU_EfEXMnCQx#+VlB2wnTpX$Dg|9{y|L-`rP;sD{M2uw z*zdB}$f*eqGvUKejjac89%sg4H}R7W0d&227?gB!Q``KNZ=n(5U7esEdik;TrQ1J( zwX*8h#lVN=c=hUT_`v69vN+1~u!Zq{lUU&(7(345r?Zt6;EoO$VbQ{ z$DM&@gu^KcmXn2Mg^(=8Ur#oZ{+|%&qhQlWnQupd$&+Fr)uCA-#CTK-5d(jXrS|q} zBpwIr!IJ69Sj%}4Zz0vU=Oj%eG*&DD#i)_!?ZG&1fm193INT)hSg!nFQFGP`vQ8>5 znqrB~wgdY$wY?RHqMG*VswU!o>7O|R`0^`F*q@xNL3<+SCbIX zi0V@>iuy>-8{d}yq5e@dnJi@(qdF=JlGC99XiN<@ZkvX?U}--@8BZ?|^z-|mH8W7pByjL#B=n}fXxx>`Wkg+*W4!tXN}axMAe_7QDC^3`xo2PuVUkC2Onk5ZuGa`~|~ zXRYWfD^|!4R<2*B6JVW`5o$sR#A8#9%)*6&udLeHvH82!0g2~!`^vTq?3X_-6bICC z_?1BUO*Q~sQWfeLn8@l1C-uERpP<_@Wr}4<7fKI(DvQLCT9PN|t$txKG>UcfjA|{uW z+$6*?!iY_Yl1^c`J1CD={K)@20VU%v@4JXJ)#0)l5rLM!|3&JJcRzz!_=P~dH5gQ( zG?WutOUv?F#tbRKqqr8P|L~Pv#er5EFDT94^_6u?30JSk_#$)j5a&&JN^Vf@H15Pf z>E7NVOV?6eqPWNjny+6<;0uunN)$foZSl6CHWQ2NDw2*Lq7b81C4!LEu=w+%HWPb& zI@_QgYMfq@PP}{S`pw!G4YjzL(jACM^+9!vK+NI_`hk#XeW^b%YrB+iC2Vt9)=aUU zE}7Kokxmyn(TT4QVFtKYM53L7I8K@Ce^-X7jhzf*Uqh3C`vC9Vv{!JfTZv2Ycp1wI zirJ~M5;#)%IunoZFkAEgaNj_3crVla{|o#s&Q)`q%XiJQSTY>({)Pc3*;4Xe34}OM zsn9!lb(n#2gfizc*kCwu#T$^q91Ra?bn%&qlWK}L6Z;H8j$y@U;^{$-aOW1`JSXJv zZNJ9x#R?Ut;>2si&00009a7bBm001F4 z001F40Y#QEU;qFB0drDELIAGL9O(c600d`2O+f$vv5yPKWuAdM={&P=qf;jMyVS*Mp3mvBDO%Brai>D^%nUraCNAYo8?PCoC22r zg1EQdsvHUh_Q4DSw?Ui2G*ZccU0B=NN@WSMR$nAVa%cM9tVmJh@@u(-17&}}O5EAy ze7u=`^WK|TGVqba-5V(>#d#^g0LctsQfEv~J|6wqH1vqX-C09Qeg%N(SgwiXS&WIR zOE0}`f)6n8iJ`gcNe1$^%Oxuo%MP2v?^@6UhW_=*G-K?1ZJC^lQ+^1EgHNDvE;hKN z7mINNdIZCfsV`JFHt7l zZsmff!3S8&3YGFn5JHFdFVDDMqlNx)gSFE^;7DNH1TM$%^F;Mmv}hEL{q)D+#?IoBxt^KNK!{DkEwF6)m%{AvM9~fZEPw-QuDwQ%H z+db+Oa{R>?fQQ_lSUa1C;*+)nq=QS|jR@%jN=S|1T!Y+y7pwD)>>dd`3_QLI)E)-E zgfp)J+_+Vzx9XMjZ&5fU`4RX?{?Y%S7wkM%g_;|7IQ_XH%m8#Qi^k-*;FzV`rn$=2LGRZeYPXkzFBz5J+E=^*wFbiwxh z2aq)%R?ex?Ygox%k08pQ$|(zp9_fuc^C0l?>k1~{Q~%V7vOAbBidlf3w!XqL}2n%E*8K7x*{_ zY4;?Mbs)fy35N?Gk|%9KfGg!`zP)h6HxNfaO`?I{Xe6xfy5H_WCok|&2yLN$^|^@V zegy{BZlNtZgYsY@3^Z^IQwY%QY_92d!pC%AeIXFJT&2v=kwd_vQOD)T=jzK=^G(}KC76NJC#&O1 z2K^zsN@R18Ll+|dXz7{~$}@v!BhMYzDQLDY4n=KI0+5wP%RU@X?dGD=BH0lw*=gN01?;fm-4 zeh6l<2Jj{;q5(hxV!ILlfO-U6LIK(5DpO-XHP-UUx28DlfRG^m2m!UIL8E3d&iO0Q z1+rL#lC=TFA&F;^`3C!qazT1d8F;{Z)4zoTdkm?g*|jkg?dR~6eAr>{RsN>Bj5$j#W1&p zW`IYihc^Ckry?8z-4n2GwZEc}Hy`a24nd9G%FMO(eG)|O{zB!P@+w*Ds!mkD^}l|h z*3Z1aB~Nl@OV0|rw6>uHQkRYuEUvWoxD^_#erB=JG%{L#f91)(4yuLfm-V>xRFaV7 zJS05Fg3!xud~G`i-O`SbB|4ie@9AQ7rK=58Jlxo^B&FRJ=t(ysVw`|hM21h`ea|<% zPC6+%Vt^OWSv7e#8y!PfIB(EEQEc6_M0D6)2&AN&HM|UkDvtah>8jKzO-CiIbueMn z($x+#s@&k4uPa4ch8_!2j{VOi+RB53ObsCuf%2nKNh62SJAr0CDmQ!2QP{DO7gSoe zbiDH5IL!2fs^+g z{&qqcku2oj%K)7~t2~J>Z=_V|_`Ho{0(1&_(ob%Dran`nw0KlGk7V%aubpl?6(%$61yBZ(e98%s?Y> zD>L8pp5pS8mU+-9t`<&p9agV@*1qFhm$|sZhvxp0tX{|N1QrfAy-vN~4C;BiMTnI( z@%9NCCC&1nGH9fGLE|jVi-nj8JwW+GEqpYNejNF7ji69P_}Qb)vOuT!IO?0`A=N&8 z0K6cW$Z+e_Pb?|qRqy~QLN`=g`poMsb%#>s<72KuDu*gsjUXqY@J7SVpu9`hkVZ~q`1zI%tK@+VAR%p{qAtg#sj;1g zD(HUdklg(TDrjGR><%S8h6$u&`c!X!nTDrKS($oWh42~mwzj6;I~V*4C~fz?>%&J5{Qw^6>$ zEPcE#?5n&4*X*#SYrgR5xrd^~!IrTE;mquN12RkLBR$a;s nB6fco$L5-)6>O`v;ePXfu8U5`H_egX5lj6*?d&;8ead43K0=T>e0<_m(a zn{YZ$fKtr}3PpZ4!8y|g|MP}@ZyxHd5h&H;+H9eUMcZX;kG}-99)n?GA#b|YWe-}5 zmI??~H<_%E)MR=k8%s4 P00000NkvXXu0mjf7-)OW literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4460d2d03598b7f3d77a0665fb3550caa97c21e GIT binary patch literal 1681 zcmV;C25$L@P)_fVT$W6r_m`Ai64FNIr;KTsM!>k_J?{ENphLs zG6@37;ZK2@LG@GZli+ZDKDk~DGxHb2%-4t1MDt3L>kjebX?{CMYZ1b@L{{Z;n2|aI zBM&ActGt$Bm}kV1Jy+BtzT~a(3vEL@;CsVWoeLOXSw!M?S=w&K`7Hz zF(xyt!&-$Sj_uuI5#`vlm6t`ld-r!L-+n@7-v;VDH9#U~)5?MU3RSI;92u9?BIFB~ z<}Z9(d(WNPP1e9>T2a^?>n&O)WN{(#ZuG`C(Vv|o|F5~A{`GD$db;cVOD=i8&JwM& z$$FzF+VA-S$$u8q&St`O3szu`TH63jUixYxTG${y?!9SR{N`b#OcSaJ#5jPlzKiVr zeQHS=@4J8xUHV_)|@e4<>=!>q#_WAR-KJ`tR6SmEUF) z=Kw>CH-3qi&&;WLQgw^tD=P@Z#*6%#PZ}7+>&cS{z$!t38)5I?rU<|o%LT^BGz^|S zX35d?0_+_ zi40!R@{NZ|Ce5uImC~&N^e@?rf|1;ZgRydtcLzqSsmXg85F&4pM>-;E8I?z~RJw9s zJ$D@0P8~;g(27FJq9NWLyhO`@GJRjxeSzAERe%*_V&#@QmOlF-;tT8p+nV;tvgud= zSSeBUC$LbBz9k0u0ZzO#6Bz_-7+g6rj&;<^vvUhuB$z#GXFMEGO8+8YQadF;83r~A zIv)h#Njr!+lk_fxsZ1rt0r5Vs$5;lH{^*U(~_3pbVmh-EX1;j-s)u#LuY-%A-$|H`Z8{_&N@yUHl2;U~g;)nb%um&rY2_i#wv^B`CLW8dsEFTzuJeZ@3CoJW_w&p2!Bk5= zYGch5X~ogmIb3BxI@BV(M^O`*lu|+&p};Tz+6RQ4UsBt&`ZQ=yTu5sG2Lvl5@?F&V z{+mG=oK#4mm1AlUpKzQMZl}#KtQf{_d*>E33Uz$miMFk#wc;hCJ2t-{X8E*j;VqYUkc_*d9wIGSQ=dB_499m z3FjckyCmM*=g6CU^Jy_Ll0STfi^)})iE)ggs;e+zRzR>OPLAB`C&Ma`UvYw8;o#&U bi@tvWIX()LwlGm_00000NkvXXu0mjf+W8yC literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@3x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_list_icon.imageset/routine_creation_list_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..30358a29bf9322e4e83454f41963c311a6b99a71 GIT binary patch literal 2499 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 literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_one_icon.imageset/routine_creation_one_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_one_icon.imageset/routine_creation_one_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b3b1b656c884c7075d4f4285c011b2eb43108208 GIT binary patch literal 1072 zcmV-01kd}4P)F5ZJ=W@9n_@Hfp*=#nlx3?dcc3IIh3yU^?i-nSUX#uX|EV^ie zmIbC#*{D+c(`+3ZMo__`&>hFg6-(nbb<_mP zisi}5ffoUV65qtTd#-$AkzeITyV$m~5e_FGO~OfMXD7P4x^z1hi`Cjbi0~HmI+aS> zaub$6=guK&5`;zE?_jBwZ-Bu1cQoNY(ZZ~@kV&UycKNj(gugWk%PFk>7CA-f4ht@G!DI~;uG zRV@S&2L<-7Yw;mr0s$a%@Dd*qghayDA5#dSaf?$xPFl!#C09-}!3Angs0xyrS~@=^ z%)yZQlgq&v>5sCG&y~GcDPs#J5_YYfug*mih^(AkwZIS|2SW}XP-C&EQLyH;W6p6F zW~8kJN`}t~vI_&&);?&wgIX>D_ovbv zf`Tb5v9RH{)j-^gwu1@$s8?0L&Qy=)d@-7zGf10Wf?q z21y>{*Ypsgo11G3AOUHDL!pR4KYf`#elIiuJ(NX6alfm}w-<}Wj=>{55zOX#a+19R zh%+QGT^J66NAT?D5LdoU#G}w6EeW=So|TEx^whj-mwKIqCg8Q2_p9A# z&j58fl(MGB$0t|WLMz>`D0nO!neN2dwc)T#i>DGOSKK(*b;f^8^j54xjv$qeVk zW>^0UVwtR|uX3^E94iXGm|5>uIrY%f(*yDley$AjE}F+N!&$%VneFTAgU>!)fcM^= zwqEm}#k-%w)YMxb6HVlbsmce6u*LTEJp%&+FG3_TS-f|2bObV?Zc8y$6$MjU?&M z`;bHrT2VTet6hIKN-CAAFqQ_cb2xB0`FZ~lD56)0$A+)avY7`D;fQRrPl6uA*I;+| zNAl|%W$%4TY94lh+RHxj=%Clpd21_anJ%ZBIQgy3?(Prp9hvp(qa(#JPEpDbBB%k1 zpTJ{S=)HT(C1P#%)tPV^G_8J3gNu-@f>i3T1HT7o6HP_dleEbnehh~?Thgu>Lk5b9C1d32vXgHvL^bY@P`S9a^ zeDk0Pca&z9zW5U4qbK=qOK>vj-6@&NaCbLUq$T!Wd+*p$OQRCe&ZQxvBrQ35TAE|< zK1ha}dhUjv>;`}xo$YJQ@gfP5>FMb%8{^tqW)pzc3M{Gl(YB_{W^DwUR* z%n8VXn(%4ob;-_yhA=Dhlnw{1_docsr02zB^4hd`tQVqlam4)=Yf}x(c`7`Wu9H0n zVWGRrx9C3aQT0#@?V@jY>L~ygn_u>pQ? zsE^dV4|Nb4kNnjU$CqlDvQ3>`%d2ex)dS7k(q&?qx&)J1uOPOoXgo#`vl|<0b)DI6 z_`Gx|G!Z37X`7U#p)b(LY1)Px@#p7H(38X%v;l}1ZS{QhgQzyJ9|-j$=vXF&?&J_uiv`KGznr{I); z7VzwQOZjh}nFjTK=Y&`1z3RxTjsHlG;l&4qw&DDh4z35VS2(i2L;KRVxG|pp5HyBn naglDC*Y>C{koNKmS~mXzKV~73XQ~hm00000NkvXXu0mjfOI+TU literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/Contents.json new file mode 100644 index 00000000..a5a863c7 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "routine_creation_repeat_icon@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "routine_creation_repeat_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "routine_creation_repeat_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@1x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8737c0b6cb55169ba38dc4419ef8c5b5d54fc2f8 GIT binary patch literal 734 zcmV<40wMj0P)<+f6tp1m1)-)&-~%89Ex|1a5tnp^k`T!e zAr;sTwtR`>^)j>PJ91}VT-ZG6?)GMP-@Kif-6agr`k+gF3q=B&(0HgdDq<(X&B2f? zx2tR~NSmJ$2(NV?`)pr<%;SgKHBu<2?^4yw*PnE@rFbx~jVXeKJk6}q2L1V{*I4l@ zOdrh|8@DV82mTMTrwZ#9CV=W_w0BPv`#DTkEPj>GBkBHD5yGRdC+2DIcG0aX{CLxX zchDO>nUHt*RYZci!|fL8pHE=B1p6YfvwmcZ*p;X8?Rf*s>+?v>#{#0U@4zpwnCd?E zUN()wfszQhI19)Hb2l!qZCE5(XgFNA_n@O4?eE=?{Mv&wu9jTi&hZvvQEI!E`Ttev z@R&>HKMtlXIT(R}JT-5Jhml@(Ou*4*t`*s~T6-?mLPC9Cg+%9F-P znHGkU%*hjS?`Mz8^w=k0B1x_gNeD%8Q2!Fnczy(O2s(0V^5P2dPIWx+O8?XarK&tG zg1R4;RPLNy@yY7&{CwOlY8D@7u=nB!%g=IVC8?ZWn&Kp=GIzlqS)ON5aah|If;p}u z5M_%`GCt9nWJ{Qac5DLpt}sb%707*qoM6N<$f-e+G)Bpeg literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_repeat_icon.imageset/routine_creation_repeat_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2e14a2225c7b292df49deec30d7aac5abdc0a1e7 GIT binary patch literal 1489 zcmV;?1upuDP)c?TtI$fWN-E`0%Bej9N*XDrK(9T7O1)NrORpu@TnfsymALd8g%gKT zVUFpgPI{=6hia)w)v7#fFqqih-TvQr*Vx8}SwjT=q7yAudYNNN>BCW)XhF!kFt}5|T%GNGgT7n1uz_ z;jMb{ybIPJ_khQ`9S>RN<+J~~de}-X)tIpJvo|h6+?H#c4M~M*KjUa)UdfY@bT$&l zNuWg7qv}vtl>3@;k?H$frRsmxfTLM*@F&hOICFj1)ZOYgZ=Av%7n3sLcf2Meecm8) zP6RWdJFvatt;%B@9RkwYjbLI>^v5D?&_Tiz)W?X6@A+<(+|Cld3J=p;LVe5g&Ohf{ zL1tl6%=ZaR;8{X^geA8O&IpB1VsJTpRQ9%;F4?`~5X?Pl{MyE1*r>vT2I8 zfAO;IcvEN36Cw5yO+@6f5xr>D{VTjG$>pH5>BzIJ^ATMM3q3gq5+Qu(37Dnfz+KE1bvq6im&jZ;Xc;2F_kU?i$%AV8!?;p$vMo@Sj812h|| zy8$R%qwqv=!$tmC$(Yl%!V~QHe<^c13H=}1j37-5aMah=_H-j`?6eW-Rhn9c$O!#2 ziq1g7<1LdC@7On|R&GkxEorDrSJ#aP*LZzIw1LO1!7v`#+9`5}uISr){~-n`937-H6Lm zu|w0YmHA>6$DwB^f_H9Sz<>zf^PVwv4&Y+JmdGe0CNz0m-u&}o)AZPtF0_lpXWfU= zNYiHN%^>rYHul5=BX4Pr*MhOdQWaP zTY(`(Sg$VYfu&$$i;m35ypS$`KRDn&J4V#81W zciaY-=)j;wZwCJmqtl?b7CUiwx(eA^w994j#9_7g9Dv7Lt|cono^;5^EIJ6j@I2ti zXd5?hO>tqLLZ`sRV+zmLYHT1H{S`W(6EgZ)*^!$Iz7Mfq%qq`$SCzp|MMK3I|CQ&! z@n#LH79O8xUe#k$yxrh9L)GqYaw_*PCewn rb(Ij0-B4-0(sD2-2YK@WRa)VHumXQx%f%jOn%uw1!{_91g+FVOM2oMBcN(dg&f-hLFyGjFYPhOl?y@q0Zk9RQaDi+ zqPRzTGNn=_D8f|%fq=wGObm&=)Aya-ao3K2#5-$~)c#2J+UuG1&fA&yYi3uF0r>@) zSTE3IuT68&V}CGCdffl(+ADlIoLJGQ$6D5nrxPjB9;}M@Y8Y(H~?MmU8TxOI%C+n1}MAdT~S{4>%xZD|k{kn}7 zG&^`)e6yvpwSK_W;6_eyiP1VIxpNUps1S0NFUFl}J<=^!1q_{yB0Z!If(13ry=ta% zzg4a9hQmFl!+;vd-I8Z2_m_H*R&YYn7=JxQok_MBp@VUs61VZ@4XFhdPNx&I-O*E0 z;A)8RkV;Y)1rN0g{-NgTAW2_Pml6)qoc77Il1@^WG92ORVat=$|Ig1cgsqnvX6)=L z54Ou@VoMwjS=6OAeU!C0j@feHwjC+1N%d+0S@=V?a3~`ihOFC@7pXiwz43}7QHwsB z-c2DBTjD_78ayddEdR^219_fWqhemt`eKoC_cthPA`YEWI>?eLRk7`8G<4pZ7%YAM z=f_oAf9X)DaM-0n4Op^PjI#Wr0xjQtQ~Dkqv1#~|cQ%!SAkYi)k2h%L?z-v~sEaL# zL58OZEN0mqXOM$K3v~SA9pxW#yJ!ppczK zT+MqI7fX%QpmF--@j2-5K;8J!cWCMM8+DAz-@?#I2wYtO&C7?r%%Cy3`|I50bDK`Q zog}^`$zdfPxJVYp#f213vrHn#WGSU~7C&wgHOi8Tqr z5ci(I!U2-5W1|Gb<18%=LXO4TuhkXX4)pGcu#z5By~hMj!6I>ZO%an9`t9|W z%DI+EC?yKE+S_2Q`bO-E|77L-aaes7y!A#6#Z^6YZnz{E zO)>8%YnOjqpjUsD9|Hn`Ti~~Pes&-2qX$=(Hwh^W;b@SjwplorOe`3NS3PP82S7(e z-^a^V4Q>V;4+4aMv;g57g$gTbn}st;1|S})$G*>ZRTWMc^dIg~uXmGCY601zMKhL~ zKnsZSczJl#Bg`&e2?SG1I0#}*{4mxy8II;N7Jtf6K$ODW6KPoHZJ>0QLC+Q5G`fX3 zbTR-|qZa$d`^|ijCPDn>dhsjt7Z%J}zcC7Q?JYk^)dGD2%hx-9H|((yT)@ zUkp~#-~uJ^<~kSdM1CNu`67E+FN{nKP&=vAg&6vVy*G&o4cnVlouq)QFaDQV(8dRSghE+Rt4uFNMv0D8@1cF?rq);$ZBX7P4@(@Ddz3)+xInah6De30epAngYw!noMT(fC1z5j?r7 z`+C?cJ1kfo-@(7aXZ*HzEUFapvYPR5)bOd7Vu252Y%n;8XFuo% z!AYSh|1#ZXLGM_;`
&@lDUE)IM(*mU}&8Z4LVHWq1<0S~TZ8%Ff)1R+is!clO9 zVF^hyWI%rl(;3Zzp*1qmPhsIWH0RFE8whDXN9=GC8>Kzk0g`iJW)I?@iL^VbVFeFX zp*-OWjZ6?*uve^n%PkAyGnE=;g!k`f!ej6)YRR~EZ+I48+zv60s{!!6Q=6e!mY- zb3s_FXcVxP9hgnCQrzt0ph1Z4cOsZ}#uiFC)D>nJGP>)Bz8s~EV0|LCcEP4}B;+*) zSTq2qZs73tv2e{|+e+nR^zxS_>MjK?N6=Z^BBit(oB18N1h1vIH|V){`(Uvm53?oe zQbuuu4yqo6#fsEr6Y+6O95QUl&Qq64+8@9b!c~O|-LD+=onxJ?XRf zP?{TAo|hMRs9mBC2e+dSpAetw@1K{SKx0oRFPVSsRNCQjC^!^57p zQ?+`tzVf{Vy0+0vNe-LN3-|u_v&iRo=RbvVjz!0KU)sC)qdgmxj*i!UC<_QQ!;fKz z2c572OL_BZl>0@F@(Cmejd$F}K(NxCQu@`2(1m!7r>>~F5gPR#o^r&y2x)HQX;#Doq^J^13~d(%en{OwEDa<`4ydWw zC{vKi?5r3D4@9jB_VMo8grxC>v|lOm^PTT=-*@j`5F~P4SKr_NlS>lRL18&$7%>Jt zMNzw+*XYUCVcKA^Sgr$N1%R27bzKDX5!LnM@G+omm-FPJ&H(Z-ldz|tS{Q|DgWxK` z5{8@PN{5-n%!I|;6j*~1Wf(A6JsIpDv{-Tie&$2qlI>LJk%Lo_aTtd1;^hYH?tYU|BTY>TN~z@3(qNfP1{|jhJ3C)stK$>!Cp><%0@>^Z z8Bk9WMdwAim^N!!CVVDXv-w`Q;~PAG_EZ36E*ZmHn(VIeMT3QgVTklOl>cO6z$zNu zxv7=4EDP3FpNRO?>o?G$^rtA&_~j(Fw8ELp0@Uu^2VOr8UZj*CJgldFOWhM`ROV0Q z(mg)O)8y#|K@a-Z2&mq_`UnoCYI%D;#zhj%cY=UZa7#a3W#jL%Vrn=yY`IW4l$O zim$^A5*d-Rg|ljPd^|4JYbNoD>Nok^>|XL|Q5}F;ZHV1IV13-jF~|BLPwXDawD~B5`Zg z-l(|5)NK^!&}^h4)gTd97>J5fZ*O1@xQC=^<=tiG;dR%){nmEdkCe^szV-U+nR)Z( z4Zt%F0}q@yp^f`|=7P=ad;BFfd%*^nFAoh1}vURB^O&gBefcQ`RM2$DDGoCeF^%*R5(0(e|PuKTg@J5D3Dw_ zJTiI-T{;DQhebz5$JlP}j^1e0*XX8YmsU}n22Tnb7RV@#hUx@T3V}YvTN?sslzcRL z>U8Zu)g~9bZ9)Wy54;L<829HoYu41^o zPa?Smc|v|*Zi~40_;D2$&VJf-Je~dt{x19jSFU{T$+{>^Po=KC!!5Y~po9|xLLx7i zoP4u#?g(&PP5+&75=xd8$NqtiGQ}Q1Nhqz5Yp2Qrt|qTntP~6ep-?Em($W`|aSDO2 zzFr1#Scr~3zaIi12wBImS~i=loRc`>#tpr4Pf&D5gzh+f##s8lXpXE4T)tTA3ZJBwxLoP+M`#nP6~->bVv;%HWr zc(0)0=4IR?Y~h&-T@Z_X0N-I;I{A*=?6xSS885kzTr7h76t7%|IN2JK{i1QW^AOA+y%VkryV}27I*_+Z>f@L2D#juI6ixekI!Aq&dzO$ z#-mb>H4;RBl$gow?utfG9+v!4eaTN)0JoW-Kq~Lubh)&l#jKU}^`zlkSMBN~25p-; z{RCaRm`YubyYE%CK61G`MmT)zKE~rKcqv@Sb^~MVGVVc^YVX%|7O>kCfA~jpj3}9d6H1r7k!RK+Mh2l^8tW1b{8Zq=c7FzbAnXj#V+mhv?e2rDa^x4cbOv@SpMf eHc1ZEA*&CU7p1^oaCbr_U@IRrru3=P`3 zDFhf-ucpRei7ZLrXtX~h1nw082^-o|02&(VJJ}4NNtzb8HyTTz5F^k6$W%rV^`T}2 zZ%W{3H10D+;d;qFTZM)|+P*a90VnCJ+sQSyH5`fTIV8YL zLi>@?(DSO@?rlrpzP?zT&Ky}D$J88lySLWp1lf&ITxe%u85*{Z+c~eUz^qvsazd~I z;QQ=*g?eHG>I#eqdtoJEiVKlQY@`&bT2a$WbBHKFBw&TX_e?Q6Zw^=;*R2|5Pyx`6 z9@#Fz4I4K8clYk?8`TFayENK#0PPK0n&TpYS-Y){pj1FkENErRiv%X^y5guhJ3HlP z8Sy@Nn7>}#SP^&>W8{1m6u|<9!y(wwvmF9~7vcW>`!GHIBm6S+D{Kt}!0&$^hEIJ3 z*{lI>L5W6kVj?y8WWS8E?|?-R!<{^z^(8 zsv)grSXkbcfrkl&6RL#e8{fb0^@_IH*?-~MJ?w$ga#?ydI}$SmN$p{X2@G znt1*K`zlXwZ=BSvrewpyojd;>oTm>nJ^fR|VabZM4IWZ6aQZ07F?)19@1@$160+oo z_)~(%)<)y&{(Y}4G6QyOtY!7Xlkhnz;;Dpt13RpJByctEg`j9Go4_m}dvLbvtz_ph z8jh?4n;|E6F?Gq6;Dcr|a?2+0!Gj4IQ`dq`;TnyaV zFLrQBLHJBJjNc!v->@-3d#)AQA3uH!_wL=ZJa&s71b6r9h>U{|)ky?R$qW=@X~eV8 zLe9-cX610#RyiJXipcX%QeX`{qu>I5zt7f9&(ugToaWVanNJbt$6+P=ipX?y=u^BQ6&ni0z0Y(MvMQ!T zxf_1`(U7#?awbW`EJV>6JxjyU1KA$12uLncRK_W$ig=oGeS<2N-K8~P3q83v(x0TA zL!baI#wAaF0nNZYdNaV=nh72Sr7UvB)`d$p^Lk&!B)}t=T8HOZLci%y!Px zEIWF7WVx0zDR=T;G-#T|U)g3sUt~8qF!aHS26Gzf-PNmmT2|t58w7JGolY6m?Ur?E z4K-!9+zQAu50Yn2S|6)HBofPt6*w_oPgthr%mbpnlb*&f7MJg?2#QYXaE-ux<0e9}) z%Bz!3^A(sr+KJPyx2@+3Pt3I|$g(}*xdCU!R6#ET%!uZZVW140~t z<%OarPj^e+d0<%ZVtELCynNSftC literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/Contents.json new file mode 100644 index 00000000..3a832765 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "routine_creation_time_icon@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "routine_creation_time_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "routine_creation_time_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@1x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..ba951cf1a4da335f83d2df3d9b2b089bfd8c6bad GIT binary patch literal 733 zcmV<30wVp1P)X1^@s6D=Y3@00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPTM^6C@&_@`7R{D)E>~ z1X`NX(7&|G+QaPT&uL9y-tIH4A*(jDA~^zI^KO_ zO7Yk!6YUB)hbUmkif&v;@uhd4dl&cXduxE?{DP1q=BM_XiM^py3;iyt^tG-)6WaS`zZWs_xBt0Z2pn-y%6`4UHOjAbOJL@Vrk-7}2 z>^0B>w>U(EBy&YiPZ=@I5ttdKC!-$4MCIZ}9Q43U&H%qbL`2A7i_;`@badl$$$@hL zTVKCGxAQmLh_M`}$xVX>GPye=S+;6x)7F{^4#KF=H#Cd+>o+GRSY~pt(*kkY^*5pt zsjI@Qx2vGht`gfnzLVo|k=Uf?|y4zvrNp zGT^#aa!>4a;ffk)PRab@rpf6`yNyRe%9?q9b{iBBOMp>nrs3E&K)0)|edQraC;-Y_ z;148LJLwh*clBIU97Ymg?AG%VM3HJ_KrGNALE9+h6A;n@k+RI?nd(p_z>qAJ8%0F< z7!pFF+!QT+!(5%QqU4VxzwG>JGM+UJ={gk{k;9*V zg3~vzp>=lF{gz!=gqaWLAtn~u+DDh|)z<@5`E?i(*S6v99|t{^XHKoxpn$ZSvVC>y z5x_%-9&0cnj@|0}T}!1v1t{@x$5^`b2tZ86F5gfbDCz}ZV=>A{-A3*T@cb4r=GrD0 z9$Ko92#?O+JpsnEKMi^8zKT0xrKYaJ)bw>219@-R4jlnf3Q{D~pa6#gxgS@qDHdky z1%3!`mg9B20#1(VEzkd-PIAjt1$ZVz5Iv_<>? zv9T$L`APfA??55hj~7ElaKO6*VkRP1cTaq`+;|0wH#CjvJ~V3kf$1DGI?djbIW_5=?lKE0^#@4>L3E4@*3AqGSV5`aa7wQhT!XhLM@ z>=YcW#IOGea5N$Hln_ZuOyD2;H9g`HvQ9k1SJeWfDi~VR)^-GTU=!;G2oLd$1)|={1p{9vOC? zmiCSB^Pt}>m5;Va^+T8_NVBap!PXx1+x~{$k=@-rqU)uJU2;I@M7kQ39=e#L`&{m} z$?Vu@xiF~mOsD|9jHXlJ4Hz>tG)feU4Jz z(Y6#=RBy8stmtl-E7_s0wnqM23BCfQrALld_S7q41gG-cr9{84(R<_;_mu()pvf-> zI^E>6kdU6?$%yhi8MVT`@<1`(Ngr>F5ZfDp_7NcclTS(Hmbv^v6%_I>5J=sTHT0GK P00000NkvXXu0mjf3Rh(y literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@3x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_time_icon.imageset/routine_creation_time_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ecce813d115fb4ea0782d38c5bbcd5d1f676760a GIT binary patch literal 1869 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 AH2?qr literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_two_icon.imageset/routine_creation_two_icon@3x.png b/Projects/Presentation/Resources/Images.xcassets/RoutineCreation/routine_creation_two_icon.imageset/routine_creation_two_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..697f5059bc8038ef92eca4bafe664a7c1c207253 GIT binary patch literal 1732 zcmV;#20QtQP)B6-kx^7Nnv`sG^Ox4GX+UnikJo?mzZ8evIw0ea8>@q-Z?j87u$X^SbBG z5dF_!a+z2x<~uxm5+mx_N+y?6I2H~^W7uk%?lTNeTGNF5Gt#T8t2^W}t_uziN53Ny zQXr=UQ=A2zlKWFtF4+;x2@iSlH%KMQ1xF$=zYv*O`Oznc2cMXzjGf~3 z7^j`4G_RABEHjp32h|27HRw7A<;Ia<&B;l+$V_E@w)4aSLE0n+#mTvj1j`8d8g(?n zbTy>OT4z@E;kHNQy5W?@o}p@~%Twu#qX0#N)IG@R&KQ60`4bNx{CUsTrz@H?Mz+(U zu|s?;#5~fc-Iun4Wx+q&J!WhVY6Vn-rN_$zLu`%NOxGX;O_v@k@?qQ&IkI2Ue+U2Z z<0tOizklx!>p;;63&EGjWzI*>(br#nL1UvAsUwqoZ`f#?Y$|$X<&PPXz_Ti%tdSmGux-0z_z>i__7+DL5 z9UFa}u3cXsmFUENY)!#J=+mSSm~o+qsVncBVoO<);t{CkW^xFk&5RTTGll8?uyV^hc5>i_ zs@Lr9?v@=pbLK^QR{oYUgfLxJnsmlb3V|6SwOD@Y#4POs7>+$un4V;h+%#!NEuMOS zKuauupVq$SpzJqr)7(IleLp|7nP~LYqW$gdpOqIMXiuRdqLJR?`T|xp;P3b=1QFT= zd?-M?%>PWaU`?$I1hmTJW!#An6@br{mX8?O!YWKPa>37Be$}?N8e5}j2D@50arJZ8!~!|j_hXxzJaEfI zqXF=eEGi1Qc75JGzs-{^u9WBDN@OPm7tKaMAt*3bauu1Iz@)MD27~C09}W+XAQ!vRaL1^Qmh`VSHa1P4Z6%tfoArk2!y0=p zxZ%fdzA;fI7AEXcVNsQyo}Lu8`HCYP8JQuQK5nDT&3e%)(MBQf$;SH$XvVIR$dW?>TPqt68l z507j%+YG2wGB!fu>Ssz?x6Hh-mRy;@XGiP9^?eJ&Bq-&2!hi~pku zy=A7Q=O7(nTdleZ1pL%2(u5O*>&0c$q;UA$g0z9AmR$M^S~FJA!7+?HCi|r=9Ypfd zr5DuIPMQbRkhwQ??3m|t;Ui9Ef>DUb$;98(1`w440l#S>%|>U4Wt*n~Y$w7hf>%4K zBo^2PEm?QQsY|IQ7T5|-CU0)Zq2Hn|L@t6~srFDy#kBPJvG)S*7IhK$e``I4SWz%0 z6)=r%5-nIP);gzV_s)%VV>iIStmW$EYSgT>Zr@(s(U>QwgRugYlXD%FmdWJGf}BDI zI?ucrPvFN(eB7YlLAk{k`c%IN!i!;$mhnJ aY5xKh-M%o0QtYV!0000LAfm#7{0Cr#r9>azB2$2B{ z2XV9d+6he!>|Knx=;lX=$yDU2}`Jg`A<{(+U!eNe2OgG9|(r8lB13Qt{fOR*+Fq iW5RVAc$G?+<@N;)n^obWgwL4(0000$tJs-1n?nE zlS~ x-NIhaDXC8^T-nok5RYn5CxUMW8Rmw+@c_59Fs}HKG>QNK002ovPDHLkV1ix4PSF4W diff --git a/Projects/Presentation/Resources/Images.xcassets/asterisk_icon.imageset/asterisk_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/asterisk_icon.imageset/asterisk_icon@2x.png index 1e2f0c238424a1adc8e53a68ae8ac5277172e5a8..1e6212315c1c6d9858fec9f7bb71e8ef9ac951f0 100644 GIT binary patch delta 530 zcmV+t0`2|s0=onuiBL{Q4GJ0x0000DNk~Le0000O0000O2nGNE0N{5$_>mzsf6z%p zK~#7F-BinQ!axjNIn3ZAB{&s?R1i}^;tIeg;1s6_B> zp&Vwq1Mr6T_oyFmWr*lV=cSh`Q%utURX+#x8;N79v=5NTK=qDZJvXJLe@1*9c4MQ7 zs3>(+M3g21qpFJI5OeQki=$NwPLFmxQjV*m0I<>`C=smze2X4Bg`-Vj{VDFM5QsO3 zqMfV^xhYa%qqhUpv&?ma9H}DFO2Ly4&B$zrnBN|PuW5mrqVhB3U3NH38VLkA=esL# zs^`_J*txbmO9a5dUh3BpXfW#FY_$LcOxGW;zKkS_keyCeVcV4%s3h>y8Rs7U_r1C= zSwux0eX$|u+}xd_k@Uc*E4>mMdNWfl*Jx{>&#wAFrXyr3qU#n`9eK`iOxeGZAJ?|K U{7FOxTmS$707*qoM6N<$f-J7@1^@s6 delta 328 zcmV-O0k{6U1o8qQiBL{Q4GJ0x0000DNk~Le0000D0000E2nGNE0Q7&B(vcxGe+NlK zK~#7Fol?!!0Z|N1=6nmw`8rSn9n5q<2Q2&?8>0eLfSnyGF*;Df(}5)$&rM`7KP=>& zO!D4Ma_{6kq92T_F8y%0vGl;}^|pxrQMGj}o~ppWCP#THf2J~fO|%?=;KU6#%3=fhVO7*3I6FBc}wL#9%e;6pzl_- a6uJZdAY+>mWMNkT0000=yf7$g2s|3K=>C2{j?==wGj)9( zQUx?Zh)+&D{P}_5MOM`UqpPm2f96yY4mE+t$B%0=0_GOx22=sf5Zc*!PYt0NLYvXQgf&>HJL1^E+I{Z`DaW< zaE`edHu$cl({Dx#tCffMDM7N5cLd3h4X}ADHOG7lV!tp3S^yhFtYK>;e?ykFnTO%i za_PfJfak9W=S-J8bMoCg8&g%BSeYayOLOB7Vk3;grO6>~kkN46kq%!P z9xC*fa$qII$H$*E8RMcZ-y!m1l3darcz76E%yw|d5oN#_vRDXx%Wp!Ia!=jdWL8d& zjV?SJm=RZW(u^ci2-3cJ1mSx}8#+V6-pxMZj_yCMU8W@%&pT1CGLd=)jmepKW zNR_Wq*X@zH;WsvWO4+x4)LSpK`)dHUIuz;HHeB~0DFcmt+vfqK#uQ!3e{qR}rYi2q m2npiK6V;#B_4+HG4gLVQHVk`^>rHsIe~MFOipYWeVT03>A7w`Jd=?vZ&Kp;`cD~GRN3A5C!K2j#3GGDf!1BI#hC`w3JVe2Gk8rsddEH z;FG!Q!kigWRkD2pNq~|)-tEHFQ9HD9I(0Dy%UX)$e%_!YsDuh6u4|z)f2vC^k}PUQ zL`i9Wh3pQJR#MjULbPZg38>hVAIiVgJSnp$a5r2-6v0O7)RKPzn^5&-*r+NLyk4(B z7Ulw9)b9`U1=pxGC5?>A6uugb z75IKxASnJXsYLVk>XGZSAE*sRNfVhSG(-yj^=Or0N)uJ|00000NkvXXu0mjf4L!~` diff --git a/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/Contents.json b/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/Contents.json new file mode 100644 index 00000000..ebbf67f2 --- /dev/null +++ b/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "clear_icon@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "clear_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "clear_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@1x.png b/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a44a4f5d800c154d8a722b68294ab04a6c5d5af0 GIT binary patch literal 470 zcmV;{0V)28P)55N-DSa(=RTw+UVB8;dnpNzv3E6^jQbmr9twEi#nK@e!7* z71ry|Gz_V>W)Xe&V!2$x`PnHNC$+>Uw)o^MBLv%SftRLJOU&mBlSQMR2B@?B@;WtV zStY7d*rk0ki51x(Lbg5+RgE8@Xt(WyGr$bVz{C*EHf=u>4^aU`53J?JdZgNx?4~V2 zKDYyJcNA14ZESHH$JS}u$kp}>R;h3sm*3i0Gv&VvABS!hxj;0!4z7ASPGvKY@%#rn zia9*QvdB!!Y)jO?xiqr>Ab#9~8Ew#j1ztEaGLvHSm%iuk{OsQU0?TKb7DP^rB>(^b M07*qoM6N<$f-~^OegFUf literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a07deaf43520ca30687dd7f46892533a8968c6ea GIT binary patch literal 695 zcmV;o0!aOdP)J=%XXJ3Bi=2qaC@D96(SL)c^F1At=rIx2sj0h|%cY0mkH!F-$~ z$t+}D@)@M*B!=Nn%=lOl3|N@Hf^n22`mn zdw=0F)&(tRXH)$9KW!D?-Ca02`Ti3QW|9kiqvaJIs)8!e6p6Zco0Ao!v_Cm8THAe= z^|!7&(Il?V_%LMldgaC9HMR>{;i_O4afQKyUU{np;d0`>;w7=3g5Vp@I?=Sh9mJK$ zpaaBV--VzK1T_wpdcs-SPKSq?(M~4Rx+&X3j;MpI2d2Mr2nJomt9xEg%z!$eUDK&y z&~KliPuy20TG9T|;A#v{9)z6pCMW#N$ELWKnZLQr&d)EL;!RH2XWG!FRka_-bzHPa z%wMoAM9a2icZG;BKa@WiHcwV+Vab7FKcr#YuoGodx|k7`O0kB8xR-3miDK>gZ1DI7 z6XiA%*Tc0WrmS(>hBwcMLBpvu4u@j-0pS$2MW*HB&co@dBQmT?|0vG9kwg5leLTmB dR@cz+{03nYN*g5*G~)mO002ovPDHLkV1kllK2`t# literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@3x.png b/Projects/Presentation/Resources/Images.xcassets/clear_icon.imageset/clear_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b541a27e07cf863fd21aa4001ec9bf59f7e93aec GIT binary patch literal 1103 zcmV-V1hD&wP)e( z(?Af0XO{|kC{+>=(B|TZR-zKL8bUI_I;iiYEj$cktZJ^6ca__#G_tI^A((B+=8G^IT1>EImEPd}8XO$d*98+t zBNUGq_??ffXO(^p6AeI@`6EOPhUhX0 zYT%6brsFy%kbR^p{VJKJ*~Wu^J-}dxa+g9r5A!$Ypg27Ry5K$LZp>2eArr^*Ti$`w z1JtLFUpRkj&ObgrK7luHX`MHFy#zBeCI9&4t0Oo)JyX5h?**p^Y+^P!IXMwHMjc+#Egq3>gc*aaon@*TF%0ntmab#cnHFFGJOSaS-Ys|-;$65g1}Q{d z6(hPyM?BylSio7xfD}-T1*wU5$@Q(sDD*QTW${S51|Ur6eWnW|kVxbHjQ#)V)P@UdiZQ7eF4p(}YSbbc;`gIRR^ zD$7WXfnA(P1be!FIO@Fs3az`9mB92D$wryrCZw3)r3fzWE{9J>48ZP@4Ywg<$W(lz z*OpVydO$X;p|3$kaQ%LzXU342#Zjl$VSwWAnXA>f&-9l>)8Mg3z9`m!ExYlg7{jrMqd)*?X@a-}PNZ%+V7-%RIhFVy6tLcr5j8w>D zj$m^&yJ4?TDAZ=NrA)xJe0>S)^7Z8-;4c=7jVtXh$;j&XRMv3>d&ke6+epq*@|c5I zHuUrq+OqlLp1!`mnk8)TULEoz>pTL-rI|epHia z6pYR2*7?LbuupuFrRtl!=>_K`hRdr|WF_*iV!oY7F5~1ZtuZcMV?5jfTf3;WJCi|h zZzc~ZeEf{=K``6^>ltj_#paO3a$X|;JNSKz{DH^zo9HE{?gVj7hy1|teBeu+k_RQu zs1(Nwwd9a3A!YV7I{!dsOCXl`r+5tFcN2bFdH;!=oJ4$rXFKbS$bTEKsIyJrYD8{_ zCH_2qKPRRgtnb9qB(`@m-|IxLn7WTEqV$(>JpT%9`Ct6BE*=UuusLHvPxX5sc^7G~ zB|1jcwtBH9p5IYlR{>jx-0Loiaz2h{!7=LqWCVR2w1+(VGkq zhX+>!x)Y+&7&sc>q63Xu@J!%i8s!QwO`@NMlUKk}L+;*Br=&Sa$hYVWa&6eXlX(A4 z+G#a0zswr=l|GR)_0o)ui*Pdms5*Ff3B5P4HIM%}?yGCqhk%w#B2aa$5*zxkan$3<>N9o!B zK>8GM@5f2Ks=G|^O`eK{=qohPHW%5P_c{j&8`*yoM_+Rg3_{3)dB0yH{vZVsZY(z1 za{$?g9f+{^lLg-&b0}0+xPSRX-$8~#FuzU_{6Mo^$6q~|?n358OJbTJ0*CFQ*bZSoh3vN}dk%sl??B3D&DSe+*5RU$LOtQOLD5i_R26`D}xCcZh4B5ofE1u8F~7f$UB z#PCVESZM~DWX6X>#oGcgwPE89v3Dn4N-4G9Bfrg5Yw`UDY$oMG<$zf{^RL5AeJIi> zN_`&3R+Vze3*O69aJu(bdpZpN{lxGbJw}b|CGyeuR6fYyfVj7Z4*0dpg|(|K@$nJ0P=oEikt-ThZ_!`mSWBTP!To1sXQ+iU)cZ8@ z&w}>~_HM$-Jpd@Vq0Rg_j|doydD@YKxO zd1|?*%%js*j5+G-G?;F4`1X^lYL|-M0pE72J>>h=XeRZZtYtjvwVCTo=C@bypqn2a zu0MJ5RBJAmYg);ExQga-#jQ;BNw4VvL%3R>I(7QMliCV}w9?OtaJ^gYio4qAE*9&? z#>O7&n!lEx8ILl;m38?%pU+o4jJJUOL3qL%|6791-rj*-*s7Ox#<$Yw?d{#1&F1$! zC|}{)9`@xiNASI5Z@Jy2_a~{ZuJTV|Tgy+!x}Ki?XLI>8FO^EAO!&!Y;Rds8^!1I_ h}xObl#oED`_!002ovPDHLkV1g(<$(aBE literal 0 HcmV?d00001 diff --git a/Projects/Presentation/Resources/Images.xcassets/divide_line_icon.imageset/divide_line_icon@2x.png b/Projects/Presentation/Resources/Images.xcassets/divide_line_icon.imageset/divide_line_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9bc45f3c84fe264a8f3ce3e8f83fa812d1ba2e90 GIT binary patch literal 5501 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 Date: Sat, 16 Aug 2025 09:50:14 +0900 Subject: [PATCH 02/10] =?UTF-8?q?Refactor:=20Week=20enum=20Domain=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Domain/Sources/Entity/Enum/WeekType.swift | 2 +- Projects/Domain/Sources/Entity/RoutineEntity.swift | 4 ++-- .../Sources/Entity/RoutineSummaryEntity.swift | 4 ++-- .../Model/{Week.swift => Week+.swift} | 13 +++---------- 4 files changed, 8 insertions(+), 15 deletions(-) rename Projects/Presentation/Sources/RoutineCreation/Model/{Week.swift => Week+.swift} (71%) diff --git a/Projects/Domain/Sources/Entity/Enum/WeekType.swift b/Projects/Domain/Sources/Entity/Enum/WeekType.swift index 822bfa68..531f1c91 100644 --- a/Projects/Domain/Sources/Entity/Enum/WeekType.swift +++ b/Projects/Domain/Sources/Entity/Enum/WeekType.swift @@ -5,7 +5,7 @@ // Created by 이동현 on 8/3/25. // -public enum WeekType: String, CaseIterable { +public enum Week: String, CaseIterable { case monday = "MONDAY" case tuesday = "TUESDAY" case wednesday = "WEDNESDAY" diff --git a/Projects/Domain/Sources/Entity/RoutineEntity.swift b/Projects/Domain/Sources/Entity/RoutineEntity.swift index ceb7d8e5..2b8b4915 100644 --- a/Projects/Domain/Sources/Entity/RoutineEntity.swift +++ b/Projects/Domain/Sources/Entity/RoutineEntity.swift @@ -9,7 +9,7 @@ public struct RoutineEntity { public let routineId: String? public let historySeq: Int public let routineName: String - public let repeatDay: [WeekType] + public let repeatDay: [Week] public let executionTime: String public let subRoutineSearchResultDto: [SubRoutineEntity] public let modifiedYn: Bool @@ -29,7 +29,7 @@ public struct RoutineEntity { completeYn: Bool, routineType: String ) { - let weekType: [WeekType] = repeatDay?.compactMap(WeekType.init(rawValue:)) ?? [] + let weekType: [Week] = repeatDay?.compactMap(Week.init(rawValue:)) ?? [] self.routineId = routineId self.historySeq = historySeq diff --git a/Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift b/Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift index 616f408b..0fb2cce5 100644 --- a/Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift +++ b/Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift @@ -8,7 +8,7 @@ public struct RoutineSummaryEntity { public let routineId: String? public let routineName: String - public let repeatDay: [WeekType] + public let repeatDay: [Week] public let executionTime: String public init( @@ -17,7 +17,7 @@ public struct RoutineSummaryEntity { repeatDay: [String]?, executionTime: String ) { - let weekType: [WeekType] = repeatDay?.compactMap(WeekType.init(rawValue:)) ?? [] + let weekType: [Week] = repeatDay?.compactMap(Week.init(rawValue:)) ?? [] self.routineId = routineId self.routineName = routineName diff --git a/Projects/Presentation/Sources/RoutineCreation/Model/Week.swift b/Projects/Presentation/Sources/RoutineCreation/Model/Week+.swift similarity index 71% rename from Projects/Presentation/Sources/RoutineCreation/Model/Week.swift rename to Projects/Presentation/Sources/RoutineCreation/Model/Week+.swift index 8169c11a..8cbf0068 100644 --- a/Projects/Presentation/Sources/RoutineCreation/Model/Week.swift +++ b/Projects/Presentation/Sources/RoutineCreation/Model/Week+.swift @@ -1,19 +1,12 @@ // -// Week.swift +// Week+.swift // Presentation // // Created by 이동현 on 7/21/25. // +import Domain -enum Week: String, CaseIterable { - case monday = "MONDAY" - case tuesday = "TUESDAY" - case wednesday = "WEDNESDAY" - case thursday = "THURSDAY" - case friday = "FRIDAY" - case saturday = "SATURDAY" - case sunday = "SUNDAY" - +extension Week { var id: Int { switch self { case .monday: 0 From 0415be8722b12dcb821a9216fa5b402e0b3d965d Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 09:51:30 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20RoutineCreationExpandable=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=86=A0=EC=BD=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 루틴 생성/수정에서 접었다 폈다 할 수 있는 카드 뷰들의 contentView가 채택하는 프로토콜입니다. --- .../Protocol/RoutineCreationExpandable.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Protocol/RoutineCreationExpandable.swift diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Protocol/RoutineCreationExpandable.swift b/Projects/Presentation/Sources/RoutineCreation/View/Protocol/RoutineCreationExpandable.swift new file mode 100644 index 00000000..8dd6868d --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Protocol/RoutineCreationExpandable.swift @@ -0,0 +1,22 @@ +// +// RoutineCreationExpandable.swift +// Presentation +// +// Created by 이동현 on 8/10/25. +// + +import SnapKit +import UIKit + +protocol RoutineCreationExpandable: UIView { + associatedtype Action + associatedtype Dependencies + + /// RoutineCreationExpandableContentView 에서 일어날 수 있는 action을 전달합니다. + var action: ((Action) -> Void)? { get set } + var heightConstraint: Constraint? { get set } + + func setExpanded(expanded: Bool) + func configure(dependencies: Dependencies) +} + From 3e180047648ba82c7da79fc7998eed2189760c61 Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 09:52:17 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8B=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D/=EC=88=98=EC=A0=95=20subview=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Component/RoutineCreationCardView.swift | 248 ++++++++++++++++++ .../Component/RoutineNameContentView.swift | 220 ++++++++++++++++ .../Component/RoutinePeriodContentView.swift | 137 ++++++++++ .../Component/RoutineRepeatContentView.swift | 228 ++++++++++++++++ .../Component/RoutineTimeContentView.swift | 154 +++++++++++ .../View/Component/TimeContentView.swift | 127 +++++++++ 6 files changed, 1114 insertions(+) create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimeContentView.swift create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift new file mode 100644 index 00000000..b6995e4d --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationCardView.swift @@ -0,0 +1,248 @@ +// +// RoutineCreationCardView.swift +// Presentation +// +// Created by 이동현 on 8/11/25. +// + +import SnapKit +import UIKit + +fileprivate enum Layout { + static let edgeSpacing: CGFloat = 18 + static let titleImageSize: CGFloat = 24 + static let titleImageTrailingSpacing: CGFloat = 15 + static let titleLabelLargeHeight: CGFloat = 24 + static let titleLabelSmallHeight: CGFloat = 20 + static let titleLabelTrailingSpacing: CGFloat = 5 + static let placeHolderLabelHeight: CGFloat = 20 + static let contentLabelHeight: CGFloat = 24 + static let contentLabelStackViewSpacing: CGFloat = 1 + static let infoImageSize: CGFloat = 16 + static let asteriskImageSize: CGFloat = 12 + static let chevronDownImageWidth: CGFloat = 20 + static let chevronDownImageHeight: CGFloat = 20 + static let chevronDownImageTrailingSpacing: CGFloat = 28 + static let divideLineTopSpacing: CGFloat = 14 + static let divideLineHeight: CGFloat = 1 + static let contentViewHeight: CGFloat = 0 +} + +final class RoutineCreationCardView: UIView, UIGestureRecognizerDelegate { + private let titleImageView = UIImageView() + private let labelStackView = UIStackView() + private let titleLabel = UILabel() + private let placeHolderLabel = UILabel() + private let infoImageView = UIImageView() + private let asteriskImageView = UIImageView() + private let chevronImageView = UIImageView() + private let divideLine = UIView() + private let contentView = ContentView() + private var labelStackViewBottomConstraint: Constraint? + public var onAction: ((ContentView.Action) -> Void)? { + didSet { contentView.action = onAction } + } + + init( + title: String, + placeHolder: String, + titleImage: UIImage?, + withInfoImage: Bool, + withAsteriskImage: Bool + ) { + super.init(frame: .zero) + configureAttribute() + configureLayout() + + titleLabel.text = title + placeHolderLabel.text = placeHolder + titleImageView.image = titleImage + infoImageView.isHidden = !withInfoImage + asteriskImageView.isHidden = !withAsteriskImage + divideLine.isHidden = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure( + image: UIImage, + title: String, + contents: [String] + ) { + titleImageView.image = image + titleLabel.text = title + } + + private func configureAttribute() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) + tapGesture.delegate = self + addGestureRecognizer(tapGesture) + + backgroundColor = BitnagilColor.gray99 + layer.cornerRadius = 12 + layer.masksToBounds = true + + labelStackView.spacing = Layout.contentLabelStackViewSpacing + labelStackView.axis = .vertical + labelStackView.distribution = .fill + labelStackView.alignment = .leading + + titleLabel.font = BitnagilFont.init(style: .body1, weight: .semiBold).font + titleLabel.textColor = .black + + placeHolderLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font + placeHolderLabel.textColor = BitnagilColor.gray70 + + infoImageView.image = BitnagilIcon.informationIcon + asteriskImageView.image = BitnagilIcon.asteriskIcon + chevronImageView.image = BitnagilIcon.chevronIcon(direction: .down)?.withRenderingMode(.alwaysTemplate) + chevronImageView.tintColor = BitnagilColor.gray30 + + divideLine.backgroundColor = BitnagilColor.gray96 + + contentView.action = { [weak self] in + self?.onAction?($0) + } + } + + private func configureLayout() { + addSubview(titleImageView) + addSubview(labelStackView) + addSubview(asteriskImageView) + addSubview(infoImageView) + addSubview(contentView) + addSubview(chevronImageView) + addSubview(divideLine) + + labelStackView.addArrangedSubview(titleLabel) + labelStackView.addArrangedSubview(placeHolderLabel) + + titleImageView.snp.makeConstraints { make in + make.centerY.equalTo(labelStackView) + make.leading.equalToSuperview().offset(Layout.edgeSpacing) + make.size.equalTo(Layout.titleImageSize) + } + + labelStackView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.edgeSpacing) + make.leading.equalTo(titleImageView.snp.trailing).offset(Layout.titleImageTrailingSpacing) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.titleLabelSmallHeight).priority(800) + labelStackViewBottomConstraint = make.bottom.equalToSuperview() + .offset(-Layout.edgeSpacing) + .priority(.medium) + .constraint + } + + titleLabel.snp.makeConstraints { make in + make.height.equalTo(Layout.titleLabelLargeHeight) + } + + placeHolderLabel.snp.makeConstraints { make in + make.height.equalTo(Layout.placeHolderLabelHeight) + } + + infoImageView.snp.makeConstraints { make in + make.leading.equalTo(titleLabel.snp.trailing).offset(Layout.titleLabelTrailingSpacing) + make.centerY.equalTo(titleLabel) + make.size.equalTo(Layout.infoImageSize) + } + + asteriskImageView.snp.makeConstraints { make in + make.leading.equalTo(titleLabel.snp.trailing).offset(Layout.titleLabelTrailingSpacing) + make.centerY.equalTo(titleLabel) + make.size.equalTo(Layout.asteriskImageSize) + } + + chevronImageView.snp.makeConstraints { make in + make.centerY.equalTo(titleImageView) + make.height.equalTo(Layout.chevronDownImageHeight) + make.width.equalTo(Layout.chevronDownImageWidth) + make.trailing.equalToSuperview().offset(-Layout.chevronDownImageTrailingSpacing) + } + + divideLine.snp.makeConstraints { make in + make.top.equalTo(titleImageView.snp.bottom).offset(Layout.divideLineTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.edgeSpacing) + make.height.equalTo(Layout.divideLineHeight) + } + + contentView.snp.makeConstraints { make in + make.top.equalTo(divideLine.snp.bottom) + make.horizontalEdges.equalToSuperview() + make.bottom.equalToSuperview().offset(-Layout.edgeSpacing) + } + + labelStackViewBottomConstraint?.isActive = true + } + + func configure(dependencies: ContentView.Dependencies) { + contentView.configure(dependencies: dependencies) + } + + @objc private func handleTap(_ gestureRecognizer: UIGestureRecognizer) { + let location = gestureRecognizer.location(in: self) + + if location.y < labelStackView.frame.maxY { + toggleExpand() + } + } + + private func configureContents(contents: [String]) { + guard !contents.isEmpty else { + placeHolderLabel.isHidden = false + titleLabel.snp.updateConstraints { make in + make.height.equalTo(Layout.titleLabelLargeHeight) + } + return + } + + placeHolderLabel.isHidden = true + + labelStackView + .arrangedSubviews + .dropFirst(2) // title, placeholder 제외한 label들 삭제 + .forEach { $0.removeFromSuperview() } + + contents.forEach { + let label = UILabel() + label.text = $0 + label.font = BitnagilFont.init(style: .body1, weight: .semiBold).font + label.textColor = .black + + labelStackView.addArrangedSubview(label) + label.snp.makeConstraints { make in + make.height.equalTo(Layout.contentLabelHeight) + } + } + + titleLabel.snp.updateConstraints { make in + make.height.equalTo(Layout.titleLabelSmallHeight) + } + } + + private func toggleExpand() { + let isExpanded = !(labelStackViewBottomConstraint?.isActive ?? false) + let nextExpandedState = !isExpanded + + if nextExpandedState { + labelStackViewBottomConstraint?.isActive = false + placeHolderLabel.isHidden = true + contentView.isHidden = false + divideLine.isHidden = false + chevronImageView.image = BitnagilIcon.chevronIcon(direction: .up) + } else { + labelStackViewBottomConstraint?.isActive = true + placeHolderLabel.isHidden = false + contentView.isHidden = true + divideLine.isHidden = true + chevronImageView.image = BitnagilIcon.chevronIcon(direction: .down) + } + + contentView.setExpanded(expanded: nextExpandedState) + + self.layoutIfNeeded() + } +} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift new file mode 100644 index 00000000..e8166068 --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift @@ -0,0 +1,220 @@ +// +// RoutineNameContentView.swift +// Presentation +// +// Created by 이동현 on 8/11/25. +// + +import SnapKit +import UIKit + +final class RoutineNameContentView: UIView, RoutineCreationExpandable { + private enum Layout { + static let edgeSpacing: CGFloat = 19 + static let numberImageSize: CGFloat = 20 + static let numberCenterYFromTextField: CGFloat = 2.5 + static let textFieldLeadingSpacing: CGFloat = 12 + static let textFieldHeight: CGFloat = 20 + static let divideLineHeight: CGFloat = 1.3 + static let textFieldTopSpacing: CGFloat = 23 + static let checkButtonViewTopSpacing: CGFloat = 24 + static let checkButtonViewSize: CGFloat = 18 + static let checkButtonLabelHeight: CGFloat = 20 + static let checkButtonLabelTrailingSpacing: CGFloat = 6 + static let foldedHeight: CGFloat = 0 + } + + enum Action { + case subroutineChanged(index: Int, text: String) + case deleteAllSubroutines + } + + struct Dependencies { + let subRoutines: [String] + } + + private let oneImageView = UIImageView() + private let twoImageView = UIImageView() + private let threeImageView = UIImageView() + private let subRoutineTextField1 = UITextField() + private let subRoutineTextField2 = UITextField() + private let subRoutineTextField3 = UITextField() + private let divideLine1 = UIImageView() + private let divideLine2 = UIImageView() + private let divideLine3 = UIImageView() + private let checkButtonLabel = UILabel() + private let checkButtonView = UIView() + private let checkButton = UIButton() + var heightConstraint: Constraint? + var action: ((Action) -> Void)? + + private var isExpanded = true // 초기 상태 추적 + + init() { + super.init(frame: .zero) + configureAttribute() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setExpanded(expanded: Bool) { + guard heightConstraint?.isActive == expanded else { return } + + subviews.forEach { $0.alpha = 0 } + defer { self.subviews.forEach { $0.alpha = 1 } } + + self.heightConstraint?.isActive = !expanded + self.subviews.forEach { $0.isHidden = !expanded } + } + + func configure(dependencies: Dependencies) { + guard !dependencies.subRoutines.isEmpty else { return } + + let subRoutines = dependencies.subRoutines + let subRoutineTextFields = [ + subRoutineTextField1, + subRoutineTextField2, + subRoutineTextField3] + let minCount = min(subRoutines.count, subRoutineTextFields.count) + + zip(subRoutines, subRoutineTextFields).prefix(minCount).forEach { + $1.text = $0 + } + } + + private func configureAttribute() { + oneImageView.image = BitnagilIcon.oneIcon + twoImageView.image = BitnagilIcon.twoIcon + threeImageView.image = BitnagilIcon.threeIcon + + let placeholders = [ + "ex) 일어나자마자 이불 개기", + "ex) 일어나서 찬물 마시기", + "ex) 양치하기"] + + let textFields = [ + subRoutineTextField1, + subRoutineTextField2, + subRoutineTextField3] + + zip(textFields, placeholders).forEach { textField, placeholder in + let attributes: [NSAttributedString.Key: Any] = [ + .font: BitnagilFont(style: .body2, weight: .medium).font, + .foregroundColor: BitnagilColor.gray90 ?? .systemGray] + + textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes) + textField.font = BitnagilFont(style: .body2, weight: .medium).font + textField.textColor = BitnagilColor.gray30 + } + + divideLine1.image = BitnagilIcon.divideLineIcon + divideLine2.image = BitnagilIcon.divideLineIcon + divideLine3.image = BitnagilIcon.divideLineIcon + + checkButtonLabel.text = "세부루틴 설정 안함" + checkButtonLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font + + checkButtonView.layer.cornerRadius = 4 + checkButtonView.layer.borderWidth = 1 + checkButtonView.backgroundColor = .white + checkButtonView.layer.borderColor = BitnagilColor.gray95?.cgColor + } + + private func configureLayout() { + addSubview(oneImageView) + addSubview(twoImageView) + addSubview(threeImageView) + addSubview(subRoutineTextField1) + addSubview(subRoutineTextField2) + addSubview(subRoutineTextField3) + addSubview(divideLine1) + addSubview(divideLine2) + addSubview(divideLine3) + addSubview(checkButtonLabel) + addSubview(checkButtonView) + addSubview(checkButton) + + self.snp.makeConstraints { make in + heightConstraint = make.height.equalTo(Layout.foldedHeight).constraint + } + + oneImageView.snp.makeConstraints { make in + make.centerY.equalTo(subRoutineTextField1).offset(Layout.numberCenterYFromTextField) + make.leading.equalToSuperview().offset(Layout.edgeSpacing) + make.size.equalTo(Layout.numberImageSize).priority(999) + } + + twoImageView.snp.makeConstraints { make in + make.centerY.equalTo(subRoutineTextField2).offset(Layout.numberCenterYFromTextField) + make.leading.equalToSuperview().offset(Layout.edgeSpacing) + make.size.equalTo(Layout.numberImageSize).priority(999) + } + + threeImageView.snp.makeConstraints { make in + make.centerY.equalTo(subRoutineTextField3).offset(Layout.numberCenterYFromTextField) + make.leading.equalToSuperview().offset(Layout.edgeSpacing) + make.size.equalTo(Layout.numberImageSize).priority(999) + } + + subRoutineTextField1.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.edgeSpacing).priority(999) + make.leading.equalTo(oneImageView.snp.trailing).offset(Layout.textFieldLeadingSpacing) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.textFieldHeight).priority(999) + } + + subRoutineTextField2.snp.makeConstraints { make in + make.top.equalTo(divideLine1.snp.bottom).offset(Layout.textFieldTopSpacing) + make.leading.equalTo(oneImageView.snp.trailing).offset(Layout.textFieldLeadingSpacing) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.textFieldHeight).priority(999) + } + + subRoutineTextField3.snp.makeConstraints { make in + make.top.equalTo(divideLine2.snp.bottom).offset(Layout.textFieldTopSpacing) + make.leading.equalTo(oneImageView.snp.trailing).offset(Layout.textFieldLeadingSpacing) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.textFieldHeight).priority(999) + } + + divideLine1.snp.makeConstraints { make in + make.horizontalEdges.equalTo(subRoutineTextField1) + make.top.equalTo(subRoutineTextField1.snp.bottom) + make.height.equalTo(Layout.divideLineHeight).priority(999) + } + + divideLine2.snp.makeConstraints { make in + make.horizontalEdges.equalTo(subRoutineTextField2) + make.top.equalTo(subRoutineTextField2.snp.bottom) + make.height.equalTo(Layout.divideLineHeight).priority(999) + } + + divideLine3.snp.makeConstraints { make in + make.horizontalEdges.equalTo(subRoutineTextField3) + make.top.equalTo(subRoutineTextField3.snp.bottom) + make.height.equalTo(Layout.divideLineHeight).priority(999) + } + + checkButtonView.snp.makeConstraints { make in + make.top.equalTo(divideLine3.snp.bottom).offset(Layout.checkButtonViewTopSpacing).priority(999) + make.bottom.equalToSuperview().offset(-Layout.edgeSpacing).priority(999) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.size.equalTo(Layout.checkButtonViewSize).priority(999) + } + + checkButtonLabel.snp.makeConstraints { make in + make.trailing.equalTo(checkButtonView.snp.leading).offset(-Layout.checkButtonLabelTrailingSpacing) + make.centerY.equalTo(checkButtonView) + } + + checkButton.snp.makeConstraints { make in + make.verticalEdges.equalTo(checkButtonView) + make.leading.equalTo(checkButtonLabel.snp.leading) + make.trailing.equalTo(checkButtonView.snp.trailing) + } + } + +} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift new file mode 100644 index 00000000..6e70280f --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutinePeriodContentView.swift @@ -0,0 +1,137 @@ +// +// PeriodContentView.swift +// Presentation +// +// Created by 이동현 on 8/14/25. +// + +import Domain +import SnapKit +import UIKit + +final class RoutinePeriodContentView: UIView, RoutineCreationExpandable { + enum ButtonType { + case start + case end + } + + private enum Layout { + static let edgeSpacing: CGFloat = 19 + static let labelTopSpacing: CGFloat = 14 + static let labelHeight: CGFloat = 39 + static let dateButtonHeight: CGFloat = 39 + static let dateButtonWidth: CGFloat = 116 + static let foldedHeight: CGFloat = 0 + } + + enum Action { + case startDateSet(date: Date) + case endDateSet(date: Date) + } + + struct Dependencies { + let start: Date + let end: Date + } + + private let startLabel = UILabel() + private let endLabel = UILabel() + private let startButton = UIButton() + private let endButton = UIButton() + var heightConstraint: Constraint? + var action: ((Action) -> Void)? + + init() { + super.init(frame: .zero) + configureAttribute() + configureLayout() + + // TODO: - 추후 삭제 + configureButon(buttonType: .start, date: Date()) + configureButon(buttonType: .end, date: Date()) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setExpanded(expanded: Bool) { + guard heightConstraint?.isActive == expanded else { return } + + subviews.forEach { $0.alpha = 0 } + defer { self.subviews.forEach { $0.alpha = 1 } } + + self.heightConstraint?.isActive = !expanded + self.subviews.forEach { $0.isHidden = !expanded } + } + + func configure(dependencies: Dependencies) { + let startString = dependencies.start.convertToString(dateType: .yearMonthDate) + let endString = dependencies.end.convertToString(dateType: .yearMonthDate) + } + + private func configureAttribute() { + startLabel.text = "시작일" + endLabel.text = "종료일" + + [startLabel, endLabel].forEach { + $0.font = BitnagilFont.init(style: .body2, weight: .semiBold).font + $0.textColor = BitnagilColor.gray30 + } + + [startButton, endButton].forEach { + $0.setTitleColor(BitnagilColor.gray30, for: .normal) + $0.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font + $0.layer.cornerRadius = 12 + $0.layer.masksToBounds = true + $0.backgroundColor = .white + $0.layer.borderColor = BitnagilColor.gray97?.cgColor + $0.layer.borderWidth = 1 + } + } + + private func configureLayout() { + addSubview(startLabel) + addSubview(startButton) + addSubview(endLabel) + addSubview(endButton) + + self.snp.makeConstraints { make in + heightConstraint = make.height.equalTo(Layout.foldedHeight).constraint + } + + startLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.labelTopSpacing).priority(999) + make.leading.equalTo(Layout.edgeSpacing) + make.height.equalTo(Layout.labelHeight) + } + + startButton.snp.makeConstraints { make in + make.top.equalTo(startLabel) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.dateButtonHeight) + make.width.equalTo(Layout.dateButtonWidth) + } + + endLabel.snp.makeConstraints { make in + make.top.equalTo(startLabel.snp.bottom).offset(Layout.labelTopSpacing) + make.leading.equalTo(Layout.edgeSpacing) + make.height.equalTo(Layout.labelHeight) + make.bottom.equalToSuperview().offset(-Layout.edgeSpacing).priority(999) + } + + endButton.snp.makeConstraints { make in + make.top.equalTo(endLabel) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.dateButtonHeight) + make.width.equalTo(Layout.dateButtonWidth) + } + } + + func configureButon(buttonType: ButtonType, date: Date) { + let button = buttonType == .start ? startButton : endButton + + let dateString = date.convertToString(dateType: .yearMonthDate) + button.setTitle(dateString, for: .normal) + } +} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift new file mode 100644 index 00000000..e246a449 --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift @@ -0,0 +1,228 @@ +// +// RoutineRepeatContentView.swift +// Presentation +// +// Created by 이동현 on 8/12/25. +// + +import Domain +import SnapKit +import UIKit + +final class RoutineRepeatContentView: UIView, RoutineCreationExpandable { + private enum Layout { + static let edgeSpacing: CGFloat = 19 + static let repeatButtonTopSpacing: CGFloat = 14 + static let repeatButtonHeight: CGFloat = 39 + static let repeaetButtonBetweenSpacing: CGFloat = 13 + static let weekStackViewTopSpacing: CGFloat = 15 + static let weekStackViewHeight: CGFloat = 38 + static let weekStackViewSpacing: CGFloat = 5 + static let checkButtonImageTopSpacing: CGFloat = 24 + static let checkButtonImageSize: CGFloat = 18 + static let checkButtonLabelHeight: CGFloat = 20 + static let checkButtonLabelTrailingSpacing: CGFloat = 6 + static let foldedHeight: CGFloat = 0 + } + + enum Action { + case repeatDaily + case repeatWeekly + case setWeeks([Week]) + } + + struct Dependencies { + let repeatType: RepeatType? + let selectedWeeks: [Week] + } + + private let dailyButton = UIButton() + private let weeklyButton = UIButton() + private let weekStackView = UIStackView() + private var repeatButtonBottomConstraint: Constraint? + var heightConstraint: Constraint? + var action: ((Action) -> Void)? + + init() { + super.init(frame: .zero) + configureAttribute() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setExpanded(expanded: Bool) { + guard heightConstraint?.isActive == expanded else { return } + + subviews.forEach { $0.alpha = 0 } + defer { self.subviews.forEach { $0.alpha = 1 } } + + self.heightConstraint?.isActive = !expanded + self.subviews.forEach { $0.isHidden = !expanded } + + if expanded { + weekStackView.isHidden = !weeklyButton.isSelected + } + } + + func configure(dependencies: Dependencies) { + configureRepeatButton(selectedType: dependencies.repeatType) + + let selectedWeeks = dependencies.selectedWeeks + + weekStackView.subviews.forEach { button in + guard let button = button as? UIButton else { return } + button.isSelected = false + button.backgroundColor = .white + button.layer.borderColor = BitnagilColor.gray96?.cgColor + } + + selectedWeeks.forEach { + let index = $0.id + guard let button = weekStackView.arrangedSubviews[index] as? UIButton else { return } + + button.isSelected = true + button.backgroundColor = BitnagilColor.orange500 + button.layer.borderColor = BitnagilColor.orange500?.cgColor + } + } + + private func configureAttribute() { + configureWeekStackView() + weekStackView.isHidden = true + + dailyButton.setTitle("매일", for: .normal) + weeklyButton.setTitle("요일 선택", for: .normal) + [dailyButton, weeklyButton].forEach { button in + button.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font + button.setTitleColor(BitnagilColor.gray30, for: .normal) + button.setTitleColor(.white, for: .selected) + button.layer.cornerRadius = 12 + button.layer.masksToBounds = true + button.layer.borderWidth = 1 + button.layer.borderColor = BitnagilColor.gray97?.cgColor + button.backgroundColor = .white + } + + dailyButton.addAction( + UIAction { [weak self] _ in + self?.configureRepeatButton(selectedType: .daily) + }, + for: .touchUpInside) + + weeklyButton.addAction( + UIAction { [weak self] _ in + self?.configureRepeatButton(selectedType: .weekly) + }, + for: .touchUpInside) + } + + private func configureLayout() { + addSubview(dailyButton) + addSubview(weeklyButton) + addSubview(weekStackView) + + self.snp.makeConstraints { make in + heightConstraint = make.height.equalTo(Layout.foldedHeight).constraint + } + + dailyButton.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.edgeSpacing) + make.leading.equalToSuperview().offset(Layout.edgeSpacing) + make.height.equalTo(Layout.repeatButtonHeight) + repeatButtonBottomConstraint = make.bottom.equalToSuperview() + .offset(-Layout.edgeSpacing) + .constraint + } + + weeklyButton.snp.makeConstraints { make in + make.top.equalTo(dailyButton) + make.leading.equalTo(dailyButton.snp.trailing).offset(Layout.repeaetButtonBetweenSpacing) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.repeatButtonHeight) + make.width.equalTo(dailyButton) + make.bottom.equalTo(dailyButton) + } + + weekStackView.snp.makeConstraints { make in + make.top.equalTo(dailyButton.snp.bottom).offset(Layout.weekStackViewTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.edgeSpacing) + make.height.equalTo(Layout.weekStackViewHeight) + make.bottom.equalToSuperview().offset(-Layout.edgeSpacing) + } + } + + private func configureWeekStackView() { + weekStackView.axis = .horizontal + weekStackView.spacing = 5 + weekStackView.distribution = .fillEqually + + Week.allCases.forEach { + let button = UIButton() + let normalLayerColor = BitnagilColor.gray96?.cgColor + button.layer.cornerRadius = 12 + button.layer.masksToBounds = true + button.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font + button.setTitleColor(BitnagilColor.gray30, for: .normal) + button.setTitleColor(.white, for: .selected) + button.setTitle($0.koreanValue, for: .normal) + button.layer.borderWidth = 1 + button.layer.borderColor = normalLayerColor + button.backgroundColor = .white + + button.addAction( + UIAction { action in + guard let sender = action.sender as? UIButton else { return } + + sender.isSelected.toggle() + if sender.isSelected { + sender.backgroundColor = BitnagilColor.orange500 + sender.layer.borderColor = BitnagilColor.orange500?.cgColor + } else { + sender.backgroundColor = .white + sender.layer.borderColor = normalLayerColor + } + }, + for: .touchUpInside) + weekStackView.addArrangedSubview(button) + } + } + + private func configureRepeatButton(selectedType: RepeatType?) { + if let selectedType { + switch selectedType { + case .daily: + dailyButton.isSelected.toggle() + if dailyButton.isSelected { + weeklyButton.isSelected = false + } + case .weekly: + weeklyButton.isSelected.toggle() + if weeklyButton.isSelected { + dailyButton.isSelected = false + } + } + } + + [dailyButton, weeklyButton].forEach { + if $0.isSelected { + $0.backgroundColor = BitnagilColor.gray10 + $0.layer.borderColor = BitnagilColor.gray10?.cgColor + } else { + $0.backgroundColor = .white + $0.layer.borderColor = BitnagilColor.gray97?.cgColor + } + } + + if weeklyButton.isSelected { + let bottomSpacing = Layout.edgeSpacing + Layout.weekStackViewTopSpacing + Layout.weekStackViewHeight + weekStackView.isHidden = false + repeatButtonBottomConstraint?.update(offset: -bottomSpacing) + } else { + weekStackView.isHidden = true + repeatButtonBottomConstraint?.update(offset: -Layout.foldedHeight) + } + } +} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimeContentView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimeContentView.swift new file mode 100644 index 00000000..cda6bce9 --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimeContentView.swift @@ -0,0 +1,154 @@ +// +// TimeContentView.swift +// Presentation +// +// Created by 이동현 on 8/15/25. +// + +import Domain +import SnapKit +import UIKit + +final class RoutineTimeContentView: UIView, RoutineCreationExpandable { + private enum Layout { + static let edgeSpacing: CGFloat = 19 + static let timeLabelTopSpacing: CGFloat = 14 + static let timeLabelHeight: CGFloat = 39 + static let timeButtonTopSpacing: CGFloat = 14 + static let timeButtonHeight: CGFloat = 39 + static let timeButtonWidth: CGFloat = 116 + static let checkButtonViewTopSpacing: CGFloat = 21 + static let checkButtonViewSize: CGFloat = 18 + static let checkButtonLabelHeight: CGFloat = 20 + static let checkButtonLabelTrailingSpacing: CGFloat = 6 + static let foldedHeight: CGFloat = 0 + } + + enum Action { + case allDayTapped + case timeSetTapped + } + + struct Dependencies { + let startTime: Date + } + + private let timeLabel = UILabel() + private let timeButton = UIButton() + private let checkButtonView = UIView() + private let checkLabel = UILabel() + private let checkButton = UIButton() + + var heightConstraint: Constraint? + var action: ((Action) -> Void)? + + init() { + super.init(frame: .zero) + configureAttribute() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setExpanded(expanded: Bool) { + guard heightConstraint?.isActive == expanded else { return } + + subviews.forEach { $0.alpha = 0 } + defer { self.subviews.forEach { $0.alpha = 1 } } + + self.heightConstraint?.isActive = !expanded + self.subviews.forEach { $0.isHidden = !expanded } + } + + func configure(dependencies: Dependencies) { + let time = dependencies.startTime + + if time.isMidnight { + timeLabel.text = "하루 종일" + } else { + timeLabel.text = time.convertToString(dateType: .amPmTimeShort) + } + } + + private func configureAttribute() { + timeLabel.text = "시작 시간" + timeLabel.font = BitnagilFont.init(style: .body2, weight: .semiBold).font + + timeButton.layer.cornerRadius = 12 + timeButton.layer.borderWidth = 1 + timeButton.layer.borderColor = BitnagilColor.gray97?.cgColor + timeButton.backgroundColor = .white + timeButton.setTitleColor(BitnagilColor.gray30, for: .normal) + timeButton.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font + + checkLabel.text = "하루종일" + checkLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font + + checkButtonView.layer.cornerRadius = 4 + checkButtonView.layer.borderWidth = 1 + checkButtonView.backgroundColor = .white + checkButtonView.layer.borderColor = BitnagilColor.gray95?.cgColor + + timeButton.addAction( + UIAction { [weak self] _ in + self?.action?(.timeSetTapped) + }, + for: .touchUpInside) + + checkButton.addAction( + UIAction { [weak self] _ in + self?.action?(.allDayTapped) + }, + for: .touchUpInside) + } + + private func configureLayout() { + addSubview(timeLabel) + addSubview(timeButton) + addSubview(checkLabel) + addSubview(checkButtonView) + addSubview(checkButton) + + self.snp.makeConstraints { make in + heightConstraint = make.height.equalTo(Layout.foldedHeight).constraint + } + + timeLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.timeLabelTopSpacing).priority(999) + make.height.equalTo(Layout.timeLabelHeight) + make.leading.equalTo(Layout.edgeSpacing) + } + + timeButton.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.timeButtonTopSpacing).priority(999) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.timeButtonHeight) + make.width.equalTo(Layout.timeButtonWidth) + } + + checkButtonView.snp.makeConstraints { make in + make.top.equalTo(timeButton.snp.bottom).offset(Layout.checkButtonViewTopSpacing) + make.size.equalTo(Layout.checkButtonViewSize) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.bottom.equalToSuperview().offset(-Layout.edgeSpacing).priority(999) + } + + checkLabel.snp.makeConstraints { make in + make.centerY.equalTo(checkButtonView) + make.trailing.equalTo(checkButtonView.snp.leading).offset(-Layout.checkButtonLabelTrailingSpacing) + } + + checkButton.snp.makeConstraints { make in + make.leading.equalTo(checkLabel) + make.verticalEdges.equalTo(checkButtonView) + make.trailing.equalTo(checkButtonView) + } + } + + func configureTime(time: Date) { + let timeString = time.convertToString(dateType: .amPmTimeShort) + timeButton.setTitle(timeString, for: .normal) + } +} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift new file mode 100644 index 00000000..f1c22577 --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift @@ -0,0 +1,127 @@ +// +// TimeContentView.swift +// Presentation +// +// Created by 이동현 on 8/15/25. +// + +import Domain +import SnapKit +import UIKit + +final class RoutineTimeContentView: UIView, RoutineCreationExpandable { + private enum Layout { + static let edgeSpacing: CGFloat = 19 + static let timeLabelTopSpacing: CGFloat = 14 + static let timeLabelHeight: CGFloat = 39 + static let timeButtonTopSpacing: CGFloat = 14 + static let timeButtonHeight: CGFloat = 39 + static let timeButtonWidth: CGFloat = 116 + static let checkButtonViewTopSpacing: CGFloat = 21 + static let checkButtonViewSize: CGFloat = 18 + static let checkButtonLabelHeight: CGFloat = 20 + static let checkButtonLabelTrailingSpacing: CGFloat = 6 + static let foldedHeight: CGFloat = 0 + } + + enum Action {} + + struct Dependencies {} + + private let timeLabel = UILabel() + private let timeButton = UIButton() + private let checkButtonView = UIView() + private let checkLabel = UILabel() + private let checkButton = UIButton() + + var heightConstraint: Constraint? + var action: ((Action) -> Void)? + + init() { + super.init(frame: .zero) + configureAttribute() + configureLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setExpanded(expanded: Bool) { + guard heightConstraint?.isActive == expanded else { return } + + subviews.forEach { $0.alpha = 0 } + defer { self.subviews.forEach { $0.alpha = 1 } } + + self.heightConstraint?.isActive = !expanded + self.subviews.forEach { $0.isHidden = !expanded } + } + + private func configureAttribute() { + timeLabel.text = "시작 시간" + timeLabel.font = BitnagilFont.init(style: .body2, weight: .semiBold).font + + timeButton.layer.cornerRadius = 12 + timeButton.layer.borderWidth = 1 + timeButton.layer.borderColor = BitnagilColor.gray97?.cgColor + timeButton.backgroundColor = .white + timeButton.setTitleColor(BitnagilColor.gray30, for: .normal) + timeButton.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font + + checkLabel.text = "하루종일" + checkLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font + + checkButtonView.layer.cornerRadius = 4 + checkButtonView.layer.borderWidth = 1 + checkButtonView.backgroundColor = .white + checkButtonView.layer.borderColor = BitnagilColor.gray95?.cgColor + } + + private func configureLayout() { + addSubview(timeLabel) + addSubview(timeButton) + addSubview(checkLabel) + addSubview(checkButtonView) + addSubview(checkButton) + + self.snp.makeConstraints { make in + heightConstraint = make.height.equalTo(Layout.foldedHeight).constraint + } + + timeLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.timeLabelTopSpacing).priority(999) + make.height.equalTo(Layout.timeLabelHeight) + make.leading.equalTo(Layout.edgeSpacing) + } + + timeButton.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.timeButtonTopSpacing).priority(999) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.height.equalTo(Layout.timeButtonHeight) + make.width.equalTo(Layout.timeButtonWidth) + } + + checkButtonView.snp.makeConstraints { make in + make.top.equalTo(timeButton.snp.bottom).offset(Layout.checkButtonViewTopSpacing) + make.size.equalTo(Layout.checkButtonViewSize) + make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) + make.bottom.equalToSuperview().offset(-Layout.edgeSpacing).priority(999) + } + + checkLabel.snp.makeConstraints { make in + make.centerY.equalTo(checkButtonView) + make.trailing.equalTo(checkButtonView.snp.leading).offset(-Layout.checkButtonLabelTrailingSpacing) + } + + checkButton.snp.makeConstraints { make in + make.leading.equalTo(checkLabel) + make.verticalEdges.equalTo(checkButtonView) + make.trailing.equalTo(checkButtonView) + } + } + + func configureTime(time: Date) { + let timeString = time.convertToString(dateType: .amPmTimeShort) + timeButton.setTitle(timeString, for: .normal) + } +} From 44c96e6cd99427fdc1a95653c93b32445cc6b905 Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 16:43:25 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor:=20v1=20=EB=B7=B0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/DatePickerView.swift | 72 -- .../Component/RoutineCreationInputView.swift | 109 --- .../Component/RoutineTimePickerButton.swift | 67 -- .../Component/SubroutineAdditionButton.swift | 63 -- .../View/Component/TimeContentView.swift | 127 ---- .../View/RoutineCreationView.swift | 652 ------------------ 6 files changed, 1090 deletions(-) delete mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/DatePickerView.swift delete mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationInputView.swift delete mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimePickerButton.swift delete mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/SubroutineAdditionButton.swift delete mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift delete mode 100644 Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/DatePickerView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/DatePickerView.swift deleted file mode 100644 index 2651ead6..00000000 --- a/Projects/Presentation/Sources/RoutineCreation/View/Component/DatePickerView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// DatePickerViewController.swift -// Presentation -// -// Created by 이동현 on 7/27/25. -// - -import SnapKit -import UIKit - -protocol DatePickerViewDelegate: AnyObject { - func datePickerView(_ pickerView: DatePickerView, didSelectTime time: Date) -} - -final class DatePickerView: UIViewController { - private enum Layout { - static let datePickerHeight: CGFloat = 195 - static let horizontalSpacing: CGFloat = 20 - static let registerButtonHeight: CGFloat = 54 - static let registerButtonVerticalSpacing: CGFloat = 14 - } - - private let datePicker = UIDatePicker() - private let registerButton = UIButton() - weak var delegate: DatePickerViewDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - configureAttribute() - configureLayout() - } - - private func configureAttribute() { - datePicker.preferredDatePickerStyle = .wheels - datePicker.datePickerMode = .time - datePicker.locale = Locale(identifier: "en_US") - datePicker.backgroundColor = .white - datePicker.tintColor = .black - - registerButton.layer.cornerRadius = 12 - registerButton.layer.masksToBounds = true - registerButton.backgroundColor = BitnagilColor.navy500 - registerButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font - registerButton.setTitle("등록하기", for: .normal) - registerButton.setTitleColor(.white, for: .normal) - registerButton.addAction( - UIAction { [weak self] _ in - guard let self else { return } - self.delegate?.datePickerView(self, didSelectTime: datePicker.date) - dismiss(animated: true) - }, - for: .touchUpInside) - } - - private func configureLayout() { - let safeArea = view.safeAreaLayoutGuide - view.addSubview(datePicker) - view.addSubview(registerButton) - - datePicker.snp.makeConstraints { make in - make.top.horizontalEdges.equalToSuperview() - make.height.equalTo(Layout.datePickerHeight) - } - - registerButton.snp.makeConstraints { make in - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) - make.top.equalTo(datePicker.snp.bottom).offset(Layout.registerButtonVerticalSpacing) - make.bottom.equalTo(safeArea.snp.bottom).offset(-Layout.registerButtonVerticalSpacing) - make.height.equalTo(Layout.registerButtonHeight) - } - } -} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationInputView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationInputView.swift deleted file mode 100644 index b23da4d8..00000000 --- a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineCreationInputView.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// RoutineCreationInputView.swift -// Presentation -// -// Created by 이동현 on 7/20/25. -// - -import SnapKit -import UIKit - -/// 루틴 등록 화면 텍스트 입력 delegate -protocol RoutineCreationInputViewDelegate: AnyObject { - /// 등록한 view를 삭제합니다. - func routineCreationInputViewDidTapDeleteButton(_ sender: RoutineCreationInputView) - - /// 입력 중인 text를 반환합니다. - func routineCreationInputView(_ sender: RoutineCreationInputView, didChangeText text: String) -} - -final class RoutineCreationInputView: UIView { - private enum Layout { - static let textFieldLeadingOffset: CGFloat = 24 - static let textFieldTrailingInset: CGFloat = 51 - static let textFieldHeight: CGFloat = 20 - static let deleteButtonSize: CGFloat = 24 - static let deleteButtonTrailingInset: CGFloat = 8 - } - - private let textField = UITextField() - private let deleteButton = UIButton() - weak var delegate: RoutineCreationInputViewDelegate? - - override init(frame: CGRect) { - super.init(frame: frame) - configureAttribute() - configureLayout() - - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func configureAttribute() { - backgroundColor = BitnagilColor.gray99 - layer.cornerRadius = 12 - - textField.delegate = self - textField.textColor = .black - textField.font = BitnagilFont.init(style: .body2, weight: .semiBold).font - textField.returnKeyType = .done - textField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged) - - deleteButton.setImage(BitnagilIcon.deleteIcon, for: .normal) - deleteButton.addAction( - UIAction { [weak self] _ in - guard let self else { return } - self.delegate?.routineCreationInputViewDidTapDeleteButton(self) - }, for: .touchUpInside) - } - - private func configureLayout() { - addSubview(textField) - addSubview(deleteButton) - - textField.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(Layout.textFieldLeadingOffset) - make.trailing.equalToSuperview().inset(Layout.textFieldTrailingInset) - make.centerY.equalToSuperview() - make.height.equalTo(Layout.textFieldHeight) - } - - deleteButton.snp.makeConstraints { make in - make.size.equalTo(Layout.deleteButtonSize) - make.centerY.equalToSuperview() - make.trailing.equalToSuperview().inset(Layout.deleteButtonTrailingInset) - } - } - - func configure(canDelete: Bool, placeholder: String?) { - deleteButton.isHidden = !canDelete - - guard - let placeholder, - let placeholderColor = BitnagilColor.gray90 - else { return } - - textField.attributedPlaceholder = NSAttributedString( - string: placeholder, - attributes: [ - .foregroundColor: placeholderColor, - .font: BitnagilFont.init(style: .body2, weight: .semiBold).font]) - } - - func configure(title: String) { - textField.text = title - } - - @objc private func textFieldEditingChanged(_ sender: UITextField) { - delegate?.routineCreationInputView(self, didChangeText: sender.text ?? "") - } -} - -extension RoutineCreationInputView: UITextFieldDelegate { - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - return true - } -} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimePickerButton.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimePickerButton.swift deleted file mode 100644 index dd546292..00000000 --- a/Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineTimePickerButton.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// RoutineTimePickerButton.swift -// Presentation -// -// Created by 이동현 on 7/27/25. -// - -import SnapKit -import UIKit - -final class RoutineTimePickerButton: UIButton { - private enum Layout { - static let chevronDownImageViewTrailingSpacing: CGFloat = 19 - static let chevronDownImageViewSize: CGFloat = 20 - static let labelLeadingSpacing: CGFloat = 24 - } - - private let chevronDownImageView = UIImageView() - private let label = UILabel() - - override init(frame: CGRect) { - super.init(frame: frame) - configureAttribute() - configureLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func configureAttribute() { - layer.borderWidth = 1 - layer.borderColor = BitnagilColor.navy100?.cgColor - layer.cornerRadius = 12 - backgroundColor = .white - - chevronDownImageView.contentMode = .scaleAspectFit - chevronDownImageView.tintColor = BitnagilColor.navy400 - chevronDownImageView.image = BitnagilIcon - .chevronIcon(direction: .down)? - .withRenderingMode(.alwaysTemplate) - - label.text = "시간 선택" - label.textColor = BitnagilColor.gray40 - label.font = BitnagilFont.init(style: .body2, weight: .medium).font - } - - private func configureLayout() { - addSubview(chevronDownImageView) - addSubview(label) - - chevronDownImageView.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(Layout.chevronDownImageViewTrailingSpacing) - make.centerY.equalToSuperview() - make.size.equalTo(Layout.chevronDownImageViewSize) - } - - label.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(Layout.labelLeadingSpacing) - make.centerY.equalToSuperview() - } - } - - func configure(title: String) { - label.text = title - } -} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/SubroutineAdditionButton.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/SubroutineAdditionButton.swift deleted file mode 100644 index d5aaeaea..00000000 --- a/Projects/Presentation/Sources/RoutineCreation/View/Component/SubroutineAdditionButton.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// DetailRoutineAdditionButton.swift -// Presentation -// -// Created by 이동현 on 7/21/25. -// - -import SnapKit -import UIKit - -final class SubroutineAdditionButton: UIButton { - private enum Layout { - static let plusImageViewLeadingSpacing: CGFloat = 24 - static let plusImageViewSize: CGFloat = 20 - static let labelLeadingSpacing: CGFloat = 10 - } - - private let plusImageView = UIImageView() - private let label = UILabel() - - override init(frame: CGRect) { - super.init(frame: frame) - configureAttribute() - configureLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func configureAttribute() { - layer.borderWidth = 1 - layer.borderColor = BitnagilColor.navy100?.cgColor - layer.cornerRadius = 12 - backgroundColor = .white - - plusImageView.contentMode = .scaleAspectFit - plusImageView.tintColor = BitnagilColor.navy400 - plusImageView.image = BitnagilIcon - .routineCreationIcon? - .withRenderingMode(.alwaysTemplate) - - label.text = "세부루틴 추가" - label.textColor = BitnagilColor.gray40 - label.font = BitnagilFont.init(style: .body2, weight: .medium).font - } - - private func configureLayout() { - addSubview(plusImageView) - addSubview(label) - - plusImageView.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(Layout.plusImageViewLeadingSpacing) - make.centerY.equalToSuperview() - make.size.equalTo(Layout.plusImageViewSize) - } - - label.snp.makeConstraints { make in - make.leading.equalTo(plusImageView.snp.trailing).offset(Layout.labelLeadingSpacing) - make.centerY.equalToSuperview() - } - } -} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift deleted file mode 100644 index f1c22577..00000000 --- a/Projects/Presentation/Sources/RoutineCreation/View/Component/TimeContentView.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// TimeContentView.swift -// Presentation -// -// Created by 이동현 on 8/15/25. -// - -import Domain -import SnapKit -import UIKit - -final class RoutineTimeContentView: UIView, RoutineCreationExpandable { - private enum Layout { - static let edgeSpacing: CGFloat = 19 - static let timeLabelTopSpacing: CGFloat = 14 - static let timeLabelHeight: CGFloat = 39 - static let timeButtonTopSpacing: CGFloat = 14 - static let timeButtonHeight: CGFloat = 39 - static let timeButtonWidth: CGFloat = 116 - static let checkButtonViewTopSpacing: CGFloat = 21 - static let checkButtonViewSize: CGFloat = 18 - static let checkButtonLabelHeight: CGFloat = 20 - static let checkButtonLabelTrailingSpacing: CGFloat = 6 - static let foldedHeight: CGFloat = 0 - } - - enum Action {} - - struct Dependencies {} - - private let timeLabel = UILabel() - private let timeButton = UIButton() - private let checkButtonView = UIView() - private let checkLabel = UILabel() - private let checkButton = UIButton() - - var heightConstraint: Constraint? - var action: ((Action) -> Void)? - - init() { - super.init(frame: .zero) - configureAttribute() - configureLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setExpanded(expanded: Bool) { - guard heightConstraint?.isActive == expanded else { return } - - subviews.forEach { $0.alpha = 0 } - defer { self.subviews.forEach { $0.alpha = 1 } } - - self.heightConstraint?.isActive = !expanded - self.subviews.forEach { $0.isHidden = !expanded } - } - - private func configureAttribute() { - timeLabel.text = "시작 시간" - timeLabel.font = BitnagilFont.init(style: .body2, weight: .semiBold).font - - timeButton.layer.cornerRadius = 12 - timeButton.layer.borderWidth = 1 - timeButton.layer.borderColor = BitnagilColor.gray97?.cgColor - timeButton.backgroundColor = .white - timeButton.setTitleColor(BitnagilColor.gray30, for: .normal) - timeButton.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font - - checkLabel.text = "하루종일" - checkLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font - - checkButtonView.layer.cornerRadius = 4 - checkButtonView.layer.borderWidth = 1 - checkButtonView.backgroundColor = .white - checkButtonView.layer.borderColor = BitnagilColor.gray95?.cgColor - } - - private func configureLayout() { - addSubview(timeLabel) - addSubview(timeButton) - addSubview(checkLabel) - addSubview(checkButtonView) - addSubview(checkButton) - - self.snp.makeConstraints { make in - heightConstraint = make.height.equalTo(Layout.foldedHeight).constraint - } - - timeLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(Layout.timeLabelTopSpacing).priority(999) - make.height.equalTo(Layout.timeLabelHeight) - make.leading.equalTo(Layout.edgeSpacing) - } - - timeButton.snp.makeConstraints { make in - make.top.equalToSuperview().offset(Layout.timeButtonTopSpacing).priority(999) - make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) - make.height.equalTo(Layout.timeButtonHeight) - make.width.equalTo(Layout.timeButtonWidth) - } - - checkButtonView.snp.makeConstraints { make in - make.top.equalTo(timeButton.snp.bottom).offset(Layout.checkButtonViewTopSpacing) - make.size.equalTo(Layout.checkButtonViewSize) - make.trailing.equalToSuperview().offset(-Layout.edgeSpacing) - make.bottom.equalToSuperview().offset(-Layout.edgeSpacing).priority(999) - } - - checkLabel.snp.makeConstraints { make in - make.centerY.equalTo(checkButtonView) - make.trailing.equalTo(checkButtonView.snp.leading).offset(-Layout.checkButtonLabelTrailingSpacing) - } - - checkButton.snp.makeConstraints { make in - make.leading.equalTo(checkLabel) - make.verticalEdges.equalTo(checkButtonView) - make.trailing.equalTo(checkButtonView) - } - } - - func configureTime(time: Date) { - let timeString = time.convertToString(dateType: .amPmTimeShort) - timeButton.setTitle(timeString, for: .normal) - } -} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift b/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift deleted file mode 100644 index fd6d9caf..00000000 --- a/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift +++ /dev/null @@ -1,652 +0,0 @@ -// -// RoutineCreationViewController.swift -// Presentation -// -// Created by 이동현 on 7/20/25. -// - -import Combine -import SnapKit -import UIKit - -final class RoutineCreationView: BaseViewController { - private enum Layout { - static let horizontalInset: CGFloat = 20 - static let nameTitleLabelTopSpacing: CGFloat = 32 - static let titleLabelHeight: CGFloat = 24 - static let titleLabelTopSpacing: CGFloat = 40 - static let titleLabelBottomSpacing: CGFloat = 14 - static let titleAsteriskTopSpacing: CGFloat = 2 - static let titleAsteriskLeadingSpacing: CGFloat = 2 - static let titleAsteriskHeight: CGFloat = 24 - static let titleAsteriskWidth: CGFloat = 9 - static let inputViewHeight: CGFloat = 52 - static let subroutineStackViewHeight: CGFloat = 0 - static let subroutineStackViewSpacing: CGFloat = 10 - static let warningLabelTopSpacing: CGFloat = 8 - static let warningLabelHeight: CGFloat = 18 - static let repeatButtonHeight: CGFloat = 52 - static let repeatWeekDayButtonLeadingSpacing: CGFloat = 13 - static let infoButtonSize: CGFloat = 16 - static let infoButtonLeadingSpacing: CGFloat = 6 - static let tooltipViewHeight: CGFloat = 47 - static let tooltipViewLeadingSpacing: CGFloat = 22 - static let tooltipViewBottomSpacing: CGFloat = 8 - static let tooltipViewTailLeadingSpacing: CGFloat = 16 - static let detailTooltipWidth: CGFloat = 209 - static let repeatTooltipWidth: CGFloat = 262 - static let weekDayStackViewTopSpacing: CGFloat = 16 - static let weekDayStackViewSpacing: CGFloat = 10 - static let weekDayStackViewHeight: CGFloat = 48 - static let allDayButtonSize: CGFloat = 20 - static let allDayButtonTrailingSpacing: CGFloat = 6 - static let allDayLabelWidth: CGFloat = 55 - static let registerButtonTopSpacing: CGFloat = 54 - static let registerButtonHeight: CGFloat = 54 - static let registerButtonBottomSpacing: CGFloat = 14 - static let datePickerBottomSheetHeight: CGFloat = 347 - } - - private let scrollView = UIScrollView() - private let contentView = UIView() - - private let nameTitleLabel = UILabel() - private let nameAsterisk = UIImageView() - private let nameInputView = RoutineCreationInputView() - - private let detailTitleLabel = UILabel() - private let detailInfoButton = UIButton() - private let subroutineAdditionButton = SubroutineAdditionButton() - private let subroutineStackView = UIStackView() - private let detailWarningLabel = UILabel() - private let detailToolTipView = TooltipView(tailPosition: .offsetFromLeading(Layout.tooltipViewTailLeadingSpacing)) - - private let repeatTitleLabel = UILabel() - private let repeatInfoButton = UIButton() - private let repeatToolTipView = TooltipView(tailPosition: .offsetFromLeading(Layout.tooltipViewTailLeadingSpacing)) - private let repeatDailyButton = UIButton() - private let repeatWeekButton = UIButton() - - private let startTimeTitleLabel = UILabel() - private let startTimeAsterisk = UIImageView() - private let timePickerButton = RoutineTimePickerButton() - private let allDayButton = UIButton() - private let allDayLabelButton = UIButton() - private let allDayLabel = UILabel() - private let weekdaysStackView = UIStackView() - - private let registerButton = UIButton() - - private let navigationTitle: String - private let registerButtonTitle: String - private var repeatRoutineTitleTopConstraint: Constraint? - private var startTimeTitleTopConstraint: Constraint? - - private var cancellables = Set() - - init(viewModel: RoutineCreationViewModel, routineId: String? = nil) { - navigationTitle = routineId == nil ? "루틴 등록" : "루틴 수정" - registerButtonTitle = routineId == nil ? "등록하기" : "수정하기" - - super.init(viewModel: viewModel) - if let routineId { - viewModel.action(input: .fetchRoutine(id: routineId)) - } - } - - init(viewModel: RoutineCreationViewModel, recommendRoutineId: Int) { - navigationTitle = "루틴 등록" - registerButtonTitle = "등록하기" - - super.init(viewModel: viewModel) - viewModel.action(input: .fetchRecommendedRoutine(id: recommendRoutineId)) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) -// configureNavigationBar(navigationStyle: .withBackButton(title: navigationTitle)) - } - - override func configureAttribute() { - configureSubroutineStackView() - configureWeekdaysStackView() - configureButtonActions() - - let titleLabels = [ - nameTitleLabel, - detailTitleLabel, - repeatTitleLabel, - startTimeTitleLabel] - let infoButtons = [detailInfoButton, repeatInfoButton] - let repeatButtons = [repeatDailyButton, repeatWeekButton] - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - - view.addGestureRecognizer(tapGesture) - view.backgroundColor = .white - tapGesture.cancelsTouchesInView = false - - titleLabels.forEach { - $0.textColor = .black - $0.font = BitnagilFont.init(style: .body1, weight: .semiBold).font - } - - nameAsterisk.image = BitnagilIcon.asteriskIcon - startTimeAsterisk.image = BitnagilIcon.asteriskIcon - nameAsterisk.contentMode = .scaleAspectFit - startTimeAsterisk.contentMode = .scaleAspectFit - - infoButtons.forEach { - $0.setImage(BitnagilIcon.informationIcon, for: .normal) - } - - repeatButtons.forEach { - $0.layer.borderWidth = 1 - $0.layer.cornerRadius = 12 - $0.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .semiBold).font - } - - nameTitleLabel.text = "루틴 이름" - detailTitleLabel.text = "세부 루틴" - repeatTitleLabel.text = "루틴 반복" - startTimeTitleLabel.text = "시작 시간" - - nameInputView.delegate = self - nameInputView.configure( - canDelete: false, - placeholder: "ex) \(RoutineCreationViewModel.RoutineExample.wakeUp.rawValue)") - - allDayButton.setImage(BitnagilIcon.uncheckedIcon, for: .normal) - - detailWarningLabel.text = "* 세부 루틴은 최대 3개까지 입력이 가능해요" - detailWarningLabel.textColor = BitnagilColor.error - detailWarningLabel.font = BitnagilFont(style: .caption1, weight: .medium).font - - detailToolTipView.configure(message: "어려운 루틴이라면 단계별로 나눠보세요!") - repeatToolTipView.configure(message: "선택하지 않을 경우, 당일 루틴으로만 자동 설정돼요.") - detailToolTipView.isHidden = true - repeatToolTipView.isHidden = true - - repeatDailyButton.setTitle("매일", for: .normal) - repeatWeekButton.setTitle("요일 선택", for: .normal) - - allDayLabel.text = "하루 종일" - allDayLabel.textColor = BitnagilColor.navy400 - allDayLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font - - registerButton.backgroundColor = BitnagilColor.navy50 - registerButton.setTitleColor(.white, for: .normal) - registerButton.setTitleColor(BitnagilColor.gray70, for: .disabled) - - registerButton.layer.cornerRadius = 12 - registerButton.layer.masksToBounds = true - registerButton.setTitle(registerButtonTitle, for: .normal) - registerButton.isEnabled = false - registerButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font - } - - override func configureLayout() { - let safeArea = view.safeAreaLayoutGuide - - view.addSubview(scrollView) - scrollView.addSubview(contentView) - - contentView.addSubview(nameTitleLabel) - contentView.addSubview(nameAsterisk) - contentView.addSubview(nameInputView) - - contentView.addSubview(detailTitleLabel) - contentView.addSubview(detailInfoButton) - contentView.addSubview(detailToolTipView) - contentView.addSubview(subroutineAdditionButton) - contentView.addSubview(subroutineStackView) - contentView.addSubview(detailWarningLabel) - - contentView.addSubview(repeatTitleLabel) - contentView.addSubview(repeatToolTipView) - contentView.addSubview(repeatInfoButton) - contentView.addSubview(repeatDailyButton) - contentView.addSubview(repeatWeekButton) - contentView.addSubview(weekdaysStackView) - - contentView.addSubview(startTimeTitleLabel) - contentView.addSubview(startTimeAsterisk) - contentView.addSubview(allDayButton) - contentView.addSubview(allDayLabelButton) - contentView.addSubview(allDayLabel) - contentView.addSubview(timePickerButton) - - contentView.addSubview(registerButton) - - scrollView.snp.makeConstraints { make in - make.edges.equalTo(safeArea) - } - contentView.snp.makeConstraints { make in - make.edges.equalTo(scrollView.contentLayoutGuide) - make.width.equalTo(scrollView.frameLayoutGuide) - make.height.equalTo(scrollView.frameLayoutGuide).priority(.low) - } - - nameTitleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(Layout.nameTitleLabelTopSpacing) - make.leading.equalToSuperview().offset(Layout.horizontalInset) - make.height.equalTo(Layout.titleLabelHeight) - } - - nameAsterisk.snp.makeConstraints { make in - make.top.equalTo(nameTitleLabel).offset(-Layout.titleAsteriskTopSpacing) - make.leading.equalTo(nameTitleLabel.snp.trailing).offset(Layout.titleAsteriskLeadingSpacing) - make.height.equalTo(Layout.titleAsteriskHeight) - make.width.equalTo(Layout.titleAsteriskWidth) - } - - nameInputView.snp.makeConstraints { make in - make.top.equalTo(nameTitleLabel.snp.bottom).offset(Layout.titleLabelBottomSpacing) - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.height.equalTo(Layout.inputViewHeight) - } - - detailTitleLabel.snp.makeConstraints { make in - make.top.equalTo(nameInputView.snp.bottom).offset(Layout.titleLabelTopSpacing) - make.leading.equalToSuperview().offset(Layout.horizontalInset) - make.height.equalTo(Layout.titleLabelHeight) - } - - detailInfoButton.snp.makeConstraints { make in - make.leading.equalTo(detailTitleLabel.snp.trailing).offset(Layout.infoButtonLeadingSpacing) - make.size.equalTo(Layout.infoButtonSize) - make.centerY.equalTo(detailTitleLabel) - } - - detailToolTipView.snp.makeConstraints { make in - make.leading.equalTo(detailInfoButton.snp.centerX).offset(-Layout.tooltipViewLeadingSpacing) - make.bottom.equalTo(detailInfoButton.snp.top).offset(-Layout.tooltipViewBottomSpacing) - make.width.equalTo(Layout.detailTooltipWidth) - make.height.equalTo(Layout.tooltipViewHeight) - } - - subroutineAdditionButton.snp.makeConstraints { make in - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.top.equalTo(detailTitleLabel.snp.bottom).offset(Layout.titleLabelBottomSpacing) - make.height.equalTo(Layout.inputViewHeight) - } - - subroutineStackView.snp.makeConstraints { make in - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.top.equalTo(subroutineAdditionButton.snp.bottom).offset(Layout.subroutineStackViewSpacing) - make.height.equalTo(Layout.subroutineStackViewHeight).priority(.medium) - } - - detailWarningLabel.snp.makeConstraints { make in - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.top.equalTo(subroutineStackView.snp.bottom).offset(Layout.warningLabelTopSpacing) - make.height.equalTo(Layout.warningLabelHeight) - } - - repeatTitleLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(Layout.horizontalInset) - make.height.equalTo(Layout.titleLabelHeight) - repeatRoutineTitleTopConstraint = make.top - .equalTo(subroutineStackView.snp.bottom) - .offset(Layout.titleLabelTopSpacing) - .constraint - } - - repeatInfoButton.snp.makeConstraints { make in - make.leading.equalTo(repeatTitleLabel.snp.trailing).offset(Layout.infoButtonLeadingSpacing) - make.size.equalTo(Layout.infoButtonSize) - make.centerY.equalTo(repeatTitleLabel) - } - - repeatToolTipView.snp.makeConstraints { make in - make.leading.equalTo(repeatInfoButton.snp.centerX).offset(-Layout.tooltipViewLeadingSpacing) - make.bottom.equalTo(repeatInfoButton.snp.top).offset(-Layout.tooltipViewBottomSpacing) - make.width.equalTo(Layout.repeatTooltipWidth) - make.height.equalTo(Layout.tooltipViewHeight) - } - - repeatDailyButton.snp.makeConstraints { make in - make.top.equalTo(repeatTitleLabel.snp.bottom).offset(Layout.titleLabelBottomSpacing) - make.leading.equalToSuperview().offset(Layout.horizontalInset) - make.height.equalTo(Layout.repeatButtonHeight) - } - - repeatWeekButton.snp.makeConstraints { make in - make.top.equalTo(repeatTitleLabel.snp.bottom).offset(Layout.titleLabelBottomSpacing) - make.leading.equalTo(repeatDailyButton.snp.trailing).offset(Layout.repeatWeekDayButtonLeadingSpacing) - make.trailing.equalToSuperview().inset(Layout.horizontalInset) - make.height.equalTo(Layout.repeatButtonHeight) - make.width.equalTo(repeatDailyButton) - } - - weekdaysStackView.snp.makeConstraints { make in - make.top.equalTo(repeatDailyButton.snp.bottom).offset(Layout.weekDayStackViewTopSpacing) - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.height.equalTo(Layout.weekDayStackViewHeight) - } - - startTimeTitleLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(Layout.horizontalInset) - make.height.equalTo(Layout.titleLabelHeight) - startTimeTitleTopConstraint = make.top - .equalTo(repeatDailyButton.snp.bottom) - .offset(Layout.titleLabelTopSpacing) - .constraint - } - - startTimeAsterisk.snp.makeConstraints { make in - make.top.equalTo(startTimeTitleLabel).offset(-Layout.titleAsteriskTopSpacing) - make.leading.equalTo(startTimeTitleLabel.snp.trailing).offset(Layout.titleAsteriskLeadingSpacing) - make.height.equalTo(Layout.titleAsteriskHeight) - make.width.equalTo(Layout.titleAsteriskWidth) - } - - allDayLabel.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(Layout.horizontalInset) - make.width.equalTo(Layout.allDayLabelWidth) - make.centerY.equalTo(startTimeTitleLabel) - } - - allDayButton.snp.makeConstraints { make in - make.trailing.equalTo(allDayLabel.snp.leading).offset(-Layout.allDayButtonTrailingSpacing) - make.size.equalTo(Layout.allDayButtonSize) - make.centerY.equalTo(startTimeTitleLabel) - } - - allDayLabelButton.snp.makeConstraints { make in - make.edges.equalTo(allDayLabel) - } - - timePickerButton.snp.makeConstraints { make in - make.top.equalTo(startTimeTitleLabel.snp.bottom).offset(Layout.titleLabelBottomSpacing) - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.height.equalTo(Layout.inputViewHeight) - } - - registerButton.snp.makeConstraints { make in - make.top.equalTo(timePickerButton.snp.bottom).offset(Layout.registerButtonTopSpacing) - make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset) - make.height.equalTo(Layout.registerButtonHeight) - make.bottom.equalToSuperview().inset(Layout.registerButtonBottomSpacing) - } - } - - override func bind() { - viewModel.output.namePublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] name in - self?.nameInputView.configure(title: name ?? "") - } - .store(in: &cancellables) - - viewModel.output.subRoutinesPublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] routines in - var repeatRoutineTitleLabelTopSpacing = Layout.titleLabelTopSpacing - - self? - .subroutineStackView - .subviews - .enumerated() - .forEach { - guard let inputView = $0.element as? RoutineCreationInputView else { return } - - if $0.offset < routines.count { - inputView.configure(title: routines[$0.offset]) - inputView.isHidden = false - } else { - inputView.isHidden = true - } - } - - self?.detailWarningLabel.isHidden = routines.count < 3 - if !(self?.detailWarningLabel.isHidden ?? true) { - repeatRoutineTitleLabelTopSpacing += (Layout.warningLabelHeight + Layout.warningLabelTopSpacing) - self?.repeatRoutineTitleTopConstraint?.update(offset: repeatRoutineTitleLabelTopSpacing) - } - } - .store(in: &cancellables) - - viewModel.output.repeatTypePublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] repeatType in - var startTimeLabelTopSpacing = Layout.titleLabelTopSpacing - - switch repeatType { - case .none: - self?.repeatDailyButton.isSelected = false - self?.repeatWeekButton.isSelected = false - self?.weekdaysStackView.isHidden = true - case .daily: - self?.repeatDailyButton.isSelected = true - self?.repeatWeekButton.isSelected = false - self?.weekdaysStackView.isHidden = true - case .week: - self?.repeatDailyButton.isSelected = false - self?.repeatWeekButton.isSelected = true - self?.weekdaysStackView.isHidden = false - startTimeLabelTopSpacing += (Layout.weekDayStackViewHeight + Layout.weekDayStackViewTopSpacing) - } - - self?.startTimeTitleTopConstraint?.update(offset: startTimeLabelTopSpacing) - self?.configureRepeatButton() - } - .store(in: &cancellables) - - viewModel.output.weekDayPublisher - .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] weekDays in - let weekDaysIndex = weekDays.map { $0.id } - - self?.weekdaysStackView - .subviews - .enumerated() - .forEach { - let isContains = weekDaysIndex.contains($0.offset) - $0.element.backgroundColor = isContains - ? BitnagilColor.lightBlue100 - : .white - } - }) - .store(in: &cancellables) - - viewModel.output.executionTimePublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] executionType in - self?.timePickerButton.configure(title: executionType.description) - let allDayButtonImage = executionType == "하루종일" - ? BitnagilIcon.checkedIcon - : BitnagilIcon.uncheckedIcon - self?.allDayButton.setImage(allDayButtonImage, for: .normal) - } - .store(in: &cancellables) - - viewModel.output.isRoutineValid - .receive(on: DispatchQueue.main) - .sink { [weak self] isRegisterButtonEnabled in - self?.registerButton.isEnabled = isRegisterButtonEnabled - self?.registerButton.backgroundColor = isRegisterButtonEnabled - ? BitnagilColor.navy500 - : BitnagilColor.navy50 - } - .store(in: &cancellables) - } - - private func configureButtonActions() { - subroutineAdditionButton.addAction( - UIAction { [weak self] _ in - self?.viewModel.action(input: .addSubRoutine) - }, - for: .touchUpInside) - - detailInfoButton.addAction( - UIAction { [weak self] _ in - self?.detailInfoButton.isSelected.toggle() - if self?.detailInfoButton.isSelected ?? false { - self?.detailToolTipView.showTooltip() - } else { - self?.detailToolTipView.hideTooltip() - } - }, - for: .touchUpInside) - - repeatInfoButton.addAction( - UIAction { [weak self] _ in - self?.repeatInfoButton.isSelected.toggle() - if self?.repeatInfoButton.isSelected ?? false { - self?.repeatToolTipView.showTooltip() - } else { - self?.repeatToolTipView.hideTooltip() - } - }, - for: .touchUpInside) - - repeatDailyButton.addAction( - UIAction { [weak self] _ in - self?.viewModel.action(input: .configureRepeatType(type: .daily)) - }, - for: .touchUpInside) - - repeatWeekButton.addAction( - UIAction { [weak self] _ in - self?.viewModel.action(input: .configureRepeatType(type: .week)) - }, - for: .touchUpInside) - - [allDayButton, allDayLabelButton].forEach { - $0.addAction( - UIAction { [weak self] _ in - self?.viewModel.action(input: .configureExecution(type: .allDay)) - }, - for: .touchUpInside) - } - - timePickerButton.addAction( - UIAction { [weak self] _ in - let datePickerView = DatePickerView() - datePickerView.delegate = self - self?.presentCustomBottomSheet(contentViewController: datePickerView, maxHeight: Layout.datePickerBottomSheetHeight) - }, - for: .touchUpInside) - - registerButton.addAction( - UIAction { [weak self] _ in - self?.viewModel.action(input: .registerRoutine) - self?.navigationController?.popViewController(animated: true) - }, - for: .touchUpInside) - } - - private func configureSubroutineStackView() { - subroutineStackView.axis = .vertical - subroutineStackView.spacing = Layout.subroutineStackViewSpacing - - RoutineCreationViewModel.RoutineExample - .allCases - .dropFirst() - .enumerated() - .forEach { - let view = RoutineCreationInputView() - let placeholder = "\($0.offset + 1). ex) \($0.element.rawValue)" - view.delegate = self - view.configure(canDelete: true, placeholder: placeholder) - view.snp.makeConstraints { make in - make.height.equalTo(Layout.inputViewHeight) - } - subroutineStackView.addArrangedSubview(view) - } - } - - private func configureWeekdaysStackView() { - weekdaysStackView.isHidden = true - weekdaysStackView.axis = .horizontal - weekdaysStackView.spacing = Layout.weekDayStackViewSpacing - weekdaysStackView.distribution = .fillEqually - - Week.allCases.forEach { week in - let button = UIButton() - button.setTitle(week.koreanValue, for: .normal) - button.layer.cornerRadius = 12 - button.backgroundColor = .white - button.setTitleColor(.black, for: .normal) - button.titleLabel?.font = BitnagilFont.init(style: .body2, weight: .medium).font - button.addAction( - UIAction { [weak self] _ in - self?.viewModel.action(input: .toggleRepeatDay(weekDay: week)) - }, - for: .touchUpInside) - weekdaysStackView.addArrangedSubview(button) - } - } - - private func configureRepeatButton() { - [repeatDailyButton, repeatWeekButton].forEach { - let isSelected = $0.isSelected - let titleColor = isSelected - ? BitnagilColor.navy500 - : BitnagilColor.navy100 - let borderColor = isSelected - ? BitnagilColor.navy500?.cgColor - : BitnagilColor.navy100?.cgColor - - $0.setTitleColor(titleColor, for: .normal) - $0.layer.borderColor = borderColor - } - } - - @objc private func handleTap(_ gesture: UITapGestureRecognizer) { - let location = gesture.location(in: contentView) - - // 탭한 곳이 버튼 영역인 경우 - if detailInfoButton.frame.contains(location) { - repeatInfoButton.isSelected = false - repeatToolTipView.hideTooltip() - return - } - if repeatInfoButton.frame.contains(location) { - detailInfoButton.isSelected = false - detailToolTipView.hideTooltip() - return - } - - // 탭한 곳이 tooltip 영역 밖인 경우 - if !detailToolTipView.frame.contains(location) { - detailInfoButton.isSelected = false - detailToolTipView.hideTooltip() - } - if !repeatToolTipView.frame.contains(location) { - repeatInfoButton.isSelected = false - repeatToolTipView.hideTooltip() - } - - view.endEditing(true) - } -} - -extension RoutineCreationView: RoutineCreationInputViewDelegate { - func routineCreationInputViewDidTapDeleteButton(_ sender: RoutineCreationInputView) { - guard let index = subroutineStackView.subviews.firstIndex(of: sender) else { return } - - viewModel.action(input: .deleteSubRoutine(index: index)) - } - - func routineCreationInputView(_ sender: RoutineCreationInputView, didChangeText text: String) { - if sender === nameInputView { - viewModel.action(input: .configureName(name: text)) - return - } - - guard let index = subroutineStackView.subviews.firstIndex(of: sender) else { return } - - viewModel.action(input: .configureSubRoutine(name: text, index: index)) - } -} - -extension RoutineCreationView: DatePickerViewDelegate { - func datePickerView(_ pickerView: DatePickerView, didSelectTime time: Date) { - viewModel.action(input: .configureExecution(type: .time(startAt: time))) - } -} From dedebe6caccebc83c1783f3bd4bc2fda6b013520 Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 16:44:01 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EC=8B=9C=EA=B0=84,=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=84=A0=ED=83=9D=20View=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/BitnagilCalendarView.swift | 152 ++++++++++++++++++ .../View/Component/TimePickerView.swift | 74 +++++++++ 2 files changed, 226 insertions(+) create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/BitnagilCalendarView.swift create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/Component/TimePickerView.swift diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/BitnagilCalendarView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/BitnagilCalendarView.swift new file mode 100644 index 00000000..ca54ef76 --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/BitnagilCalendarView.swift @@ -0,0 +1,152 @@ +// +// BitnagilCalendarView.swift +// Presentation +// +// Created by 이동현 on 8/16/25. +// + +import FSCalendar +import SnapKit +import UIKit + +protocol BitnagilCalendarViewDelegate: AnyObject { + func bitnagilCalendarView(_ sender: BitnagilCalendarView, didSelectDate date: Date) +} + +final class BitnagilCalendarView: UIViewController { + private enum Layout { + static let horizontalSpacing: CGFloat = 20 + static let chevronImageSize: CGFloat = 15 + static let monthMoveButtonSize: CGFloat = 48 + static let calendarHeaderHeight: CGFloat = 0 + static let calendarHeight: CGFloat = 323 + static let calendarTopSpacing: CGFloat = 15 + static let calendarHorizontalSpacing: CGFloat = 24 + static let confirmButtonHeight: CGFloat = 54 + static let confirmButtonTopSpacing: CGFloat = 27 + static let confirmButtonBottomSpacing: CGFloat = 14 + } + + private let dateLabel = UILabel() + private let nextButton = UIButton() + private let prevButton = UIButton() + private let leftChevronImageView = UIImageView() + private let rightChevronImageView = UIImageView() + private var calendarView = FSCalendar() + private let confirmButton = UIButton() + + var delegate: BitnagilCalendarViewDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + configureAttribute() + configureLayout() + } + + private func configureAttribute() { + configureCalendar() + configureConfirmButton() + + let dateString = Date().convertToString(dateType: .yearMonth) + dateLabel.text = dateString + dateLabel.font = BitnagilFont.init(style: .title2, weight: .bold).font + leftChevronImageView.image = BitnagilIcon + .chevronIcon(direction: .left)? + .withRenderingMode(.alwaysTemplate) + rightChevronImageView.image = BitnagilIcon + .chevronIcon(direction: .right)? + .withRenderingMode(.alwaysTemplate) + leftChevronImageView.tintColor = BitnagilColor.gray95 + rightChevronImageView.tintColor = BitnagilColor.gray10 + prevButton.backgroundColor = .clear + nextButton.backgroundColor = .clear + } + + private func configureLayout() { + let safeArea = view.safeAreaLayoutGuide + view.addSubview(dateLabel) + view.addSubview(leftChevronImageView) + view.addSubview(rightChevronImageView) + view.addSubview(prevButton) + view.addSubview(nextButton) + view.addSubview(calendarView) + view.addSubview(confirmButton) + + dateLabel.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(Layout.horizontalSpacing) + make.centerY.equalTo(prevButton) + } + + prevButton.snp.makeConstraints { make in + make.top.equalToSuperview() + make.trailing.equalTo(nextButton.snp.leading) + make.size.equalTo(Layout.monthMoveButtonSize) + } + + nextButton.snp.makeConstraints { make in + make.top.equalToSuperview() + make.trailing.equalToSuperview() + make.size.equalTo(Layout.monthMoveButtonSize) + } + + leftChevronImageView.snp.makeConstraints { make in + make.size.equalTo(Layout.chevronImageSize) + make.center.equalTo(prevButton) + } + + rightChevronImageView.snp.makeConstraints { make in + make.size.equalTo(Layout.chevronImageSize) + make.center.equalTo(nextButton) + } + + calendarView.snp.makeConstraints { make in + make.top.equalTo(nextButton.snp.bottom).offset(Layout.calendarTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.calendarHorizontalSpacing) + make.height.equalTo(Layout.calendarHeight).priority(.medium) + } + + confirmButton.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + make.top.equalTo(calendarView.snp.bottom).offset(Layout.confirmButtonTopSpacing) + make.bottom.equalTo(safeArea.snp.bottom).offset(-Layout.confirmButtonBottomSpacing) + make.height.equalTo(Layout.confirmButtonHeight) + } + } + + private func configureCalendar() { + calendarView.delegate = self + calendarView.headerHeight = Layout.calendarHeaderHeight + calendarView.appearance.weekdayTextColor = BitnagilColor.gray50 + calendarView.appearance.weekdayFont = BitnagilFont.init(style: .body2, weight: .semiBold).font + calendarView.appearance.titleFont = BitnagilFont.init(style: .subtitle1, weight: .regular).font + calendarView.appearance.todayColor = .clear + calendarView.appearance.titleTodayColor = .black + calendarView.appearance.titleSelectionColor = BitnagilColor.orange500 + calendarView.appearance.selectionColor = BitnagilColor.orange50 + calendarView.appearance.borderRadius = 0.5 + + calendarView.select(Date()) + } + + private func configureConfirmButton() { + confirmButton.layer.cornerRadius = 12 + confirmButton.layer.masksToBounds = true + confirmButton.backgroundColor = BitnagilColor.gray10 + confirmButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font + confirmButton.setTitle("확인", for: .normal) + confirmButton.setTitleColor(.white, for: .normal) + confirmButton.addAction( + UIAction { [weak self] _ in + guard let self else { return } + self.delegate?.bitnagilCalendarView(self, didSelectDate: Date()) + dismiss(animated: true) + }, + for: .touchUpInside) + } +} + +extension BitnagilCalendarView: FSCalendarDelegate { + func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) { + delegate?.bitnagilCalendarView(self, didSelectDate: date) + } +} diff --git a/Projects/Presentation/Sources/RoutineCreation/View/Component/TimePickerView.swift b/Projects/Presentation/Sources/RoutineCreation/View/Component/TimePickerView.swift new file mode 100644 index 00000000..37683ac1 --- /dev/null +++ b/Projects/Presentation/Sources/RoutineCreation/View/Component/TimePickerView.swift @@ -0,0 +1,74 @@ +// +// DatePickerViewController.swift +// Presentation +// +// Created by 이동현 on 7/27/25. +// + +import SnapKit +import UIKit + +protocol TimePickerViewDelegate: AnyObject { + func timePickerView(_ sender: TimePickerView, didSelectTime time: Date) +} + +final class TimePickerView: UIViewController { + private enum Layout { + static let datePickerHeight: CGFloat = 195 + static let horizontalSpacing: CGFloat = 20 + static let registerButtonHeight: CGFloat = 54 + static let registerButtonTopSpacing: CGFloat = 36 + static let registerButtonBottomSpacing: CGFloat = 14 + } + + private let datePicker = UIDatePicker() + private let registerButton = UIButton() + weak var delegate: TimePickerViewDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + configureAttribute() + configureLayout() + } + + private func configureAttribute() { + datePicker.preferredDatePickerStyle = .wheels + datePicker.datePickerMode = .time + + datePicker.backgroundColor = .white + datePicker.tintColor = .black + + registerButton.layer.cornerRadius = 12 + registerButton.layer.masksToBounds = true + registerButton.backgroundColor = BitnagilColor.gray10 + registerButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font + registerButton.setTitle("확인", for: .normal) + registerButton.setTitleColor(.white, for: .normal) + registerButton.addAction( + UIAction { [weak self] _ in + guard let self else { return } + self.delegate?.timePickerView(self, didSelectTime: datePicker.date) + dismiss(animated: true) + }, + for: .touchUpInside) + } + + private func configureLayout() { + let safeArea = view.safeAreaLayoutGuide + view.addSubview(datePicker) + view.addSubview(registerButton) + + datePicker.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + make.height.equalTo(Layout.datePickerHeight).priority(.medium) + } + + registerButton.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + make.top.equalTo(datePicker.snp.bottom).offset(Layout.registerButtonTopSpacing) + make.bottom.equalTo(safeArea.snp.bottom).offset(-Layout.registerButtonBottomSpacing) + make.height.equalTo(Layout.registerButtonHeight) + } + + } +} From 55b39346d6bd201680f63c6838f56e4b8ac028ff Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 16:45:57 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20v2=20=EB=A3=A8=ED=8B=B4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Entity/Enum/RepeatType.swift | 11 + .../View/RecommendedRoutineView.swift | 4 +- .../View/ResultRecommendedRoutineView.swift | 2 +- .../Component/RoutineCreationCardView.swift | 1 + .../Component/RoutinePeriodContentView.swift | 19 +- .../Component/RoutineRepeatContentView.swift | 5 +- .../Component/RoutineTimeContentView.swift | 37 +-- .../View/RoutineCreationViewController.swift | 224 ++++++++++++++++++ .../ViewModel/RoutineCreationViewModel.swift | 11 +- Projects/Shared/Sources/Extension/Date+.swift | 5 + 10 files changed, 287 insertions(+), 32 deletions(-) create mode 100644 Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift diff --git a/Projects/Domain/Sources/Entity/Enum/RepeatType.swift b/Projects/Domain/Sources/Entity/Enum/RepeatType.swift index e69de29b..ee69ff9e 100644 --- a/Projects/Domain/Sources/Entity/Enum/RepeatType.swift +++ b/Projects/Domain/Sources/Entity/Enum/RepeatType.swift @@ -0,0 +1,11 @@ +// +// RepeatType.swift +// Domain +// +// Created by 이동현 on 8/15/25. +// + +public enum RepeatType { + case daily + case weekly +} diff --git a/Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift b/Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift index c35f123c..76fb1886 100644 --- a/Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift +++ b/Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift @@ -303,7 +303,7 @@ extension RecommendedRoutineView: RecommendedRoutineCardViewDelegate { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") } - let routineCreationView = RoutineCreationView(viewModel: routineCreationViewModel, recommendRoutineId: routine.id) + let routineCreationView = RoutineCreationViewController(viewModel: routineCreationViewModel, recommendRoutineId: routine.id) routineCreationView.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(routineCreationView, animated: true) } @@ -326,7 +326,7 @@ extension RecommendedRoutineView: FloatingMenuViewDelegate { guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") } - let routineCreationView = RoutineCreationView(viewModel: routineCreationViewModel) + let routineCreationView = RoutineCreationViewController(viewModel: routineCreationViewModel) routineCreationView.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(routineCreationView, animated: true) } diff --git a/Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift b/Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift index 06003127..1e46be69 100644 --- a/Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift +++ b/Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift @@ -366,7 +366,7 @@ final class ResultRecommendedRoutineView: BaseViewController { + private enum Layout { + static let horizontalSpacing: CGFloat = 20 + static let textFieldHeight: CGFloat = 24 + static let textFieldTopSpacing: CGFloat = 51 + static let textFieldUnderLineTopSpacing: CGFloat = 12 + static let textFieldUnderLineHeight: CGFloat = 2 + static let clearButtonSize: CGFloat = 16 + static let subroutineViewTopSpacing: CGFloat = 40 + static let routineCreationViewTopSpacing: CGFloat = 12 + static let registerButtonTopSpacing: CGFloat = 59 + static let registerButtonHeight: CGFloat = 54 + static let registerButtonBottomSpacing: CGFloat = 14 + static let timePickerBottomSheetHeight: CGFloat = 357 + static let calendarBottomSheetHeight: CGFloat = 517 + } + + private let scrollView = UIScrollView() + private let contentView = UIView() + private let routineTextField = UITextField() + private let textViewUnderLineView = UIView() + private let clearButton = UIButton() + + private let subRoutineNameView = RoutineCreationCardView( + title: "세부루틴", + placeHolder: "ex) 일어나자마자 이불 개기", + titleImage: BitnagilIcon.routineListIcon, + withInfoImage: true, + withAsteriskImage: false) + private let repeatView = RoutineCreationCardView( + title: "반복 요일", + placeHolder: "ex) 매주 월,화,수,목,금", + titleImage: BitnagilIcon.routineRepeatIcon, + withInfoImage: false, + withAsteriskImage: false) + private let periodView = RoutineCreationCardView( + title: "목표 기간", + placeHolder: "ex) 25.08.06 - 25.08.06", + titleImage: BitnagilIcon.routineDateIcon, + withInfoImage: false, + withAsteriskImage: true) + private let timeView = RoutineCreationCardView( + title: "시간", + placeHolder: "ex) 오전 9:40부터 시작", + titleImage: BitnagilIcon.routineTimeIcon, + withInfoImage: false, + withAsteriskImage: true) + + private let registerButton = UIButton() + private let navigationTitle: String + private let registerButtonTitle: String + private var cancellables = Set() + + init(viewModel: RoutineCreationViewModel, routineId: String? = nil) { + navigationTitle = routineId == nil ? "루틴 등록" : "루틴 수정" + registerButtonTitle = routineId == nil ? "등록하기" : "수정하기" + + super.init(viewModel: viewModel) + if let routineId { + viewModel.action(input: .fetchRoutine(id: routineId)) + } + } + + init(viewModel: RoutineCreationViewModel, recommendRoutineId: Int) { + navigationTitle = "루틴 등록" + registerButtonTitle = "등록하기" + + super.init(viewModel: viewModel) + viewModel.action(input: .fetchRecommendedRoutine(id: recommendRoutineId)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + configureNavigationBar(navigationStyle: .withBackButton(title: navigationTitle)) + } + + override func configureAttribute() { + let attributes: [NSAttributedString.Key: Any] = [ + .font: BitnagilFont(style: .title3, weight: .semiBold).font, + .foregroundColor: BitnagilColor.gray90 ?? .systemGray] + + view.backgroundColor = .white + + routineTextField.attributedPlaceholder = NSAttributedString(string: "루틴 제목을 입력해주세요.", attributes: attributes) + routineTextField.font = BitnagilFont(style: .title3, weight: .semiBold).font + routineTextField.textColor = BitnagilColor.gray10 + + textViewUnderLineView.backgroundColor = BitnagilColor.gray90 + + clearButton.setImage(BitnagilIcon.clearIcon, for: .normal) + + registerButton.layer.cornerRadius = 12 + registerButton.layer.masksToBounds = true + registerButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font + registerButton.backgroundColor = BitnagilColor.gray95 + registerButton.setTitle("등록하기", for: .normal) + + subRoutineNameView.configure(dependencies: .init(subRoutines: ["ㄴㄴㄴ", "ㅋㅋㅋ"])) + bindCreationCardViews() + } + + override func configureLayout() { + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(scrollView) + scrollView.addSubview(contentView) + contentView.addSubview(routineTextField) + contentView.addSubview(clearButton) + contentView.addSubview(textViewUnderLineView) + contentView.addSubview(subRoutineNameView) + contentView.addSubview(repeatView) + contentView.addSubview(periodView) + contentView.addSubview(timeView) + contentView.addSubview(registerButton) + + scrollView.snp.makeConstraints { make in + make.edges.equalTo(safeArea) + } + + contentView.snp.makeConstraints { make in + make.edges.equalTo(scrollView.contentLayoutGuide) + make.width.equalTo(scrollView.frameLayoutGuide) + make.height.equalTo(scrollView.frameLayoutGuide).priority(.low) + } + + routineTextField.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Layout.textFieldTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + make.height.equalTo(Layout.textFieldHeight) + } + + clearButton.snp.makeConstraints { make in + make.trailing.equalToSuperview().offset(-Layout.horizontalSpacing) + make.centerY.equalTo(routineTextField) + make.size.equalTo(Layout.clearButtonSize) + } + + textViewUnderLineView.snp.makeConstraints { make in + make.top.equalTo(routineTextField.snp.bottom).offset(Layout.textFieldUnderLineTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + make.height.equalTo(Layout.textFieldUnderLineHeight) + } + + subRoutineNameView.snp.makeConstraints { make in + make.top.equalTo(textViewUnderLineView.snp.bottom).offset(Layout.subroutineViewTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + } + + repeatView.snp.makeConstraints { make in + make.top.equalTo(subRoutineNameView.snp.bottom).offset(Layout.routineCreationViewTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + } + + periodView.snp.makeConstraints { make in + make.top.equalTo(repeatView.snp.bottom).offset(Layout.routineCreationViewTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + } + + timeView.snp.makeConstraints { make in + make.top.equalTo(periodView.snp.bottom).offset(Layout.routineCreationViewTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + } + + registerButton.snp.makeConstraints { make in + make.top.greaterThanOrEqualTo(timeView.snp.bottom).offset(Layout.registerButtonTopSpacing) + make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing) + make.height.equalTo(Layout.registerButtonHeight) + make.bottom.equalToSuperview().inset(Layout.registerButtonBottomSpacing) + } + } + + override func bind() { + + } + + private func bindCreationCardViews() { + periodView.onAction = { [weak self] action in + let datePickerView = BitnagilCalendarView() + datePickerView.delegate = self + + self?.presentCustomBottomSheet(contentViewController: datePickerView, maxHeight: Layout.calendarBottomSheetHeight) + } + + timeView.onAction = { [weak self] action in + switch action { + case .allDayTapped: + print() + case .timeSetTapped: + let datePickerView = TimePickerView() + datePickerView.delegate = self + self?.presentCustomBottomSheet(contentViewController: datePickerView, maxHeight: Layout.timePickerBottomSheetHeight) + } + } + } +} + +extension RoutineCreationViewController: TimePickerViewDelegate { + func timePickerView(_ pickerView: TimePickerView, didSelectTime time: Date) { + viewModel.action(input: .configureExecution(type: .time(startAt: time))) + timeView.configure(dependencies: .init(startTime: time)) + } +} + +extension RoutineCreationViewController: BitnagilCalendarViewDelegate { + func bitnagilCalendarView(_ sender: BitnagilCalendarView, didSelectDate date: Date) { + periodView.configure(dependencies: .init(start: date, end: date)) + } +} diff --git a/Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift b/Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift index 977c5af4..ab626f35 100644 --- a/Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift +++ b/Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift @@ -10,11 +10,6 @@ import Domain import Foundation final class RoutineCreationViewModel: ViewModel { - enum RepeatType { - case daily - case week - } - enum RoutineExample: String, CaseIterable { case wakeUp = "아침에 개운하게 일어나기" case foldBlanket = "일어나자마자 이불 개기" @@ -77,7 +72,7 @@ final class RoutineCreationViewModel: ViewModel { init(routineUseCase: RoutineUseCaseProtocol, recommenededRoutineUseCase: RecommendedRoutineUseCaseProtocol) { self.routineUseCase = routineUseCase self.recommenededRoutineUseCase = recommenededRoutineUseCase - + output = Output( namePublisher: nameSubject.eraseToAnyPublisher(), subRoutinesPublisher: subRoutinesSubject @@ -133,7 +128,7 @@ final class RoutineCreationViewModel: ViewModel { sortOrder: $0.sortOrder) } let weekDay = routine.repeatDay.compactMap { Week(rawValue: $0.rawValue) } - let repeatType: RepeatType = weekDay.count == Week.allCases.count ? .daily : .week + let repeatType: RepeatType = weekDay.count == Week.allCases.count ? .daily : .weekly let executionType: ExecutionType if routine.executionTime == "00:00:00" { @@ -235,7 +230,7 @@ final class RoutineCreationViewModel: ViewModel { switch selectedType { case .daily: weekDaySubject.send(Set(Week.allCases)) - case .week: + case .weekly: if repeatType == .daily { weekDaySubject.send([]) } diff --git a/Projects/Shared/Sources/Extension/Date+.swift b/Projects/Shared/Sources/Extension/Date+.swift index cbfeced5..6f5e3625 100644 --- a/Projects/Shared/Sources/Extension/Date+.swift +++ b/Projects/Shared/Sources/Extension/Date+.swift @@ -8,6 +8,11 @@ import Foundation extension Date { + public var isMidnight: Bool { + let components = Calendar.current.dateComponents([.hour, .minute, .second], from: self) + return components.hour == 0 && components.minute == 0 + } + public func convertToString(dateType: DateType) -> String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "ko_KR") From e8a7c4968c6aa4212cacc94e2d73a34d31299522 Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 16:46:13 +0900 Subject: [PATCH 08/10] =?UTF-8?q?chore:=20FSCalendar=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Presentation/Project.swift | 4 ++++ Tuist/Dependencies.swift | 2 +- Tuist/Package.resolved | 9 +++++++++ Tuist/Package.swift | 3 ++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Projects/Presentation/Project.swift b/Projects/Presentation/Project.swift index f9ecfd0a..c3b7f012 100644 --- a/Projects/Presentation/Project.swift +++ b/Projects/Presentation/Project.swift @@ -2,6 +2,9 @@ import ProjectDescription let project = Project( name: "Presentation", + settings: .settings( + base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] + ), targets: [ .target( name: "Presentation", @@ -15,6 +18,7 @@ let project = Project( .external(name: "SnapKit"), .external(name: "Kingfisher"), .external(name: "Lottie"), + .external(name: "FSCalendar"), .project(target: "Domain", path: "../Domain"), .project(target: "Shared", path: "../Shared") ] diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 14979193..6cc6b8d3 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -4,7 +4,7 @@ let dependencies = Dependencies( swiftPackageManager: .init( [ .remote(url: "https://github.com/SnapKit/SnapKit.git", requirement: .upToNextMajor(from: "5.0.0")), - .remote(url: "https://github.com/devxoul/Then.git", requirement: .upToNextMajor(from: "3.0.0")), + .remote(url: "https://github.com/devxoul/Then.git", requirement: .upToNextMajor(from: "3.0.0")) ], productTypes: [:] ), diff --git a/Tuist/Package.resolved b/Tuist/Package.resolved index cff34c51..e5e60718 100644 --- a/Tuist/Package.resolved +++ b/Tuist/Package.resolved @@ -9,6 +9,15 @@ "version" : "5.10.2" } }, + { + "identity" : "fscalendar", + "kind" : "remoteSourceControl", + "location" : "https://github.com/WenchaoD/FSCalendar.git", + "state" : { + "revision" : "0fbdec5172fccb90f707472eeaea4ffe095278f6", + "version" : "2.8.4" + } + }, { "identity" : "kakao-ios-sdk", "kind" : "remoteSourceControl", diff --git a/Tuist/Package.swift b/Tuist/Package.swift index c0b0cff2..2c91fc53 100644 --- a/Tuist/Package.swift +++ b/Tuist/Package.swift @@ -7,6 +7,7 @@ let package = Package( .package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.0.0"), .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.23.0"), .package(url: "https://github.com/onevcat/Kingfisher.git", from: "8.0.0"), - .package(url: "https://github.com/airbnb/lottie-ios", from: "4.0.0") + .package(url: "https://github.com/airbnb/lottie-ios", from: "4.0.0"), + .package(url: "https://github.com/WenchaoD/FSCalendar.git", from: "2.8.4") ] ) From 7ee7dc04276836926371d3d671c61227c7e703f3 Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 16:46:37 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20v1=20=EB=A3=A8=ED=8B=B4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95=20v2=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Presentation/Sources/Home/View/HomeView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Projects/Presentation/Sources/Home/View/HomeView.swift b/Projects/Presentation/Sources/Home/View/HomeView.swift index 824a2218..6b79be37 100644 --- a/Projects/Presentation/Sources/Home/View/HomeView.swift +++ b/Projects/Presentation/Sources/Home/View/HomeView.swift @@ -597,8 +597,9 @@ extension HomeView: FloatingMenuViewDelegate { guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") } - let routineCreationView = RoutineCreationView(viewModel: routineCreationViewModel) - routineCreationView.hidesBottomBarWhenPushed = true - self.navigationController?.pushViewController(routineCreationView, animated: true) + let routineCreationViewController = RoutineCreationViewController(viewModel: routineCreationViewModel) + routineCreationViewController.hidesBottomBarWhenPushed = true + + self.navigationController?.pushViewController(routineCreationViewController, animated: true) } } From 4387aa923483f1ff1666f78d5a5c78cf65a248d0 Mon Sep 17 00:00:00 2001 From: taipaise Date: Sat, 16 Aug 2025 16:53:50 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20develop=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=20rebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Presentation/Sources/Home/View/HomeView.swift | 2 +- .../RoutineCreation/View/RoutineCreationViewController.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Projects/Presentation/Sources/Home/View/HomeView.swift b/Projects/Presentation/Sources/Home/View/HomeView.swift index 6b79be37..710a8944 100644 --- a/Projects/Presentation/Sources/Home/View/HomeView.swift +++ b/Projects/Presentation/Sources/Home/View/HomeView.swift @@ -166,7 +166,7 @@ final class HomeView: BaseViewController { guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") } - let routineCreationView = RoutineCreationView(viewModel: routineCreationViewModel) + let routineCreationView = RoutineCreationViewController(viewModel: routineCreationViewModel) routineCreationView.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(routineCreationView, animated: true) } diff --git a/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift b/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift index 5aaa071d..f72654b6 100644 --- a/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift +++ b/Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift @@ -87,7 +87,7 @@ final class RoutineCreationViewController: BaseViewController