From 3c0889e36b7de334cc2d0ec2e3263a5f66f6978c Mon Sep 17 00:00:00 2001 From: FpRaArNkK Date: Wed, 30 Oct 2024 17:02:40 +0900 Subject: [PATCH 01/26] =?UTF-8?q?Feat:=20RecordEditView=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Sources/RecordEdit/RecordEditView.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift new file mode 100644 index 00000000..49d7940a --- /dev/null +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift @@ -0,0 +1,15 @@ +// +// RecordEditView.swift +// FeatureHome +// +// Created by 박민서 on 10/30/24. +// + +import SwiftUI +import SharedDesignSystem + +struct RecordEditView: View { + var body: some View { + Text("RecordEditView") + } +} From 9169dc49fdcd1cec3bf47a6141d3d629379528bb Mon Sep 17 00:00:00 2001 From: FpRaArNkK Date: Thu, 31 Oct 2024 15:47:43 +0900 Subject: [PATCH 02/26] =?UTF-8?q?Fix:=20=EA=B8=B0=EB=A1=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?noPic=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/RecordEdit/DiaryEditView.swift | 88 ++++++++++++++++++ .../Sources/RecordEdit/RecordEditView.swift | 15 --- .../NewImage/noPicture.imageset/Contents.json | 6 +- .../NewImage/noPicture.imageset/noPic.png | Bin .../NewImage/noPicture.imageset/noPic@2x.png | Bin .../NewImage/noPicture.imageset/noPic@3x.png | Bin .../\352\270\260\353\241\235.png" | Bin 993 -> 0 bytes .../\352\270\260\353\241\235@2x.png" | Bin 1702 -> 0 bytes .../\352\270\260\353\241\235@3x.png" | Bin 2448 -> 0 bytes 9 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift delete mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift rename "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235 1.png" => Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic.png (100%) rename "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@2x 1.png" => Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic@2x.png (100%) rename "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@3x 1.png" => Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic@3x.png (100%) delete mode 100644 "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235.png" delete mode 100644 "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@2x.png" delete mode 100644 "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@3x.png" diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift new file mode 100644 index 00000000..82cc75d6 --- /dev/null +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift @@ -0,0 +1,88 @@ +// +// RecordEditView.swift +// FeatureHome +// +// Created by 박민서 on 10/30/24. +// + +import SwiftUI +import SharedDesignSystem +import SharedUtil + +public struct RecordEditView: View { + + let isRevise: Bool = false + let recordName: String = "코딩 스터디" + let date: String = Date().toYMDEHM() + let placeName: + var saveButtonState: NamoButton.NamoButtonType = .inactive + + + public init() {} + + public var body: some View { + VStack(spacing: 0) { + Text("RecordEditView") + // DatePlaceView + // EnjoyRateView + // ContentView + // DiaryImageView + // SaveButton + NamoButton( + title: isRevise ? "변경 내용 저장" : "기록 저장", + font: .pretendard(.bold, size: 15), + cornerRadius: 0, + verticalPadding: 30, + type: saveButtonState, + action: { + switch saveButtonState { + case .active: + print("기록 저장") + default: + break + } + } + ) + } + .namoNabBar(center: { + Text(recordName) + .font(.pretendard(.bold, size: 22)) + }, left: { + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icArrowLeftThick) + .frame(width: 32, height: 32) + }) + }, right: { + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icTrashcan) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .disabled(!isRevise) + .hidden(!isRevise) + }) + }) + } +} + +extension RecordEditView { + var datePlaceView: some View { + HStack { + VStack { + datePlaceItem(title: "날짜", content: <#T##String#>) + } + } + } + + func datePlaceItem(title: String, content: String) -> some View { + HStack(spacing: 12) { + Text(title) + .font(.pretendard(.bold, size: 15)) + .foregroundStyle(Color.mainText) + + Text(content) + .font(.pretendard(.regular, size: 15)) + .foregroundStyle(Color.mainText) + } + } +} diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift deleted file mode 100644 index 49d7940a..00000000 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/RecordEditView.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// RecordEditView.swift -// FeatureHome -// -// Created by 박민서 on 10/30/24. -// - -import SwiftUI -import SharedDesignSystem - -struct RecordEditView: View { - var body: some View { - Text("RecordEditView") - } -} diff --git a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/Contents.json b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/Contents.json index 0e8d1f5d..453b9299 100644 --- a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/Contents.json +++ b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "기록 1.png", + "filename" : "noPic.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "기록@2x 1.png", + "filename" : "noPic@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "기록@3x 1.png", + "filename" : "noPic@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git "a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235 1.png" b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic.png similarity index 100% rename from "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235 1.png" rename to Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic.png diff --git "a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@2x 1.png" b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic@2x.png similarity index 100% rename from "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@2x 1.png" rename to Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic@2x.png diff --git "a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@3x 1.png" b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic@3x.png similarity index 100% rename from "Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@3x 1.png" rename to Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/noPic@3x.png diff --git "a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235.png" "b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235.png" deleted file mode 100644 index 2fcddbac1b694259f2813a94870ef3c81a3aa69c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 993 zcmV<710MW|P)RlS;6JyWh0K`8_#^A^F_Y0 z(P(s|>(uZRw{+gDtY9!0q|Uj6zdxw@W6^3M3nyfX!){8*1ZF`*L`1YS(<^W?09oL| za4clSm{;n?Fc4!Hh%pSr7zSdDL*tT=3Cx0sh=_>Z3w|%l4t_7oAO>O#12Kky7{fq} zVIamh^v@77fmsj{5fLp*gs#WFZo+S^*XwOQNLOXc)(f_>aaiu&n9UW|sM? zlSS@crpsEyu5W3AtjpTn-A(Y28VW9uy}i9_eLBKeCi?nBY9^>auvO?W;P&k7tfgm5 zALRL1-D^mVU}%lL1Vp!t=jYAM&Ei;K@)UL+$in%CII>8$jO^*@>8I|he(M?2wf+KM z%MSWD#ogcE|EBNTmVh9O;Eo~Z=jWevch$s013G?Ljxg5O*T>kk>11%B1bLQ)t-`lu zOvCtodU}doF)jdu3Z*tpxU*ptTaJeELjnt>7J-z7Q3x%R-~*`&qc9ta80Roxqy*|; z!YIsyVivM+6{B~W_CH}1rlFXHEP~u+%@al_f(xY1VL(uU)IAKa9>^_1z>7tMQ7p?c z>n(levZrs99AqxzxaVTU0 zvf!1vG12UuKD9nR?Q}Y&-G=pji9U@>7ZOF2cDwDpT~F2BL~)y+Znrz}%!kiQ%E~Zc zq;I@PjH**Hs7~e^>mL0xs=8&v~EcJkR?)?{j{?H{H(%yJXQ1 ziy#ol5>F3Te^B51B(MeGUWu<_gKFVXkHBaM#K7#6%=7d&9RrQ?qW!U3Ayp5JLcT(C(uEAR*y0mradxooz~` zG7@DJoG4ejKm}LJ`r@A%Xm7d1(^>=5hiNR*fX;`WI2jgvF8`OlcCg{U*YC@^Vw`R} zjbZXv^}fL3;%qC*NaXl@Olaw7!_u982py!(w{YkQ%n8gz4C0$P)wW<=fAP4+mvj(F zBDPB|mz#v-dauPnt2%>v!WMnL``^KTmGs$+Fs`H6&2yAPzb1MeY+v9rrOFx^8A)~x zpHlP*`2vCZP&NLom|N7TEgY1qR4Q?Md%I;jUYe2|Kuf_g;L?nYDO>4|xs4g}`e$4+ zZTm+5I9u-@TNdrs7C01h9vqWKA?`@`U z15MA6>|X1R5{i<9uk58^5ecR04JTueP(y2L+AZqLo9=7GW?eHekp*1hrYc7rw#S$g zrVoWT9`9AGSLB;fN=6B_H8nNN*RFNb4Ap+P?dFWY(jv<4VWq4|LOAdkRYlE_4Bfmj zQpnTdxHwIWO8G_jJ+bhBCR>ZxYCb>V9G11u=;w=$`;bcU9M3t6Ens@^hz1AURA z*)wCXh|Q1I((^oct25ctc_k~UG!>l22(FV?Vn^If6I1Fz7`m~S zS#*QGgUq(AQZYgu?jG+|vFkk0nVruCEh&K&iqPf2*kM?NSOoB45h)^I1{UEa0-P5{ z%ohO(V5nieKc{LE)Z4EI&Wy|C3zyOy3$nliLBey z1dcoXAT1xWCu$cf`_=gSW^i0YAhwRrpBb-V#Z#wHs;f6QEtxM6U1APKGJVN5UUTOdoSG*?dS@naQny|_u*EmMQKe-~w#_GU3Bw&3HpZLPZfHya z>gjik5Fb4S2sY$;Kh!&VWz32LBJ{uMmbnUKb{g4VQ;!`mANO zYV0lL-_yJX2l)a}6ViVAZ(;$t;jWkfWd4(^K$1g-fCI@j>-Un1ZT4D{%a?&we#cd+ zmBj`jyl6Csd?Sz-8x<90W4M@I{OaK|w!CZtr55dFB`^@kktJ*KS5y+ z%GsHZw=^i~^qXx_Pfkv{kdW*SU~iqTl_>ftsZgS7@sV}<^`>ot%cvxzbtUiS#bG;l c9ptpO%=q1vqx3{Qv*} diff --git "a/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@3x.png" "b/Namo_SwiftUI/Projects/Shared/DesignSystem/Resources/Assets.xcassets/NewImage/noPicture.imageset/\352\270\260\353\241\235@3x.png" deleted file mode 100644 index 6965edf04575810eda54a41d70c6f5b4181f0d65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2448 zcmbVN4>;5N8Xw1hNy)#cqdy0g6|ymOMTJm8T1);6HN(M~|H({5r>)URiTnwpv<;JW z$JS)GM#>ditHUhXk~2#zYEH=c?Vfv|r{`Wh&%O73p6~N}|9rpS_xt|d_x-#V1N^=9 zbj)->AdsHV!2>}ckP2A2v@`(C8EjD+@X`M1;E5CvXfsT?R6I-Vk$_SqCCGapsJVZG z6cE&6JPvz+K&{2Pi;?QU0ZX3)9>JL^QzDNbAwo6xwY7xSzqU`?%E|O-!JQ~5xTYd( zOik+MQL+w_lGmOQ=40H4*SV!Lp;qFo(#f$Cjy;+Uk8;*IcRuyJeBJ2rhJCx^@KgCx>?*vnaJFYisM6lHU4tafY~X92SeUeg4k1nqp3PAPZj%Td4K&_V%ty zXJL&e8D`5Gg|%LC=c#Vn+qEc(Jo9vPbhKeyYIgShlZM>3fVTNc<62ZpID3*2($rJy zg%Ntk$H&{$qU1@NmX@bqof5%8kIy4jhPIJe&J25q`utjf*ZN46p*7%5=6UL;_S15I z)H1%T$5FjISj4vjftw?N-?AdrIO9u7O4^bm6A}_ELEy`Aq+w+YAmiYBfr<4xUthHu z;qIuW`?wJB`=5TkX(Xl|R^)&X0cJoZR-^$tF4C^-sxCXcMGK@`KfFz0sdo39*C}KA zZ;j|9RwZl@tp=j3rxj=ng(s0n{vEW|R(l;t|J%23AC8ZYTLbZ%n^H!Y7=7!N7S!z%_&r#EivJqWBH8mxYHj*KDK8MTYT3}vE z_dvS(`~4?GtPByXVvE41xFMlv@bl>G#4~+W;?Y$a`92*3vX6N8Pq%E}@Mol+i#?fp zzBFvFWr8=72rOMqIRf6{m-+p}`@H8<>?AVruHUcj{6Oy*CM+(QdWD`4YyPk&@YEI6 zPkuERHA*>3^QNRRdfDv~gy-1(%bUGV$J<3XeMk`!$|ogTu|*c|FT$Qo9yhj7H}j_5 zi2c+&hIMh0@(zA*yYG-S{m%mXDj73_8=C2C_R?=l3kp^fTxRiF;?vk>vvm*d%VIGo zqf3imduXrw;b7Z5aM}_jIw9q0}VTH=u1GKV>TW=3( z7T%6z25mUOZtlxa?YoE!gbMHZv9cmgw4?}d-!XP>d>EoPGgvVr_cDsiQn|nM)TaJ& z81u#R3J@dyMPqu)OLGk^i}dL)M$yj?&&p*bnYmbm2zsa$-$UiO=2uDglUk-+_H85+ zKkVAtcUMGrP{eqt_ytqY2^~Y|8u7jrf8?`-wcFgZNn`tfX27E9AzM zmX`8n&IX@pG~fC`I7d~Sphk67zgS-?9s2legiL5z3wA5%XZQ~KW(;?ruFaa#L@+a< zKC~k{eQ6FJR>w7YXYipw>Ql#Zb#+}ZF)=CTNmpY&C#~wmO}e4dMBe9a4eDSF6_Bp0 zNK8{~J1^BVkz=%#qL1-q)$mUbiXGCmJJucN?wCT|y0hx#9ENqFPs=zZBaHe4FuLS% zL$Nk+%R%vuJnqN{M9J;uMD6Ny0=PO~T6-D}s^oq^jF2se$9(7q)d(k#9;f=&6(BJ- zlSP2$y%VP{onb_|f)+*Oc{U}w0{fXa)d<6VHt+^Yoe>(ssWYA}Iwnn~qa8Wo3!-Dd z=`TpoIj@SVZ)`3xv@SfI)>`m}D?z9`y`;|i@BiZv!*D4GsRaI5~r{#MFV0nT= zNl(htS?q;-Mir2|S&H{zjLKr6+)8P3mvcWeD{b-O-ZT=`iW|oywhN^5n@QlHVp3()}#Cx`_D8 z@DbZL0D$!z{#gJCE|$EreSMV5YfRqjrU@XIVLk^Lv}-Ply=B1EK4d{LWj66j^ka1# z>79Y&uX6<~liPvl&RJ7R1eZJ%BG2scbyhc<;1W!hfW8yJSSAqz4+p4>g`@R_vB7B= zOshe3+8Xo*qUs8`3aHx4#q;x-j8H^G#I$CvdQd_zYa2f1+Vscox>WBtKEZ0A``3W_ z3P1jbsq*I-SlKtA?K^_W&R(P{-H+-jhe~65pdstpw6-xVKncqG{(VgTAFrs7XBMk= zLR~)jc z3LwqTewN8(XKPWT@=y_CF-#$z9@dAtxVTKxg;Q+-4+Qg0cB`QHXR%H>&U#Su;vO4w zb8}JvNg|Pm>b6xE+~7{owV-D|P9A$8i2KWGC|>9m0@RH*w4ypj!0u&w{W;XTmsMc9 V%j_?EWri!m@$vLO(7X?s_XnbHO!EK$ From da5ae744e9a0d18555d172c7a3b051ee1ee6a3b5 Mon Sep 17 00:00:00 2001 From: FpRaArNkK Date: Thu, 31 Oct 2024 15:48:22 +0900 Subject: [PATCH 03/26] =?UTF-8?q?Chore:=20NamoNavBarModifier=20-=20content?= =?UTF-8?q?=20=EC=9C=84=20=EC=95=84=EB=9E=98=20Spacer=20minLength=200=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/NavBar/NamoNabBarModifier.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/NavBar/NamoNabBarModifier.swift b/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/NavBar/NamoNabBarModifier.swift index c0b08b56..c51bfff2 100644 --- a/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/NavBar/NamoNabBarModifier.swift +++ b/Namo_SwiftUI/Projects/Shared/DesignSystem/Sources/Components/NavBar/NamoNabBarModifier.swift @@ -39,11 +39,11 @@ public struct NamoNavBarModifier: ViewModifier where C: View, L: View, } .frame(height: 52) - Spacer() + Spacer(minLength: 0) content - Spacer() + Spacer(minLength: 0) } .background(.white) .navigationBarHidden(true) From b983308056c36de6990db0b718a6b93e9076c678 Mon Sep 17 00:00:00 2001 From: FpRaArNkK Date: Thu, 31 Oct 2024 15:49:24 +0900 Subject: [PATCH 04/26] =?UTF-8?q?Feat:=20HomeDiaryEditView=20UI=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/RecordEdit/DiaryEditView.swift | 88 ------- .../RecordEdit/HomeDiaryEditView.swift | 241 ++++++++++++++++++ 2 files changed, 241 insertions(+), 88 deletions(-) delete mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift create mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift deleted file mode 100644 index 82cc75d6..00000000 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/DiaryEditView.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// RecordEditView.swift -// FeatureHome -// -// Created by 박민서 on 10/30/24. -// - -import SwiftUI -import SharedDesignSystem -import SharedUtil - -public struct RecordEditView: View { - - let isRevise: Bool = false - let recordName: String = "코딩 스터디" - let date: String = Date().toYMDEHM() - let placeName: - var saveButtonState: NamoButton.NamoButtonType = .inactive - - - public init() {} - - public var body: some View { - VStack(spacing: 0) { - Text("RecordEditView") - // DatePlaceView - // EnjoyRateView - // ContentView - // DiaryImageView - // SaveButton - NamoButton( - title: isRevise ? "변경 내용 저장" : "기록 저장", - font: .pretendard(.bold, size: 15), - cornerRadius: 0, - verticalPadding: 30, - type: saveButtonState, - action: { - switch saveButtonState { - case .active: - print("기록 저장") - default: - break - } - } - ) - } - .namoNabBar(center: { - Text(recordName) - .font(.pretendard(.bold, size: 22)) - }, left: { - Button(action: {}, label: { - Image(asset: SharedDesignSystemAsset.Assets.icArrowLeftThick) - .frame(width: 32, height: 32) - }) - }, right: { - Button(action: {}, label: { - Image(asset: SharedDesignSystemAsset.Assets.icTrashcan) - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) - .disabled(!isRevise) - .hidden(!isRevise) - }) - }) - } -} - -extension RecordEditView { - var datePlaceView: some View { - HStack { - VStack { - datePlaceItem(title: "날짜", content: <#T##String#>) - } - } - } - - func datePlaceItem(title: String, content: String) -> some View { - HStack(spacing: 12) { - Text(title) - .font(.pretendard(.bold, size: 15)) - .foregroundStyle(Color.mainText) - - Text(content) - .font(.pretendard(.regular, size: 15)) - .foregroundStyle(Color.mainText) - } - } -} diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift new file mode 100644 index 00000000..f3525caf --- /dev/null +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -0,0 +1,241 @@ +// +// HomeDiaryEditView.swift +// FeatureHome +// +// Created by 박민서 on 10/30/24. +// + +import SwiftUI +import SharedDesignSystem +import SharedUtil +import _PhotosUI_SwiftUI + +struct TempStore { + let isRevise: Bool = false // 외부 주입 + let recordName: String = "코딩 스터디" // 외부 주입 + let monthString: String = "Oct" + let dayString: String = Date().toDD() + let dateString: String = Date().toYMDEHM() // 외부 주입? + let placeName: String = "강남역" // 외부 주입? + let enjoyRating: Int = 1 + @State var contentString: String = "" + let contentLimit: Int = 200 + @State var selectedImages: [UIImage] = [] + @State var selectedItems: [PhotosPickerItem] = [] + var saveButtonState: NamoButton.NamoButtonType = .inactive +} + +extension TempStore { + var contentCount: Int { contentString.count } +} + +public struct HomeDiaryEditView: View { + + var store = TempStore() + + public init() {} + + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + + DatePlaceView(store: store) + .padding(.horizontal, 30) + .padding(.bottom, 24) + + EnjoyRateView(store: store) + .padding(.horizontal, 45) + .padding(.bottom, 16) + + ContentView(store: store) + .padding(.horizontal, 30) + .padding(.bottom, 20) + + DiaryImageView(store: store) + .padding(.horizontal, 30) + + Spacer() + + NamoButton( + title: store.isRevise ? "변경 내용 저장" : "기록 저장", + font: .pretendard(.bold, size: 15), + cornerRadius: 0, + verticalPadding: 30, + type: store.saveButtonState, + action: { + switch store.saveButtonState { + case .active: + print("기록 저장") + default: + break + } + } + ) + } + .namoNabBar(center: { + Text(store.recordName) + .font(.pretendard(.bold, size: 22)) + }, left: { + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icArrowLeftThick) + .frame(width: 32, height: 32) + }) + }, right: { + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icTrashcan) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .disabled(!store.isRevise) + .hidden(!store.isRevise) + }) + }) + .ignoresSafeArea(.container, edges: .bottom) + } +} + +// MARK: DatePlaceView +private extension HomeDiaryEditView { + func DatePlaceView(store: TempStore) -> some View { + HStack(spacing: 25) { + DateCircleView(monthString: store.monthString, dayString: store.dayString) + + VStack(alignment: .leading, spacing: 12) { + DatePlaceItem(title: "날짜", content: store.dateString) + DatePlaceItem(title: "장소", content: store.placeName) + } + } + } + + func DateCircleView(monthString: String, dayString: String) -> some View { + VStack { + Text(monthString) + .font(.pretendard(.bold, size: 15)) + .foregroundStyle(Color.mainText) + + Text(dayString) + .font(.pretendard(.bold, size: 36)) + .foregroundStyle(Color.mainText) + } + .padding(20) + .background { + Circle() + .foregroundColor(.white) + .shadow(color: .black.opacity(0.15), radius: 8) + } + } + + func DatePlaceItem(title: String, content: String) -> some View { + HStack(spacing: 12) { + Text(title) + .font(.pretendard(.bold, size: 15)) + .foregroundStyle(Color.mainText) + + Text(content) + .font(.pretendard(.regular, size: 15)) + .foregroundStyle(Color.mainText) + } + } +} + +// MARK: EnojoyRateView +private extension HomeDiaryEditView { + func EnjoyRateView(store: TempStore) -> some View { + HStack { + Text("재미도") + .font(.pretendard(.bold, size: 15)) + .foregroundStyle(Color.mainText) + Spacer() + EnjoyCountView(rate: store.enjoyRating) + } + } + + func EnjoyCountView(rate: Int) -> some View { + HStack(spacing: 4) { + ForEach(0..<3) { index in + let isFilled = index < rate + Image(asset: isFilled ? SharedDesignSystemAsset.Assets.icHeartSelected : SharedDesignSystemAsset.Assets.icHeart) + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) + } + } + } +} + +// MARK: ContentView +private extension HomeDiaryEditView { + + func ContentView(store: TempStore) -> some View { + VStack(spacing: 10) { + ContentInputView(store: store) + ContentFooterView(count: store.contentCount, maxCount: store.contentLimit) + } + } + + func ContentInputView(store: TempStore) -> some View { + HStack(spacing: 0) { + // 좌측 고정 빨간 박스 + Rectangle() + .fill(Color.namoPink) + .frame(width: 10) + // 다중 줄 텍스트 입력 + TextEditor(text: store.$contentString) + .font(.pretendard(.regular, size: 14)) + .foregroundStyle(Color.mainText) + .backgroundStyle(Color.itemBackground) + .padding(.vertical, 12) + .padding(.horizontal, 16) + .overlay( + Text("내용 입력") + .font(.pretendard(.bold, size: 14)) + .foregroundStyle(Color.textUnselected) + .opacity(store.contentString.isEmpty ? 1 : 0) + .padding(.vertical, 16) + .padding(.horizontal, 16), + alignment: .topLeading + ) + } + .frame(height: 150) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } + + func ContentFooterView(count: Int, maxCount: Int) -> some View { + HStack { + Spacer() + Text("\(count) / \(maxCount)") + .font(.pretendard(.bold, size: 12)) + .foregroundStyle(Color.textUnselected) + } + } +} + +// MARK: DiaryImageView +private extension HomeDiaryEditView { + + func DiaryImageView(store: TempStore) -> some View { + PhotosPicker(selection: store.$selectedItems, maxSelectionCount: 3) { + ForEach(Array(store.selectedImages.enumerated()), id: \.self.element) { (offset, image) in + Image(uiImage: image) + .resizable() + .frame(width: 92, height: 92) + .overlay(alignment: .topTrailing) { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(.white) + .overlay { + Image(asset: SharedDesignSystemAsset.Assets.icXmark) + } + .offset(x: 10, y: -10) + .onTapGesture { + store.selectedImages.remove(at: offset) + } + } + } + if store.selectedImages.count < 3 { + Image(asset: SharedDesignSystemAsset.Assets.noPicture) + .resizable() + .frame(width: 92, height: 92) + } + } + } +} From 6d8a805c19943823e8579e26acd1583d811de911 Mon Sep 17 00:00:00 2001 From: FpRaArNkK Date: Thu, 31 Oct 2024 16:04:25 +0900 Subject: [PATCH 05/26] =?UTF-8?q?Feat:=20HomeDiaryEditStore=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift new file mode 100644 index 00000000..a4ade9ae --- /dev/null +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -0,0 +1,32 @@ +// +// HomeDiaryEditStore.swift +// FeatureHome +// +// Created by 박민서 on 10/31/24. +// + +import ComposableArchitecture + +@Reducer +public struct HomeDiaryEditStore { + public init() {} + + @ObservableState + public struct State: Equatable { + public init() {} + } + + public enum Action { + + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + + } + } + } +} + + From 6d1c438d0f5d01310eecacc624104c91a143775b Mon Sep 17 00:00:00 2001 From: FpRaArNkK Date: Fri, 8 Nov 2024 18:30:19 +0900 Subject: [PATCH 06/26] =?UTF-8?q?Feat:=20HomeDiaryEditStore=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EA=B5=AC=ED=98=84,=20=EB=B7=B0=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 87 +++++++++++++++++- .../RecordEdit/HomeDiaryEditView.swift | 90 +++++++++---------- 2 files changed, 129 insertions(+), 48 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index a4ade9ae..ea27f768 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -7,23 +7,106 @@ import ComposableArchitecture +import DomainSchedule +import _PhotosUI_SwiftUI +import SharedDesignSystem + @Reducer public struct HomeDiaryEditStore { public init() {} + public static let contentLimit: Int = 200 @ObservableState public struct State: Equatable { - public init() {} + public init(schedule: Schedule) { + // get API 호출 + let apiResult_isSuccess = true // 존재하면 true, 없으면 false + let apiResult_enjoyRating = 3 + let apiResult_contentString = "Hello, world!" + let apiResult_imageURLs: [String] = [] + let apiResult_selectedItems: [PhotosPickerItem] = [] + let apiResult_selectedImages: [Data] = [] + let saveButtonState: NamoButton.NamoButtonType = .inactive + + self.isRevise = apiResult_isSuccess + self.scheduleName = schedule.title + self.monthString = schedule.startDate.toMM() + self.dayString = schedule.startDate.toDD() + self.dateString = schedule.startDate.toYMDEHM() + self.placeName = schedule.locationInfo?.locationName ?? "" + self.enjoyRating = apiResult_enjoyRating + self.contentString = apiResult_contentString + self.selectedItems = apiResult_selectedItems + self.selectedImages = apiResult_selectedImages + self.saveButtonState = saveButtonState + } + + let isRevise: Bool + let scheduleName: String + let monthString: String + let dayString: String + let dateString: String + let placeName: String + var enjoyRating: Int + var contentString: String + var selectedItems: [PhotosPickerItem] + var selectedImages: [Data] + var saveButtonState: NamoButton.NamoButtonType } public enum Action { - + case tapEnjoyRating(Int) + case typeContent(String) + case selectPhoto(PhotosPickerItem) + case addImage(Result) + case deleteImage(Int) + case tapDeleteDiaryButton + case tapSaveDiaryButton } public var body: some ReducerOf { Reduce { state, action in switch action { + case .tapEnjoyRating(let rate): + state.enjoyRating = rate + return .none + + case .typeContent(let content): + state.contentString = content + return .none + + case .selectPhoto(let pickerItem): + return .run { send in + await send(.addImage(Result { + try await pickerItem.loadTransferable(type: Data.self) + })) + } + + case .addImage(.success(let data)): + guard let data else { + print("data is nil") + return .none + } + state.selectedImages.append(data) + return .none + + case .addImage(.failure(let error)): + print("error occured: \(error.localizedDescription)") + return .none + + case .deleteImage(let index): + state.selectedImages.remove(at: index) + return .none + + case .tapDeleteDiaryButton: + print("Delete Diary") + return .none + + case .tapSaveDiaryButton: + print("Save Diary") + return .none + } } } diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index f3525caf..0b6f4695 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -9,31 +9,15 @@ import SwiftUI import SharedDesignSystem import SharedUtil import _PhotosUI_SwiftUI - -struct TempStore { - let isRevise: Bool = false // 외부 주입 - let recordName: String = "코딩 스터디" // 외부 주입 - let monthString: String = "Oct" - let dayString: String = Date().toDD() - let dateString: String = Date().toYMDEHM() // 외부 주입? - let placeName: String = "강남역" // 외부 주입? - let enjoyRating: Int = 1 - @State var contentString: String = "" - let contentLimit: Int = 200 - @State var selectedImages: [UIImage] = [] - @State var selectedItems: [PhotosPickerItem] = [] - var saveButtonState: NamoButton.NamoButtonType = .inactive -} - -extension TempStore { - var contentCount: Int { contentString.count } -} +import ComposableArchitecture public struct HomeDiaryEditView: View { - var store = TempStore() + var store: StoreOf - public init() {} + public init(store: StoreOf) { + self.store = store + } public var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -72,7 +56,7 @@ public struct HomeDiaryEditView: View { ) } .namoNabBar(center: { - Text(store.recordName) + Text(store.scheduleName) .font(.pretendard(.bold, size: 22)) }, left: { Button(action: {}, label: { @@ -95,7 +79,7 @@ public struct HomeDiaryEditView: View { // MARK: DatePlaceView private extension HomeDiaryEditView { - func DatePlaceView(store: TempStore) -> some View { + func DatePlaceView(store: StoreOf) -> some View { HStack(spacing: 25) { DateCircleView(monthString: store.monthString, dayString: store.dayString) @@ -139,7 +123,7 @@ private extension HomeDiaryEditView { // MARK: EnojoyRateView private extension HomeDiaryEditView { - func EnjoyRateView(store: TempStore) -> some View { + func EnjoyRateView(store: StoreOf) -> some View { HStack { Text("재미도") .font(.pretendard(.bold, size: 15)) @@ -165,21 +149,21 @@ private extension HomeDiaryEditView { // MARK: ContentView private extension HomeDiaryEditView { - func ContentView(store: TempStore) -> some View { + func ContentView(store: StoreOf) -> some View { VStack(spacing: 10) { ContentInputView(store: store) - ContentFooterView(count: store.contentCount, maxCount: store.contentLimit) + ContentFooterView(count: store.contentString.count, maxCount: HomeDiaryEditStore.contentLimit) } } - func ContentInputView(store: TempStore) -> some View { + func ContentInputView(store: StoreOf) -> some View { HStack(spacing: 0) { // 좌측 고정 빨간 박스 Rectangle() .fill(Color.namoPink) .frame(width: 10) // 다중 줄 텍스트 입력 - TextEditor(text: store.$contentString) + TextEditor(text: Binding(get: { store.contentString }, set: { store.send(.typeContent($0)) })) .font(.pretendard(.regular, size: 14)) .foregroundStyle(Color.mainText) .backgroundStyle(Color.itemBackground) @@ -212,25 +196,21 @@ private extension HomeDiaryEditView { // MARK: DiaryImageView private extension HomeDiaryEditView { - func DiaryImageView(store: TempStore) -> some View { - PhotosPicker(selection: store.$selectedItems, maxSelectionCount: 3) { - ForEach(Array(store.selectedImages.enumerated()), id: \.self.element) { (offset, image) in - Image(uiImage: image) - .resizable() - .frame(width: 92, height: 92) - .overlay(alignment: .topTrailing) { - Circle() - .frame(width: 20, height: 20) - .foregroundStyle(.white) - .overlay { - Image(asset: SharedDesignSystemAsset.Assets.icXmark) - } - .offset(x: 10, y: -10) - .onTapGesture { - store.selectedImages.remove(at: offset) - } - } + func DiaryImageView(store: StoreOf) -> some View { + + PhotosPicker(selection: Binding(get: { store.selectedItems }, set: { newItems in + if let newItem = newItems.last, newItems.count > store.selectedItems.count { + store.send(.selectPhoto(newItem)) } + }), maxSelectionCount: 3) { + ForEach(Array(store.selectedImages.enumerated()), id: \.self.element) { (offset, imageData) in + DiaryImageListItemView( + image: UIImage(data: imageData) ?? UIImage(), + index: offset, + store: store + ) + } + if store.selectedImages.count < 3 { Image(asset: SharedDesignSystemAsset.Assets.noPicture) .resizable() @@ -238,4 +218,22 @@ private extension HomeDiaryEditView { } } } + + func DiaryImageListItemView(image: UIImage, index: Int, store: StoreOf) -> some View { + Image(uiImage: image) + .resizable() + .frame(width: 92, height: 92) + .overlay(alignment: .topTrailing) { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(.white) + .overlay { + Image(asset: SharedDesignSystemAsset.Assets.icXmark) + } + .offset(x: 10, y: -10) + .onTapGesture { + store.send(.deleteImage(index)) + } + } + } } From f5cd497edf98c675061f1cacccf37f456ae1bd27 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:13:34 +0900 Subject: [PATCH 07/26] =?UTF-8?q?Feat:=20HomeDiaryEdit=20=EA=B8=B0?= =?UTF-8?q?=ED=9A=8D=20=EC=88=98=EC=A0=95=20=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 2 +- .../RecordEdit/HomeDiaryEditView.swift | 123 +++++++++--------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index ea27f768..e7c66e67 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -32,7 +32,7 @@ public struct HomeDiaryEditStore { self.scheduleName = schedule.title self.monthString = schedule.startDate.toMM() self.dayString = schedule.startDate.toDD() - self.dateString = schedule.startDate.toYMDEHM() + self.dateString = "\(schedule.startDate.toYMDEHM()) \n - \(schedule.endDate.toYMDEHM())" self.placeName = schedule.locationInfo?.locationName ?? "" self.enjoyRating = apiResult_enjoyRating self.contentString = apiResult_contentString diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index 0b6f4695..c8d0375d 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -20,60 +20,62 @@ public struct HomeDiaryEditView: View { } public var body: some View { - VStack(alignment: .leading, spacing: 0) { - - DatePlaceView(store: store) - .padding(.horizontal, 30) - .padding(.bottom, 24) - - EnjoyRateView(store: store) - .padding(.horizontal, 45) - .padding(.bottom, 16) - - ContentView(store: store) - .padding(.horizontal, 30) - .padding(.bottom, 20) - - DiaryImageView(store: store) - .padding(.horizontal, 30) - - Spacer() - - NamoButton( - title: store.isRevise ? "변경 내용 저장" : "기록 저장", - font: .pretendard(.bold, size: 15), - cornerRadius: 0, - verticalPadding: 30, - type: store.saveButtonState, - action: { - switch store.saveButtonState { - case .active: - print("기록 저장") - default: - break + WithPerceptionTracking { + VStack(alignment: .leading, spacing: 0) { + + DatePlaceView(store: store) + .padding(.horizontal, 30) + .padding(.bottom, 24) + + EnjoyRateView(store: store) + .padding(.horizontal, 45) + .padding(.bottom, 16) + + ContentView(store: store) + .padding(.horizontal, 30) + .padding(.bottom, 20) + + DiaryImageView(store: store) + .padding(.horizontal, 30) + + Spacer() + + NamoButton( + title: store.isRevise ? "변경 내용 저장" : "기록 저장", + font: .pretendard(.bold, size: 15), + cornerRadius: 0, + verticalPadding: 30, + type: store.saveButtonState, + action: { + switch store.saveButtonState { + case .active: + print("기록 저장") + default: + break + } } - } - ) - } - .namoNabBar(center: { - Text(store.scheduleName) - .font(.pretendard(.bold, size: 22)) - }, left: { - Button(action: {}, label: { - Image(asset: SharedDesignSystemAsset.Assets.icArrowLeftThick) - .frame(width: 32, height: 32) - }) - }, right: { - Button(action: {}, label: { - Image(asset: SharedDesignSystemAsset.Assets.icTrashcan) - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) - .disabled(!store.isRevise) - .hidden(!store.isRevise) + ) + } + .namoNabBar(center: { + Text(store.scheduleName) + .font(.pretendard(.bold, size: 22)) + }, left: { + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icArrowLeftThick) + .frame(width: 32, height: 32) + }) + }, right: { + Button(action: {}, label: { + Image(asset: SharedDesignSystemAsset.Assets.icTrashcan) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .disabled(!store.isRevise) + .hidden(!store.isRevise) + }) }) - }) - .ignoresSafeArea(.container, edges: .bottom) + .ignoresSafeArea(.container, edges: .bottom) + } } } @@ -109,7 +111,7 @@ private extension HomeDiaryEditView { } func DatePlaceItem(title: String, content: String) -> some View { - HStack(spacing: 12) { + HStack(alignment: .top, spacing: 12) { Text(title) .font(.pretendard(.bold, size: 15)) .foregroundStyle(Color.mainText) @@ -129,18 +131,21 @@ private extension HomeDiaryEditView { .font(.pretendard(.bold, size: 15)) .foregroundStyle(Color.mainText) Spacer() - EnjoyCountView(rate: store.enjoyRating) + EnjoyCountView(store: store) } } - func EnjoyCountView(rate: Int) -> some View { + func EnjoyCountView(store: StoreOf) -> some View { HStack(spacing: 4) { ForEach(0..<3) { index in - let isFilled = index < rate + let isFilled = index < store.enjoyRating Image(asset: isFilled ? SharedDesignSystemAsset.Assets.icHeartSelected : SharedDesignSystemAsset.Assets.icHeart) .resizable() .scaledToFit() .frame(width: 18, height: 18) + .onTapGesture { + store.send(.tapEnjoyRating(index + 1), animation: .default) + } } } } @@ -166,7 +171,7 @@ private extension HomeDiaryEditView { TextEditor(text: Binding(get: { store.contentString }, set: { store.send(.typeContent($0)) })) .font(.pretendard(.regular, size: 14)) .foregroundStyle(Color.mainText) - .backgroundStyle(Color.itemBackground) + .scrollContentBackground(.hidden) .padding(.vertical, 12) .padding(.horizontal, 16) .overlay( @@ -180,6 +185,7 @@ private extension HomeDiaryEditView { ) } .frame(height: 150) + .background(Color.itemBackground) .clipShape(RoundedRectangle(cornerRadius: 10)) } @@ -222,6 +228,7 @@ private extension HomeDiaryEditView { func DiaryImageListItemView(image: UIImage, index: Int, store: StoreOf) -> some View { Image(uiImage: image) .resizable() + .scaledToFit() .frame(width: 92, height: 92) .overlay(alignment: .topTrailing) { Circle() @@ -232,7 +239,7 @@ private extension HomeDiaryEditView { } .offset(x: 10, y: -10) .onTapGesture { - store.send(.deleteImage(index)) + store.send(.deleteImage(index), animation: .default) } } } From 0f79d54967ee2dabfbb0bafa1dd0dc2448617c72 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:29:16 +0900 Subject: [PATCH 08/26] =?UTF-8?q?Feat:=20Toast=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Sources/RecordEdit/HomeDiaryEditStore.swift | 9 ++++++++- .../Home/Sources/RecordEdit/HomeDiaryEditView.swift | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index e7c66e67..485757d9 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -52,9 +52,11 @@ public struct HomeDiaryEditStore { var selectedItems: [PhotosPickerItem] var selectedImages: [Data] var saveButtonState: NamoButton.NamoButtonType + var showToast: Bool = false } - public enum Action { + public enum Action: BindableAction { + case binding(BindingAction) case tapEnjoyRating(Int) case typeContent(String) case selectPhoto(PhotosPickerItem) @@ -65,9 +67,14 @@ public struct HomeDiaryEditStore { } public var body: some ReducerOf { + BindingReducer() + Reduce { state, action in switch action { + case .binding: + return .none + case .tapEnjoyRating(let rate): state.enjoyRating = rate return .none diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index c8d0375d..e6ff78ea 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -13,7 +13,7 @@ import ComposableArchitecture public struct HomeDiaryEditView: View { - var store: StoreOf + @Perception.Bindable var store: StoreOf public init(store: StoreOf) { self.store = store @@ -75,6 +75,11 @@ public struct HomeDiaryEditView: View { }) }) .ignoresSafeArea(.container, edges: .bottom) + .namoToastView( + isPresented: $store.showToast, + title: "기록이 저장되었습니다.", + isTabBarScreen: false + ) } } } From 20bd0e029bbc3a5ed4ab925d28def901815b77e3 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:36:19 +0900 Subject: [PATCH 09/26] =?UTF-8?q?Feat:=20NamoAlertContent=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Shared/Util/Sources/Model/NamoAlertContent.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift diff --git a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift new file mode 100644 index 00000000..21ce21f6 --- /dev/null +++ b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift @@ -0,0 +1,11 @@ +// +// NamoAlertContent.swift +// SharedUtil +// +// Created by 박민서 on 11/13/24. +// + +struct NamoAlertContent { + var title: String + var message: String +} From 03d6e1d1a5a41afb6f707c867e0eb08ecf081610 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Thu, 14 Nov 2024 03:18:01 +0900 Subject: [PATCH 10/26] =?UTF-8?q?Feat:=20Alert=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 10 ++++++- .../RecordEdit/HomeDiaryEditView.swift | 14 ++++++++-- .../Util/Sources/Model/NamoAlertContent.swift | 28 +++++++++++++++++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 485757d9..e5880cb5 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -10,6 +10,7 @@ import ComposableArchitecture import DomainSchedule import _PhotosUI_SwiftUI import SharedDesignSystem +import SharedUtil @Reducer public struct HomeDiaryEditStore { @@ -53,6 +54,8 @@ public struct HomeDiaryEditStore { var selectedImages: [Data] var saveButtonState: NamoButton.NamoButtonType var showToast: Bool = false + var alertContent: NamoAlertType = .custom(.init()) + var showAlert: Bool = false } public enum Action: BindableAction { @@ -62,6 +65,7 @@ public struct HomeDiaryEditStore { case selectPhoto(PhotosPickerItem) case addImage(Result) case deleteImage(Int) + case tapBackButton case tapDeleteDiaryButton case tapSaveDiaryButton } @@ -106,8 +110,12 @@ public struct HomeDiaryEditStore { state.selectedImages.remove(at: index) return .none + case .tapBackButton: + return .none + case .tapDeleteDiaryButton: - print("Delete Diary") + state.alertContent = .deleteDiary + state.showAlert = true return .none case .tapSaveDiaryButton: diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index e6ff78ea..90e2db83 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -60,12 +60,16 @@ public struct HomeDiaryEditView: View { Text(store.scheduleName) .font(.pretendard(.bold, size: 22)) }, left: { - Button(action: {}, label: { + Button(action: { + store.send(.tapBackButton, animation: .default) + }, label: { Image(asset: SharedDesignSystemAsset.Assets.icArrowLeftThick) .frame(width: 32, height: 32) }) }, right: { - Button(action: {}, label: { + Button(action: { + store.send(.tapDeleteDiaryButton, animation: .default) + }, label: { Image(asset: SharedDesignSystemAsset.Assets.icTrashcan) .resizable() .scaledToFit() @@ -80,6 +84,12 @@ public struct HomeDiaryEditView: View { title: "기록이 저장되었습니다.", isTabBarScreen: false ) + .namoAlertView( + isPresented: $store.showAlert, + title: store.alertContent.content.title, + content: store.alertContent.content.message, + confirmAction: { } + ) } } } diff --git a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift index 21ce21f6..4f8828c8 100644 --- a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift +++ b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift @@ -5,7 +5,29 @@ // Created by 박민서 on 11/13/24. // -struct NamoAlertContent { - var title: String - var message: String +public struct NamoAlertContent: Equatable { + public var title: String + public var message: String + + public init(title: String = "", message: String = "") { + self.title = title + self.message = message + } +} + +public enum NamoAlertType: Equatable { + case deleteDiary + case backWithoutSave + case custom(NamoAlertContent) + + public var content: NamoAlertContent { + switch self { + case .deleteDiary: + return NamoAlertContent(title: "기록을 정말 삭제하시겠어요?") + case .backWithoutSave: + return NamoAlertContent(title: "편집된 내용이 저장되지 않습니다.", message: "정말 나가시겠어요?") + case .custom(let content): + return NamoAlertContent(title: content.title, message: content.title) + } + } } From 85377edbbe06c2c5c920ed14aa45631f6751ff92 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Thu, 14 Nov 2024 03:48:12 +0900 Subject: [PATCH 11/26] =?UTF-8?q?Feat:=20Content=20Validating=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Sources/RecordEdit/HomeDiaryEditStore.swift | 6 ++++++ .../Home/Sources/RecordEdit/HomeDiaryEditView.swift | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index e5880cb5..e203016d 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -50,6 +50,7 @@ public struct HomeDiaryEditStore { let placeName: String var enjoyRating: Int var contentString: String + var isContentValid: Bool = true var selectedItems: [PhotosPickerItem] var selectedImages: [Data] var saveButtonState: NamoButton.NamoButtonType @@ -62,6 +63,7 @@ public struct HomeDiaryEditStore { case binding(BindingAction) case tapEnjoyRating(Int) case typeContent(String) + case validateContent(String) case selectPhoto(PhotosPickerItem) case addImage(Result) case deleteImage(Int) @@ -85,6 +87,10 @@ public struct HomeDiaryEditStore { case .typeContent(let content): state.contentString = content + return .send(.validateContent(content)) + + case .validateContent(let content): + state.isContentValid = content.count <= Self.contentLimit return .none case .selectPhoto(let pickerItem): diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index 90e2db83..2e6c1a76 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -172,7 +172,11 @@ private extension HomeDiaryEditView { func ContentView(store: StoreOf) -> some View { VStack(spacing: 10) { ContentInputView(store: store) - ContentFooterView(count: store.contentString.count, maxCount: HomeDiaryEditStore.contentLimit) + ContentFooterView( + count: store.contentString.count, + maxCount: HomeDiaryEditStore.contentLimit, + isValid: store.isContentValid + ) } } @@ -204,12 +208,12 @@ private extension HomeDiaryEditView { .clipShape(RoundedRectangle(cornerRadius: 10)) } - func ContentFooterView(count: Int, maxCount: Int) -> some View { + func ContentFooterView(count: Int, maxCount: Int, isValid: Bool) -> some View { HStack { Spacer() Text("\(count) / \(maxCount)") .font(.pretendard(.bold, size: 12)) - .foregroundStyle(Color.textUnselected) + .foregroundStyle(isValid ? Color.textUnselected : Color.mainOrange) } } } From e06a19b27f01ba51a2a76ae7e6bed1064d384678 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Thu, 14 Nov 2024 04:12:02 +0900 Subject: [PATCH 12/26] =?UTF-8?q?Feat:=20Alert,=20Action=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 35 ++++++++++++++++--- .../RecordEdit/HomeDiaryEditView.swift | 11 +++--- .../Util/Sources/Model/NamoAlertContent.swift | 3 ++ 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index e203016d..5cf96b4c 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -27,7 +27,6 @@ public struct HomeDiaryEditStore { let apiResult_imageURLs: [String] = [] let apiResult_selectedItems: [PhotosPickerItem] = [] let apiResult_selectedImages: [Data] = [] - let saveButtonState: NamoButton.NamoButtonType = .inactive self.isRevise = apiResult_isSuccess self.scheduleName = schedule.title @@ -39,7 +38,6 @@ public struct HomeDiaryEditStore { self.contentString = apiResult_contentString self.selectedItems = apiResult_selectedItems self.selectedImages = apiResult_selectedImages - self.saveButtonState = saveButtonState } let isRevise: Bool @@ -53,9 +51,9 @@ public struct HomeDiaryEditStore { var isContentValid: Bool = true var selectedItems: [PhotosPickerItem] var selectedImages: [Data] - var saveButtonState: NamoButton.NamoButtonType + var saveButtonState: NamoButton.NamoButtonType = .inactive var showToast: Bool = false - var alertContent: NamoAlertType = .custom(.init()) + var alertContent: NamoAlertType = .none var showAlert: Bool = false } @@ -70,6 +68,8 @@ public struct HomeDiaryEditStore { case tapBackButton case tapDeleteDiaryButton case tapSaveDiaryButton + case handleAlertConfirm + case dismiss } public var body: some ReducerOf { @@ -117,6 +117,8 @@ public struct HomeDiaryEditStore { return .none case .tapBackButton: + state.alertContent = .backWithoutSave + state.showAlert = true return .none case .tapDeleteDiaryButton: @@ -125,7 +127,30 @@ public struct HomeDiaryEditStore { return .none case .tapSaveDiaryButton: - print("Save Diary") + switch state.saveButtonState { + + case .active: + print("기록 저장") + return .none + default: + return .none + } + + case .handleAlertConfirm: + state.alertContent = .none // alert 초기화 + switch state.alertContent { + + case .deleteDiary: + print("삭제 api 호출") + return .none + case .backWithoutSave: + return .send(.dismiss) + default: + return .none + } + + case .dismiss: + print("dismiss") return .none } diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index 2e6c1a76..34a3e2d9 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -47,12 +47,7 @@ public struct HomeDiaryEditView: View { verticalPadding: 30, type: store.saveButtonState, action: { - switch store.saveButtonState { - case .active: - print("기록 저장") - default: - break - } + store.send(.tapSaveDiaryButton) } ) } @@ -88,7 +83,9 @@ public struct HomeDiaryEditView: View { isPresented: $store.showAlert, title: store.alertContent.content.title, content: store.alertContent.content.message, - confirmAction: { } + confirmAction: { + store.send(.handleAlertConfirm) + } ) } } diff --git a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift index 4f8828c8..78bab750 100644 --- a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift +++ b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift @@ -19,9 +19,12 @@ public enum NamoAlertType: Equatable { case deleteDiary case backWithoutSave case custom(NamoAlertContent) + case none public var content: NamoAlertContent { switch self { + case .none: + return NamoAlertContent() case .deleteDiary: return NamoAlertContent(title: "기록을 정말 삭제하시겠어요?") case .backWithoutSave: From 56c3c206259e7437697989062c89223eddaa6e88 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:09:11 +0900 Subject: [PATCH 13/26] =?UTF-8?q?Feat:=20Diary=20DTO,=20Repo=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/Diary/DiaryRepository.swift | 10 +- .../Core/Network/Sources/DTO/DiaryDTO.swift | 135 ++++++++++-------- 2 files changed, 80 insertions(+), 65 deletions(-) diff --git a/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/DiaryRepository.swift b/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/DiaryRepository.swift index f051ab19..707f0de7 100644 --- a/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/DiaryRepository.swift +++ b/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/DiaryRepository.swift @@ -10,9 +10,9 @@ import Foundation import CoreNetwork protocol DiaryRepository { - func createDiary(scheduleId: Int, content: String, images: [Data?]) async -> CreateDiaryResponseDTO? - func getMonthDiary(request: GetDiaryRequestDTO) async -> GetDiaryResponseDTO? - func getOneDiary(scheduleId: Int) async -> GetOneDiaryResponseDTO? - func changeDiary(scheduleId: Int, content: String, images: [Data?], deleteImageIds: [Int]) async -> Bool - func deleteDiary(scheduleId: Int) async -> Bool +// func createDiary(scheduleId: Int, content: String, images: [Data?]) async -> CreateDiaryResponseDTO? +// func getMonthDiary(request: GetDiaryRequestDTO) async -> GetDiaryResponseDTO? +// func getOneDiary(scheduleId: Int) async -> GetOneDiaryResponseDTO? +// func changeDiary(scheduleId: Int, content: String, images: [Data?], deleteImageIds: [Int]) async -> Bool +// func deleteDiary(scheduleId: Int) async -> Bool } diff --git a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift index aa7c2a01..6295e706 100644 --- a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift +++ b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift @@ -3,74 +3,89 @@ // Namo_SwiftUI // // Created by 서은수 on 3/16/24. +// Updated by 박민서 on 11/13/24. // import Foundation -public struct Diary: Decodable { - public var scheduleId: Int - public var name: String - public var startDate: Int - public var contents: String? - public var images: [ImageResponse]? - public var categoryId: Int - public var color: Int - public var placeName: String - - public init(scheduleId: Int? = nil, name: String? = nil, startDate: Int? = nil, contents: String? = nil, images: [ImageResponse]? = nil, categoryId: Int? = nil, color: Int? = nil, placeName: String? = nil) { - self.scheduleId = scheduleId ?? -1 - self.name = name ?? "" - self.categoryId = categoryId ?? -1 - self.startDate = startDate ?? 0 - self.contents = contents ?? "" - self.images = images ?? [] - self.categoryId = categoryId ?? -1 - self.color = color ?? -1 - self.placeName = placeName ?? "" - } +public struct DiaryResponseDTO: Decodable { + public let diaryId: Int + public let content: String + public let enjoyRating: Int + public let diaryImages: [DiaryImageResponseDTO] } -public struct GetDiaryRequestDTO: Encodable { - public init(year: Int, month: Int, page: Int, size: Int) { - self.year = year - self.month = month - self.page = page - self.size = size - } - - public var year: Int - public var month: Int - public var page: Int - public var size: Int -} - -public struct GetDiaryResponseDTO: Decodable { - public init(content: [Diary], currentPage: Int, size: Int, first: Bool, last: Bool) { - self.content = content - self.currentPage = currentPage - self.size = size - self.first = first - self.last = last - } - - public var content: [Diary] - public var currentPage: Int - public var size: Int - public var first: Bool - public var last: Bool -} - -/// 개별 기록 조회 API 응답 -public struct GetOneDiaryResponseDTO: Decodable { - public init(contents: String? = nil, images: [ImageResponse]? = nil) { - self.contents = contents - self.images = images - } - - public var contents: String? - public var images: [ImageResponse]? +public struct DiaryImageResponseDTO: Decodable { + public let orderNumber: Int + public let diaryImageId: Int + public let imageUrl: String } +// +//public struct Diary: Decodable { +// public var scheduleId: Int +// public var name: String +// public var startDate: Int +// public var contents: String? +// public var images: [ImageResponse]? +// public var categoryId: Int +// public var color: Int +// public var placeName: String +// +// public init(scheduleId: Int? = nil, name: String? = nil, startDate: Int? = nil, contents: String? = nil, images: [ImageResponse]? = nil, categoryId: Int? = nil, color: Int? = nil, placeName: String? = nil) { +// self.scheduleId = scheduleId ?? -1 +// self.name = name ?? "" +// self.categoryId = categoryId ?? -1 +// self.startDate = startDate ?? 0 +// self.contents = contents ?? "" +// self.images = images ?? [] +// self.categoryId = categoryId ?? -1 +// self.color = color ?? -1 +// self.placeName = placeName ?? "" +// } +//} +// +//public struct GetDiaryRequestDTO: Encodable { +// public init(year: Int, month: Int, page: Int, size: Int) { +// self.year = year +// self.month = month +// self.page = page +// self.size = size +// } +// +// public var year: Int +// public var month: Int +// public var page: Int +// public var size: Int +//} +// +//public struct GetDiaryResponseDTO: Decodable { +// public init(content: [Diary], currentPage: Int, size: Int, first: Bool, last: Bool) { +// self.content = content +// self.currentPage = currentPage +// self.size = size +// self.first = first +// self.last = last +// } +// +// public var content: [Diary] +// public var currentPage: Int +// public var size: Int +// public var first: Bool +// public var last: Bool +//} +// +///// 개별 기록 조회 API 응답 +//public struct GetOneDiaryResponseDTO: Decodable { +// public init(contents: String? = nil, images: [ImageResponse]? = nil) { +// self.contents = contents +// self.images = images +// } +// +// public var contents: String? +// public var images: [ImageResponse]? +//} +// public struct CreateDiaryResponseDTO: Codable { public init(scheduleId: Int) { self.scheduleId = scheduleId From 08234fd415dae7db2271d893b4082163cdd7c177 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:40:03 +0900 Subject: [PATCH 14/26] =?UTF-8?q?Feat:=20Diary=20Domain=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Diary/Sources/Mapper/DiaryMapper.swift | 26 +++++++++++++++++++ .../Domain/Diary/Sources/Model/Diary.swift | 18 +++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift index 59f10da8..51a1a244 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift @@ -55,3 +55,29 @@ extension DiaryScheduleParticipantDTO { ) } } + +extension DiaryResponseDTO { + func toEntity() -> Diary { + return Diary( + id: diaryId, + content: content, + enjoyRating: enjoyRating, + images: mapAndSortDiaryImages(from: diaryImages) + ) + } + + func mapAndSortDiaryImages(from responseDTOs: [DiaryImageResponseDTO]) -> [DiaryImage] { + return responseDTOs + .sorted(by: { $0.orderNumber < $1.orderNumber }) + .map { DiaryImage(id: $0.diaryImageId, imageUrl: $0.imageUrl) } + } +} + +extension DiaryImageResponseDTO { + func toEntity() -> DiaryImage { + return DiaryImage( + id: diaryImageId, + imageUrl: imageUrl + ) + } +} diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift new file mode 100644 index 00000000..4c25918d --- /dev/null +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift @@ -0,0 +1,18 @@ +// +// Diary.swift +// DomainDiary +// +// Created by 박민서 on 11/14/24. +// + +public struct Diary: Equatable { + public let id: Int + public var content: String + public var enjoyRating: Int + public var images: [DiaryImage] +} + +public struct DiaryImage: Equatable { + public let id: Int? + public let imageUrl: String +} From 300434bd3c48eb4dce35b7a5b42c283061eef9a6 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:42:55 +0900 Subject: [PATCH 15/26] =?UTF-8?q?Feat:=20Diary=20=EA=B4=80=EB=A0=A8=20API?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Diary/MoimDiaryRepository.swift | 2 +- .../API/EndPoint/Diary/DiaryEndPoint.swift | 84 ++++++---- .../Core/Network/Sources/DTO/DiaryDTO.swift | 148 ++++++++++-------- .../Diary/Sources/UseCase/DiaryUseCase.swift | 23 +++ .../RecordEdit/HomeDiaryEditStore.swift | 16 +- 5 files changed, 167 insertions(+), 106 deletions(-) diff --git a/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/MoimDiaryRepository.swift b/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/MoimDiaryRepository.swift index cf584307..6e7f7527 100644 --- a/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/MoimDiaryRepository.swift +++ b/Namo_SwiftUI/Projects/App/Sources/old/Data/Repository/Diary/MoimDiaryRepository.swift @@ -18,6 +18,6 @@ protocol MoimDiaryRepository { func deleteMoimDiary(moimScheduleId: Int) async -> Bool func getMonthMoimDiary(req: GetMonthMoimDiaryReqDTO) async -> GetMonthMoimDiaryResDTO? func getOneMoimDiary(moimScheduleId: Int) async -> GetOneMoimDiaryResDTO? - func getOneMoimDiaryDetail(moimScheduleId: Int) async -> Diary? + func getOneMoimDiaryDetail(moimScheduleId: Int) async -> Diary_Old? func deleteMoimDiaryOnPersonal(scheduleId: Int) async -> Bool } diff --git a/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift b/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift index 3f797556..6264534c 100644 --- a/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift +++ b/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift @@ -13,39 +13,59 @@ import SharedUtil public enum DiaryEndPoint { case getCalendarByMonth(ym: YearMonth) case getDiaryByDate(ymd: YearMonthDay) + case getDiaryBySchedule(id: Int) + case patchDiary(id: Int, reqDto: DiaryPatchRequestDTO) + case postDiary(reqDto: DiaryPostRequestDTO) } extension DiaryEndPoint: EndPoint { - public var baseURL: String { - return "\(SecretConstants.baseURL)/diaries" - } - - public var path: String { - switch self { - case .getCalendarByMonth(let ym): - return "/calendar/\(ym.year)-\(String(format: "%02d", ym.month))" - case .getDiaryByDate(let ymd): - return "/date/\(ymd.year)-\(String(format: "%02d", ymd.month))-\(String(format: "%02d", ymd.day))" - } - } - - public var method: Alamofire.HTTPMethod { - switch self { - case .getCalendarByMonth: - return .get - case .getDiaryByDate: - return .get - } - } - - public var task: APITask { - switch self { - case .getCalendarByMonth: - return .requestPlain - case .getDiaryByDate: - return .requestPlain - } - } - - + public var baseURL: String { + return "\(SecretConstants.baseURL)/diaries" + } + + public var path: String { + switch self { + case .getCalendarByMonth(let ym): + return "/calendar/\(ym.year)-\(String(format: "%02d", ym.month))" + case .getDiaryByDate(let ymd): + return "/date/\(ymd.year)-\(String(format: "%02d", ymd.month))-\(String(format: "%02d", ymd.day))" + case .getDiaryBySchedule(let id): + return "/\(id)" + case .patchDiary(let id, _): + return "/\(id)" + case .postDiary: + return "" + } + } + + public var method: Alamofire.HTTPMethod { + switch self { + case .getCalendarByMonth: + return .get + case .getDiaryByDate: + return .get + case .getDiaryBySchedule: + return .get + case .patchDiary: + return .patch + case .postDiary: + return .post + } + } + + public var task: APITask { + switch self { + case .getCalendarByMonth: + return .requestPlain + case .getDiaryByDate: + return .requestPlain + case .getDiaryBySchedule: + return .requestPlain + case .patchDiary(_, let reqDto): + return .requestJSONEncodable(parameters: reqDto) + case .postDiary(let reqDto): + return .requestJSONEncodable(parameters: reqDto) + } + } + } diff --git a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift index 6295e706..39b0462a 100644 --- a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift +++ b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift @@ -21,71 +21,89 @@ public struct DiaryImageResponseDTO: Decodable { public let imageUrl: String } -// -//public struct Diary: Decodable { -// public var scheduleId: Int -// public var name: String -// public var startDate: Int -// public var contents: String? -// public var images: [ImageResponse]? -// public var categoryId: Int -// public var color: Int -// public var placeName: String -// -// public init(scheduleId: Int? = nil, name: String? = nil, startDate: Int? = nil, contents: String? = nil, images: [ImageResponse]? = nil, categoryId: Int? = nil, color: Int? = nil, placeName: String? = nil) { -// self.scheduleId = scheduleId ?? -1 -// self.name = name ?? "" -// self.categoryId = categoryId ?? -1 -// self.startDate = startDate ?? 0 -// self.contents = contents ?? "" -// self.images = images ?? [] -// self.categoryId = categoryId ?? -1 -// self.color = color ?? -1 -// self.placeName = placeName ?? "" -// } -//} -// -//public struct GetDiaryRequestDTO: Encodable { -// public init(year: Int, month: Int, page: Int, size: Int) { -// self.year = year -// self.month = month -// self.page = page -// self.size = size -// } -// -// public var year: Int -// public var month: Int -// public var page: Int -// public var size: Int -//} -// -//public struct GetDiaryResponseDTO: Decodable { -// public init(content: [Diary], currentPage: Int, size: Int, first: Bool, last: Bool) { -// self.content = content -// self.currentPage = currentPage -// self.size = size -// self.first = first -// self.last = last -// } -// -// public var content: [Diary] -// public var currentPage: Int -// public var size: Int -// public var first: Bool -// public var last: Bool -//} -// -///// 개별 기록 조회 API 응답 -//public struct GetOneDiaryResponseDTO: Decodable { -// public init(contents: String? = nil, images: [ImageResponse]? = nil) { -// self.contents = contents -// self.images = images -// } -// -// public var contents: String? -// public var images: [ImageResponse]? -//} -// +public struct DiaryPatchRequestDTO: Encodable { + public let content: String + public let enjoyRating: Int + public let diaryImages: [DiaryImageRequestDTO] + public let deleteImages: [Int] +} + +public struct DiaryPostRequestDTO: Encodable { + public let scheduleId: Int + public let content: String + public let enjoyRating: Int + public let diaryImages: [DiaryImageRequestDTO] +} + +public struct DiaryImageRequestDTO: Encodable { + public let ordernumber: Int + public let imageUrl: String +} + +public struct Diary_Old: Decodable { + public var scheduleId: Int + public var name: String + public var startDate: Int + public var contents: String? + public var images: [ImageResponse]? + public var categoryId: Int + public var color: Int + public var placeName: String + + public init(scheduleId: Int? = nil, name: String? = nil, startDate: Int? = nil, contents: String? = nil, images: [ImageResponse]? = nil, categoryId: Int? = nil, color: Int? = nil, placeName: String? = nil) { + self.scheduleId = scheduleId ?? -1 + self.name = name ?? "" + self.categoryId = categoryId ?? -1 + self.startDate = startDate ?? 0 + self.contents = contents ?? "" + self.images = images ?? [] + self.categoryId = categoryId ?? -1 + self.color = color ?? -1 + self.placeName = placeName ?? "" + } +} + +public struct GetDiaryRequestDTO: Encodable { + public init(year: Int, month: Int, page: Int, size: Int) { + self.year = year + self.month = month + self.page = page + self.size = size + } + + public var year: Int + public var month: Int + public var page: Int + public var size: Int +} + +public struct GetDiaryResponseDTO: Decodable { + public init(content: [Diary_Old], currentPage: Int, size: Int, first: Bool, last: Bool) { + self.content = content + self.currentPage = currentPage + self.size = size + self.first = first + self.last = last + } + + public var content: [Diary_Old] + public var currentPage: Int + public var size: Int + public var first: Bool + public var last: Bool +} + +/// 개별 기록 조회 API 응답 +public struct GetOneDiaryResponseDTO: Decodable { + public init(contents: String? = nil, images: [ImageResponse]? = nil) { + self.contents = contents + self.images = images + } + + public var contents: String? + public var images: [ImageResponse]? +} + public struct CreateDiaryResponseDTO: Codable { public init(scheduleId: Int) { self.scheduleId = scheduleId diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift index 12121f3d..17191445 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift @@ -11,6 +11,7 @@ import ComposableArchitecture import SwiftUICalendar import CoreNetwork +import SharedUtil @DependencyClient public struct DiaryUseCase { @@ -66,6 +67,28 @@ public struct DiaryUseCase { return "00:00 - 23:59" } } + + public func getDiaryBySchedule(id: Int) async throws -> Diary? { + let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.getDiaryBySchedule(id: id)) + + return response.result?.toEntity() + } + + public func patchDiary(id: Int, reqDTO: DiaryPatchRequestDTO) async throws -> Void { + let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.patchDiary(id: id, reqDto: reqDTO)) + + if response.code != 200 { + throw APIError.customError("기록 수정 실패: 응답 코드 \(response.code)") + } + } + + public func postDiary(id: Int, reqDTO: DiaryPostRequestDTO) async throws -> Void { + let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.postDiary(reqDto: reqDTO)) + + if response.code != 200 { + throw APIError.customError("기록 작성 실패: 응답 코드 \(response.code)") + } + } } extension DiaryUseCase: DependencyKey { diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 5cf96b4c..8d834a67 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -40,14 +40,14 @@ public struct HomeDiaryEditStore { self.selectedImages = apiResult_selectedImages } - let isRevise: Bool - let scheduleName: String - let monthString: String - let dayString: String - let dateString: String - let placeName: String - var enjoyRating: Int - var contentString: String + let isRevise: Bool // 외부 주입 + let scheduleName: String // 외부 주입 + let monthString: String // 외부 주입 + let dayString: String // 외부 주입 + let dateString: String // 외부 주입 + let placeName: String // 외부 주입 + var enjoyRating: Int // API + var contentString: String // API var isContentValid: Bool = true var selectedItems: [PhotosPickerItem] var selectedImages: [Data] From 3aeaef10456f1c0bd7a78a7f3c31c6d4c971bb3c Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 15 Nov 2024 21:48:49 +0900 Subject: [PATCH 16/26] =?UTF-8?q?Feat:=20HomeDiaryEditStore=20State=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Diary/Sources/Model/Diary.swift | 14 ++++- .../RecordEdit/HomeDiaryEditStore.swift | 62 +++++++++---------- .../RecordEdit/HomeDiaryEditView.swift | 8 +-- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift index 4c25918d..3f90d984 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift @@ -6,10 +6,22 @@ // public struct Diary: Equatable { - public let id: Int + public let id: Int? public var content: String public var enjoyRating: Int public var images: [DiaryImage] + + public init( + id: Int? = nil, + content: String = "", + enjoyRating: Int = 0, + images: [DiaryImage] = [] + ) { + self.id = id + self.content = content + self.enjoyRating = enjoyRating + self.images = images + } } public struct DiaryImage: Equatable { diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 8d834a67..7ee7753b 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -11,49 +11,49 @@ import DomainSchedule import _PhotosUI_SwiftUI import SharedDesignSystem import SharedUtil +import DomainDiary @Reducer public struct HomeDiaryEditStore { - public init() {} + + @Dependency(\.diaryUseCase) var diaryUseCase public static let contentLimit: Int = 200 + public init() {} @ObservableState public struct State: Equatable { public init(schedule: Schedule) { - // get API 호출 - let apiResult_isSuccess = true // 존재하면 true, 없으면 false - let apiResult_enjoyRating = 3 - let apiResult_contentString = "Hello, world!" - let apiResult_imageURLs: [String] = [] - let apiResult_selectedItems: [PhotosPickerItem] = [] - let apiResult_selectedImages: [Data] = [] - - self.isRevise = apiResult_isSuccess - self.scheduleName = schedule.title - self.monthString = schedule.startDate.toMM() - self.dayString = schedule.startDate.toDD() - self.dateString = "\(schedule.startDate.toYMDEHM()) \n - \(schedule.endDate.toYMDEHM())" - self.placeName = schedule.locationInfo?.locationName ?? "" - self.enjoyRating = apiResult_enjoyRating - self.contentString = apiResult_contentString - self.selectedItems = apiResult_selectedItems - self.selectedImages = apiResult_selectedImages + self.schedule = schedule } - let isRevise: Bool // 외부 주입 - let scheduleName: String // 외부 주입 - let monthString: String // 외부 주입 - let dayString: String // 외부 주입 - let dateString: String // 외부 주입 - let placeName: String // 외부 주입 - var enjoyRating: Int // API - var contentString: String // API + /// 기존 게시물 여부 - API 응답 결과 + let isRevise: Bool = false // API 호출 후 변경되 + /// 컨텐츠 수정 상태 + var isChanged: Bool { initialDiary == diary } + + /// 스케쥴 + let schedule: Schedule + var scheduleName: String { schedule.title } + var monthString: String { schedule.startDate.toMM() } + var dayString: String { schedule.startDate.toDD() } + var dateString: String { "\(schedule.startDate.toYMDEHM()) \n - \(schedule.endDate.toYMDEHM())" } + var placeName: String { schedule.locationInfo?.locationName ?? "" } + + /// 기록 + let initialDiary: Diary = Diary() + var diary: Diary = Diary() + + /// 본문 조건 적합 체크 var isContentValid: Bool = true - var selectedItems: [PhotosPickerItem] - var selectedImages: [Data] + var selectedItems: [PhotosPickerItem] = [] + var selectedImages: [Data] = [] + /// 저장 버튼 상태 var saveButtonState: NamoButton.NamoButtonType = .inactive + /// 토스트 표시 var showToast: Bool = false + /// alert 컨텐츠 var alertContent: NamoAlertType = .none + /// alert 표시 var showAlert: Bool = false } @@ -82,11 +82,11 @@ public struct HomeDiaryEditStore { return .none case .tapEnjoyRating(let rate): - state.enjoyRating = rate + state.diary.enjoyRating = rate return .none case .typeContent(let content): - state.contentString = content + state.diary.content = content return .send(.validateContent(content)) case .validateContent(let content): diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index 34a3e2d9..b23572c0 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -150,7 +150,7 @@ private extension HomeDiaryEditView { func EnjoyCountView(store: StoreOf) -> some View { HStack(spacing: 4) { ForEach(0..<3) { index in - let isFilled = index < store.enjoyRating + let isFilled = index < store.diary.enjoyRating Image(asset: isFilled ? SharedDesignSystemAsset.Assets.icHeartSelected : SharedDesignSystemAsset.Assets.icHeart) .resizable() .scaledToFit() @@ -170,7 +170,7 @@ private extension HomeDiaryEditView { VStack(spacing: 10) { ContentInputView(store: store) ContentFooterView( - count: store.contentString.count, + count: store.diary.content.count, maxCount: HomeDiaryEditStore.contentLimit, isValid: store.isContentValid ) @@ -184,7 +184,7 @@ private extension HomeDiaryEditView { .fill(Color.namoPink) .frame(width: 10) // 다중 줄 텍스트 입력 - TextEditor(text: Binding(get: { store.contentString }, set: { store.send(.typeContent($0)) })) + TextEditor(text: Binding(get: { store.diary.content }, set: { store.send(.typeContent($0)) })) .font(.pretendard(.regular, size: 14)) .foregroundStyle(Color.mainText) .scrollContentBackground(.hidden) @@ -194,7 +194,7 @@ private extension HomeDiaryEditView { Text("내용 입력") .font(.pretendard(.bold, size: 14)) .foregroundStyle(Color.textUnselected) - .opacity(store.contentString.isEmpty ? 1 : 0) + .opacity(store.diary.content.isEmpty ? 1 : 0) .padding(.vertical, 16) .padding(.horizontal, 16), alignment: .topLeading From f0a54b3d3cd846f43a0aeae801432a6e92339065 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:40:13 +0900 Subject: [PATCH 17/26] =?UTF-8?q?Feat:=20HomeDiaryEdit=20-=20DIary=20Get?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Diary/Sources/UseCase/DiaryUseCase.swift | 14 ++++++++--- .../Schedule/Sources/Model/Schedule.swift | 2 +- .../RecordEdit/HomeDiaryEditStore.swift | 25 ++++++++++++++++--- .../RecordEdit/HomeDiaryEditView.swift | 1 + 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift index 17191445..95a9d5b8 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift @@ -68,10 +68,18 @@ public struct DiaryUseCase { } } - public func getDiaryBySchedule(id: Int) async throws -> Diary? { + public func getDiaryBySchedule(id: Int) async throws -> Diary { let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.getDiaryBySchedule(id: id)) - - return response.result?.toEntity() + + if response.code != 200 { + throw APIError.customError("기록 로드 실패: 응답 코드 \(response.code)") + } + + guard let diary = response.result?.toEntity() else { + throw APIError.parseError("result.result is nil") + } + + return diary } public func patchDiary(id: Int, reqDTO: DiaryPatchRequestDTO) async throws -> Void { diff --git a/Namo_SwiftUI/Projects/Domain/Schedule/Sources/Model/Schedule.swift b/Namo_SwiftUI/Projects/Domain/Schedule/Sources/Model/Schedule.swift index ee1a8c5e..0b466a77 100644 --- a/Namo_SwiftUI/Projects/Domain/Schedule/Sources/Model/Schedule.swift +++ b/Namo_SwiftUI/Projects/Domain/Schedule/Sources/Model/Schedule.swift @@ -117,7 +117,7 @@ public struct ScheduleNotification: Decodable, Hashable { public extension Schedule { static let dummySchedules: [Schedule] = [ Schedule( - scheduleId: 1, + scheduleId: 321, title: "Test1", categoryInfo: ScheduleCategory( categoryId: 1, diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 7ee7753b..b376e05e 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -22,12 +22,13 @@ public struct HomeDiaryEditStore { @ObservableState public struct State: Equatable { - public init(schedule: Schedule) { + public init(schedule: Schedule, hasDiary: Bool) { self.schedule = schedule + self.isRevise = hasDiary } /// 기존 게시물 여부 - API 응답 결과 - let isRevise: Bool = false // API 호출 후 변경되 + let isRevise: Bool /// 컨텐츠 수정 상태 var isChanged: Bool { initialDiary == diary } @@ -40,7 +41,7 @@ public struct HomeDiaryEditStore { var placeName: String { schedule.locationInfo?.locationName ?? "" } /// 기록 - let initialDiary: Diary = Diary() + var initialDiary: Diary = Diary() var diary: Diary = Diary() /// 본문 조건 적합 체크 @@ -70,6 +71,9 @@ public struct HomeDiaryEditStore { case tapSaveDiaryButton case handleAlertConfirm case dismiss + case onAppear + case loadDiary + case loadDiaryCompleted(Diary) } public var body: some ReducerOf { @@ -153,6 +157,21 @@ public struct HomeDiaryEditStore { print("dismiss") return .none + case .onAppear: + return state.isRevise + ? .send(.loadDiary) + : .none + + case .loadDiary: + return .run { [id = state.schedule.scheduleId] send in + let result = try await diaryUseCase.getDiaryBySchedule(id: id) + await send(.loadDiaryCompleted(result)) + } + + case .loadDiaryCompleted(let diary): + state.diary = diary + state.initialDiary = diary + return .none } } } diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index b23572c0..f959108f 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -51,6 +51,7 @@ public struct HomeDiaryEditView: View { } ) } + .onAppear { store.send(.onAppear) } .namoNabBar(center: { Text(store.scheduleName) .font(.pretendard(.bold, size: 22)) From 841831ee1f1e0c5d440870c83972938639ed252b Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:49:25 +0900 Subject: [PATCH 18/26] =?UTF-8?q?Feat:=20Diary=20DTO=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Network/Sources/DTO/DiaryDTO.swift | 14 ++++++++++- .../Diary/Sources/Mapper/DiaryMapper.swift | 23 ++++++++++++++++++- .../Domain/Diary/Sources/Model/Diary.swift | 11 +++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift index 39b0462a..ac816ee4 100644 --- a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift +++ b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift @@ -33,11 +33,23 @@ public struct DiaryPostRequestDTO: Encodable { public let content: String public let enjoyRating: Int public let diaryImages: [DiaryImageRequestDTO] + + public init(scheduleId: Int, content: String, enjoyRating: Int, diaryImages: [DiaryImageRequestDTO]) { + self.scheduleId = scheduleId + self.content = content + self.enjoyRating = enjoyRating + self.diaryImages = diaryImages + } } public struct DiaryImageRequestDTO: Encodable { - public let ordernumber: Int + public let orderNumber: Int public let imageUrl: String + + public init(orderNumber: Int, imageUrl: String) { + self.orderNumber = orderNumber + self.imageUrl = imageUrl + } } public struct Diary_Old: Decodable { diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift index 51a1a244..ac1f838d 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift @@ -69,7 +69,18 @@ extension DiaryResponseDTO { func mapAndSortDiaryImages(from responseDTOs: [DiaryImageResponseDTO]) -> [DiaryImage] { return responseDTOs .sorted(by: { $0.orderNumber < $1.orderNumber }) - .map { DiaryImage(id: $0.diaryImageId, imageUrl: $0.imageUrl) } + .map { DiaryImage(id: $0.diaryImageId, orderNumber: $0.orderNumber, imageUrl: $0.imageUrl) } + } +} + +extension Diary { + func toPostDTO(scheduleId: Int) -> DiaryPostRequestDTO { + return DiaryPostRequestDTO( + scheduleId: scheduleId, + content: content, + enjoyRating: enjoyRating, + diaryImages: images.map { $0.toDTO() } + ) } } @@ -77,6 +88,16 @@ extension DiaryImageResponseDTO { func toEntity() -> DiaryImage { return DiaryImage( id: diaryImageId, + orderNumber: orderNumber, + imageUrl: imageUrl + ) + } +} + +extension DiaryImage { + func toDTO() -> DiaryImageRequestDTO { + return DiaryImageRequestDTO( + orderNumber: orderNumber, imageUrl: imageUrl ) } diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift index 3f90d984..cdfb8204 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift @@ -26,5 +26,16 @@ public struct Diary: Equatable { public struct DiaryImage: Equatable { public let id: Int? + public let orderNumber: Int public let imageUrl: String + + public init( + id: Int? = nil, + orderNumber: Int, + imageUrl: String + ) { + self.id = id + self.orderNumber = orderNumber + self.imageUrl = imageUrl + } } From 81b9181d764221c7c10ddfb75f73f979269602e7 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:49:57 +0900 Subject: [PATCH 19/26] =?UTF-8?q?Feat:=20=EC=9D=BC=EA=B8=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=B3=91=EB=A0=AC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Diary/Sources/UseCase/DiaryUseCase.swift | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift index 95a9d5b8..776a93d4 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift @@ -90,13 +90,48 @@ public struct DiaryUseCase { } } - public func postDiary(id: Int, reqDTO: DiaryPostRequestDTO) async throws -> Void { - let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.postDiary(reqDto: reqDTO)) + public func postDiary(scheduleId: Int, reqDiary: Diary) async throws -> Void { + let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.postDiary(reqDto: reqDiary.toPostDTO(scheduleId: scheduleId))) if response.code != 200 { throw APIError.customError("기록 작성 실패: 응답 코드 \(response.code)") } } + + public func postDiaryImages(scheduleId: Int, images: [UIImage]) async throws -> [DiaryImage] { + let compImgs = try images.map { image in + guard let compressedData = image.jpegData(compressionQuality: 0.6) else { + throw NSError(domain: "이미지 압축 에러", code: 1001) + } + return compressedData + } + + // 이미지 병렬 업로드 처리 + return try await withThrowingTaskGroup(of: (Int, String).self) { group in + for (index, img) in compImgs.enumerated() { + group.addTask { + let fileName = "diary_image_schedule_\(scheduleId)_index_\(index)_\(Int(Date().timeIntervalSince1970))_\(UUID().uuidString)" + + guard let url = try await APIManager.shared.getPresignedUrl(prefix: "diary", filename: fileName).result else { + throw APIError.customError("S3 getPresignedUrl 에러") + } + + guard let uploadedUrl = try await APIManager.shared.uploadImageToS3(presignedUrl: url, imageFile: img) else { + throw APIError.customError("S3 uploadImageToS3 에러") + } + + return (index, uploadedUrl) + } + } + + var uploadedImages: [DiaryImage] = [] + for try await (index, uploadedUrl) in group { + uploadedImages.append(DiaryImage(orderNumber: index, imageUrl: uploadedUrl)) + } + + return uploadedImages + } + } } extension DiaryUseCase: DependencyKey { From 4ecf71ff50c23d7f386f32fb1ac14d5548b13611 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:50:40 +0900 Subject: [PATCH 20/26] =?UTF-8?q?Feat:=20Diary=20Post=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20Store=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index b376e05e..dc63765d 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -30,7 +30,7 @@ public struct HomeDiaryEditStore { /// 기존 게시물 여부 - API 응답 결과 let isRevise: Bool /// 컨텐츠 수정 상태 - var isChanged: Bool { initialDiary == diary } + var isChanged: Bool { initialDiary != diary } /// 스케쥴 let schedule: Schedule @@ -49,7 +49,9 @@ public struct HomeDiaryEditStore { var selectedItems: [PhotosPickerItem] = [] var selectedImages: [Data] = [] /// 저장 버튼 상태 - var saveButtonState: NamoButton.NamoButtonType = .inactive + var saveButtonState: NamoButton.NamoButtonType { + return isChanged ? .active : .inactive + } /// 토스트 표시 var showToast: Bool = false /// alert 컨텐츠 @@ -74,6 +76,9 @@ public struct HomeDiaryEditStore { case onAppear case loadDiary case loadDiaryCompleted(Diary) + case postDiaryImages([UIImage]) + case addPostedDiaryImages([DiaryImage]) + case postDiary } public var body: some ReducerOf { @@ -131,25 +136,25 @@ public struct HomeDiaryEditStore { return .none case .tapSaveDiaryButton: - switch state.saveButtonState { - - case .active: - print("기록 저장") - return .none - default: - return .none - } + guard state.saveButtonState == .active else { return .none } + let images: [UIImage] = state.selectedImages.compactMap { UIImage(data: $0) } + + return images.count > 0 + ? .send(.postDiaryImages(images)) + : .send(.postDiary) case .handleAlertConfirm: - state.alertContent = .none // alert 초기화 switch state.alertContent { case .deleteDiary: print("삭제 api 호출") + state.alertContent = .none return .none case .backWithoutSave: + state.alertContent = .none return .send(.dismiss) default: + state.alertContent = .none return .none } @@ -159,19 +164,42 @@ public struct HomeDiaryEditStore { case .onAppear: return state.isRevise - ? .send(.loadDiary) - : .none + ? .send(.loadDiary) + : .none case .loadDiary: return .run { [id = state.schedule.scheduleId] send in - let result = try await diaryUseCase.getDiaryBySchedule(id: id) - await send(.loadDiaryCompleted(result)) + do { + let result = try await diaryUseCase.getDiaryBySchedule(id: id) + await send(.loadDiaryCompleted(result)) + } catch { + print(error.localizedDescription) + await send(.dismiss) + } } case .loadDiaryCompleted(let diary): state.diary = diary state.initialDiary = diary return .none + + case .postDiaryImages(let imgs): + return .run { [id = state.schedule.scheduleId] send in + let diaryImgs = try await diaryUseCase.postDiaryImages(scheduleId: id, images: imgs) + await send(.addPostedDiaryImages(diaryImgs)) + } + + case .addPostedDiaryImages(let diaryImgs): + state.diary.images = diaryImgs + return .send(.postDiary) + + case .postDiary: + return .run { [ + id = state.schedule.scheduleId, + diary = state.diary + ] send in + try await diaryUseCase.postDiary(scheduleId: id, reqDiary: diary) + } } } } From 4fbefaa53fcdfbfbec67195884bb9a4cda025fa3 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 01:09:00 +0900 Subject: [PATCH 21/26] =?UTF-8?q?Feat:=20HomeDiaryEdit=20Toast=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=EC=A1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 58 +++++++++++++++++-- .../RecordEdit/HomeDiaryEditView.swift | 2 +- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index dc63765d..692ba107 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -13,6 +13,32 @@ import SharedDesignSystem import SharedUtil import DomainDiary +extension HomeDiaryEditStore { + public enum Toast { + case none + case saveSuccess + case saveFailed + case addImageFailed + case uploadImageFailed + + var content: String { + switch self { + + case .none: + return "" + case .saveSuccess: + return "기록이 저장되었습니다." + case .saveFailed: + return "기록 저장에 실패했습니다.\n다시 시도해주세요." + case .addImageFailed: + return "사진 추가에 실패했습니다.\n다시 시도해주세요." + case .uploadImageFailed: + return "사진 업로드에 실패했습니다.\n다시 시도해주세요." + } + } + } +} + @Reducer public struct HomeDiaryEditStore { @@ -54,6 +80,8 @@ public struct HomeDiaryEditStore { } /// 토스트 표시 var showToast: Bool = false + /// 토스트 컨텐츠 + var toast: Toast = .none /// alert 컨텐츠 var alertContent: NamoAlertType = .none /// alert 표시 @@ -74,6 +102,7 @@ public struct HomeDiaryEditStore { case handleAlertConfirm case dismiss case onAppear + case showToast(Toast) case loadDiary case loadDiaryCompleted(Diary) case postDiaryImages([UIImage]) @@ -111,15 +140,14 @@ public struct HomeDiaryEditStore { case .addImage(.success(let data)): guard let data else { - print("data is nil") - return .none + return .send(.showToast(.addImageFailed)) } state.selectedImages.append(data) return .none case .addImage(.failure(let error)): print("error occured: \(error.localizedDescription)") - return .none + return .send(.showToast(.addImageFailed)) case .deleteImage(let index): state.selectedImages.remove(at: index) @@ -131,6 +159,7 @@ public struct HomeDiaryEditStore { return .none case .tapDeleteDiaryButton: + // TODO : ALERT 로직 toast 처럼 수정 state.alertContent = .deleteDiary state.showAlert = true return .none @@ -167,6 +196,11 @@ public struct HomeDiaryEditStore { ? .send(.loadDiary) : .none + case .showToast(let toast): + state.toast = toast + state.showToast = true + return .none + case .loadDiary: return .run { [id = state.schedule.scheduleId] send in do { @@ -174,6 +208,7 @@ public struct HomeDiaryEditStore { await send(.loadDiaryCompleted(result)) } catch { print(error.localizedDescription) + // TODO: 로딩 실패 Alert 표시 await send(.dismiss) } } @@ -185,8 +220,13 @@ public struct HomeDiaryEditStore { case .postDiaryImages(let imgs): return .run { [id = state.schedule.scheduleId] send in - let diaryImgs = try await diaryUseCase.postDiaryImages(scheduleId: id, images: imgs) - await send(.addPostedDiaryImages(diaryImgs)) + do { + let diaryImgs = try await diaryUseCase.postDiaryImages(scheduleId: id, images: imgs) + await send(.addPostedDiaryImages(diaryImgs)) + } catch { + print(error.localizedDescription) + await send(.showToast(.uploadImageFailed)) + } } case .addPostedDiaryImages(let diaryImgs): @@ -198,7 +238,13 @@ public struct HomeDiaryEditStore { id = state.schedule.scheduleId, diary = state.diary ] send in - try await diaryUseCase.postDiary(scheduleId: id, reqDiary: diary) + do { + try await diaryUseCase.postDiary(scheduleId: id, reqDiary: diary) + await send(.showToast(.saveSuccess)) + } + catch { + await send(.showToast(.saveFailed)) + } } } } diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index f959108f..824d1920 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -77,7 +77,7 @@ public struct HomeDiaryEditView: View { .ignoresSafeArea(.container, edges: .bottom) .namoToastView( isPresented: $store.showToast, - title: "기록이 저장되었습니다.", + title: store.toast.content, isTabBarScreen: false ) .namoAlertView( From 335c7f329139d91e1574af1ee0411ba590408ab8 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 01:19:36 +0900 Subject: [PATCH 22/26] =?UTF-8?q?Feat:=20show=20Alert=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20Toast=EC=99=80=20=EA=B0=99=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 49 ++++++++++++++----- .../Util/Sources/Model/NamoAlertContent.swift | 20 -------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 692ba107..fd1802c1 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -39,6 +39,31 @@ extension HomeDiaryEditStore { } } +extension HomeDiaryEditStore { + public enum AlertType: Equatable { + case none + case deleteDiary + case backWithoutSave + case loadFailed + case custom(NamoAlertContent) + + public var content: NamoAlertContent { + switch self { + case .none: + return NamoAlertContent() + case .deleteDiary: + return NamoAlertContent(title: "기록을 정말 삭제하시겠어요?") + case .backWithoutSave: + return NamoAlertContent(title: "편집된 내용이 저장되지 않습니다.", message: "정말 나가시겠어요?") + case .loadFailed: + return NamoAlertContent(title: "기록 불러오기에 실패했습니다.\n다시 시도해주세요.") + case .custom(let content): + return NamoAlertContent(title: content.title, message: content.title) + } + } + } +} + @Reducer public struct HomeDiaryEditStore { @@ -83,7 +108,7 @@ public struct HomeDiaryEditStore { /// 토스트 컨텐츠 var toast: Toast = .none /// alert 컨텐츠 - var alertContent: NamoAlertType = .none + var alertContent: AlertType = .none /// alert 표시 var showAlert: Bool = false } @@ -103,6 +128,7 @@ public struct HomeDiaryEditStore { case dismiss case onAppear case showToast(Toast) + case showAlert(AlertType) case loadDiary case loadDiaryCompleted(Diary) case postDiaryImages([UIImage]) @@ -154,15 +180,12 @@ public struct HomeDiaryEditStore { return .none case .tapBackButton: - state.alertContent = .backWithoutSave - state.showAlert = true - return .none + return state.isChanged + ? .send(.showAlert(.backWithoutSave)) + : .none case .tapDeleteDiaryButton: - // TODO : ALERT 로직 toast 처럼 수정 - state.alertContent = .deleteDiary - state.showAlert = true - return .none + return .send(.showAlert(.deleteDiary)) case .tapSaveDiaryButton: guard state.saveButtonState == .active else { return .none } @@ -179,7 +202,7 @@ public struct HomeDiaryEditStore { print("삭제 api 호출") state.alertContent = .none return .none - case .backWithoutSave: + case .backWithoutSave, .loadFailed: state.alertContent = .none return .send(.dismiss) default: @@ -201,6 +224,11 @@ public struct HomeDiaryEditStore { state.showToast = true return .none + case .showAlert(let alert): + state.alertContent = alert + state.showAlert = true + return .none + case .loadDiary: return .run { [id = state.schedule.scheduleId] send in do { @@ -208,8 +236,7 @@ public struct HomeDiaryEditStore { await send(.loadDiaryCompleted(result)) } catch { print(error.localizedDescription) - // TODO: 로딩 실패 Alert 표시 - await send(.dismiss) + await send(.showAlert(.loadFailed)) } } diff --git a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift index 78bab750..9fc7d5d0 100644 --- a/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift +++ b/Namo_SwiftUI/Projects/Shared/Util/Sources/Model/NamoAlertContent.swift @@ -14,23 +14,3 @@ public struct NamoAlertContent: Equatable { self.message = message } } - -public enum NamoAlertType: Equatable { - case deleteDiary - case backWithoutSave - case custom(NamoAlertContent) - case none - - public var content: NamoAlertContent { - switch self { - case .none: - return NamoAlertContent() - case .deleteDiary: - return NamoAlertContent(title: "기록을 정말 삭제하시겠어요?") - case .backWithoutSave: - return NamoAlertContent(title: "편집된 내용이 저장되지 않습니다.", message: "정말 나가시겠어요?") - case .custom(let content): - return NamoAlertContent(title: content.title, message: content.title) - } - } -} From d15dd1d21839d6041ad461e44f67b47009d5a6b4 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 01:48:46 +0900 Subject: [PATCH 23/26] =?UTF-8?q?Feat:=20loadDiaryIamges=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index fd1802c1..5d2fd0e9 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -12,6 +12,7 @@ import _PhotosUI_SwiftUI import SharedDesignSystem import SharedUtil import DomainDiary +import Kingfisher extension HomeDiaryEditStore { public enum Toast { @@ -131,6 +132,7 @@ public struct HomeDiaryEditStore { case showAlert(AlertType) case loadDiary case loadDiaryCompleted(Diary) + case loadDiaryImages(Diary) case postDiaryImages([UIImage]) case addPostedDiaryImages([DiaryImage]) case postDiary @@ -232,8 +234,49 @@ public struct HomeDiaryEditStore { case .loadDiary: return .run { [id = state.schedule.scheduleId] send in do { - let result = try await diaryUseCase.getDiaryBySchedule(id: id) - await send(.loadDiaryCompleted(result)) + let diary = try await diaryUseCase.getDiaryBySchedule(id: id) + await send(.loadDiaryCompleted(diary)) + } catch { + print(error.localizedDescription) + await send(.showAlert(.loadFailed)) + } + } + + case .loadDiaryImages(let diary): + return .run { send in + do { + let updatedImages = try await withThrowingTaskGroup(of: Data.self) { group in + + for diaryImage in diary.images { + group.addTask { + guard let url = URL(string: diaryImage.imageUrl) else { + throw APIError.customError("잘못된 URL") + } + + let data = try await withCheckedThrowingContinuation { continuation in + + KingfisherManager.shared.retrieveImage(with: url) { result in + switch result { + case .success(let value): + continuation.resume(returning: value.image.pngData() ?? Data()) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + return data + } + } + + var images: [Data] = [] + for try await image in group { + images.append(image) + } + return images + } + + // TODO: addImage 배열로 바꾸고 다시 수정 +// await send(.addImage(.success(updatedImages))) } catch { print(error.localizedDescription) await send(.showAlert(.loadFailed)) From 31d26372309ae44e82985c5767a9586d28f98882 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 03:14:41 +0900 Subject: [PATCH 24/26] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EA=B4=80=EB=A0=A8=20API=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordEdit/HomeDiaryEditStore.swift | 29 +++++++++++++++---- .../RecordEdit/HomeDiaryEditView.swift | 22 +++++++------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 5d2fd0e9..e0061414 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -15,12 +15,13 @@ import DomainDiary import Kingfisher extension HomeDiaryEditStore { - public enum Toast { + public enum Toast: Equatable { case none case saveSuccess case saveFailed case addImageFailed case uploadImageFailed + case custom(String) var content: String { switch self { @@ -35,6 +36,8 @@ extension HomeDiaryEditStore { return "사진 추가에 실패했습니다.\n다시 시도해주세요." case .uploadImageFailed: return "사진 업로드에 실패했습니다.\n다시 시도해주세요." + case .custom(let content): + return content } } } @@ -82,7 +85,7 @@ public struct HomeDiaryEditStore { /// 기존 게시물 여부 - API 응답 결과 let isRevise: Bool /// 컨텐츠 수정 상태 - var isChanged: Bool { initialDiary != diary } + var isChanged: Bool { initialDiary != diary || initialImages != selectedImages } /// 스케쥴 let schedule: Schedule @@ -99,6 +102,7 @@ public struct HomeDiaryEditStore { /// 본문 조건 적합 체크 var isContentValid: Bool = true var selectedItems: [PhotosPickerItem] = [] + var initialImages: [Data] = [] var selectedImages: [Data] = [] /// 저장 버튼 상태 var saveButtonState: NamoButton.NamoButtonType { @@ -133,6 +137,7 @@ public struct HomeDiaryEditStore { case loadDiary case loadDiaryCompleted(Diary) case loadDiaryImages(Diary) + case loadInitialImages([Data], Diary) case postDiaryImages([UIImage]) case addPostedDiaryImages([DiaryImage]) case postDiary @@ -170,6 +175,9 @@ public struct HomeDiaryEditStore { guard let data else { return .send(.showToast(.addImageFailed)) } + guard state.selectedImages.count < 3 else { + return .send(.showToast(.custom("3개가 넘습니다"))) + } state.selectedImages.append(data) return .none @@ -235,7 +243,11 @@ public struct HomeDiaryEditStore { return .run { [id = state.schedule.scheduleId] send in do { let diary = try await diaryUseCase.getDiaryBySchedule(id: id) - await send(.loadDiaryCompleted(diary)) + if !diary.images.isEmpty { + await send(.loadDiaryImages(diary)) + } else { + await send(.loadDiaryCompleted(diary)) + } } catch { print(error.localizedDescription) await send(.showAlert(.loadFailed)) @@ -275,13 +287,20 @@ public struct HomeDiaryEditStore { return images } - // TODO: addImage 배열로 바꾸고 다시 수정 -// await send(.addImage(.success(updatedImages))) + for image in updatedImages { + await send(.addImage(.success(image))) + } + + await send(.loadInitialImages(updatedImages, diary)) } catch { print(error.localizedDescription) await send(.showAlert(.loadFailed)) } } + + case .loadInitialImages(let imgs, let diary): + imgs.forEach { state.initialImages.append($0) } + return .send(.loadDiaryCompleted(diary)) case .loadDiaryCompleted(let diary): state.diary = diary diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift index 824d1920..3a4edb93 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditView.swift @@ -220,12 +220,7 @@ private extension HomeDiaryEditView { private extension HomeDiaryEditView { func DiaryImageView(store: StoreOf) -> some View { - - PhotosPicker(selection: Binding(get: { store.selectedItems }, set: { newItems in - if let newItem = newItems.last, newItems.count > store.selectedItems.count { - store.send(.selectPhoto(newItem)) - } - }), maxSelectionCount: 3) { + HStack(spacing: 15) { ForEach(Array(store.selectedImages.enumerated()), id: \.self.element) { (offset, imageData) in DiaryImageListItemView( image: UIImage(data: imageData) ?? UIImage(), @@ -234,10 +229,17 @@ private extension HomeDiaryEditView { ) } - if store.selectedImages.count < 3 { - Image(asset: SharedDesignSystemAsset.Assets.noPicture) - .resizable() - .frame(width: 92, height: 92) + PhotosPicker(selection: Binding(get: { store.selectedItems }, set: { + newItems in + guard let item = newItems.last else { return } + store.send(.selectPhoto(item)) + }), maxSelectionCount: 1) { + + if store.selectedImages.count < 3 { + Image(asset: SharedDesignSystemAsset.Assets.noPicture) + .resizable() + .frame(width: 92, height: 92) + } } } } From e68b92ea8cbad461a2112c14c7098ff7fdff4666 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:09:59 +0900 Subject: [PATCH 25/26] =?UTF-8?q?Feat:=20Delete,=20Patch=20Diary=20API=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../API/EndPoint/Diary/DiaryEndPoint.swift | 7 + .../Core/Network/Sources/DTO/DiaryDTO.swift | 7 + .../Diary/Sources/Mapper/DiaryMapper.swift | 9 ++ .../Domain/Diary/Sources/Model/Diary.swift | 2 +- .../Diary/Sources/UseCase/DiaryUseCase.swift | 13 +- .../RecordEdit/HomeDiaryEditStore.swift | 126 +++++++++++++++--- 6 files changed, 146 insertions(+), 18 deletions(-) diff --git a/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift b/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift index 6264534c..19524d9d 100644 --- a/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift +++ b/Namo_SwiftUI/Projects/Core/Network/Sources/API/EndPoint/Diary/DiaryEndPoint.swift @@ -16,6 +16,7 @@ public enum DiaryEndPoint { case getDiaryBySchedule(id: Int) case patchDiary(id: Int, reqDto: DiaryPatchRequestDTO) case postDiary(reqDto: DiaryPostRequestDTO) + case deleteDiary(id: Int) } extension DiaryEndPoint: EndPoint { @@ -35,6 +36,8 @@ extension DiaryEndPoint: EndPoint { return "/\(id)" case .postDiary: return "" + case .deleteDiary(let id): + return "/\(id)" } } @@ -50,6 +53,8 @@ extension DiaryEndPoint: EndPoint { return .patch case .postDiary: return .post + case .deleteDiary: + return .delete } } @@ -65,6 +70,8 @@ extension DiaryEndPoint: EndPoint { return .requestJSONEncodable(parameters: reqDto) case .postDiary(let reqDto): return .requestJSONEncodable(parameters: reqDto) + case .deleteDiary: + return .requestPlain } } diff --git a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift index ac816ee4..87e0e568 100644 --- a/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift +++ b/Namo_SwiftUI/Projects/Core/Network/Sources/DTO/DiaryDTO.swift @@ -26,6 +26,13 @@ public struct DiaryPatchRequestDTO: Encodable { public let enjoyRating: Int public let diaryImages: [DiaryImageRequestDTO] public let deleteImages: [Int] + + public init(content: String, enjoyRating: Int, diaryImages: [DiaryImageRequestDTO], deleteImages: [Int]) { + self.content = content + self.enjoyRating = enjoyRating + self.diaryImages = diaryImages + self.deleteImages = deleteImages + } } public struct DiaryPostRequestDTO: Encodable { diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift index ac1f838d..d0492b83 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Mapper/DiaryMapper.swift @@ -82,6 +82,15 @@ extension Diary { diaryImages: images.map { $0.toDTO() } ) } + + func toPatchDTO(deleteImages: [Int]) -> DiaryPatchRequestDTO { + return DiaryPatchRequestDTO( + content: self.content, + enjoyRating: self.enjoyRating, + diaryImages: self.images.map { $0.toDTO() }, + deleteImages: deleteImages + ) + } } extension DiaryImageResponseDTO { diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift index cdfb8204..fb26c1b3 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/Model/Diary.swift @@ -26,7 +26,7 @@ public struct Diary: Equatable { public struct DiaryImage: Equatable { public let id: Int? - public let orderNumber: Int + public var orderNumber: Int public let imageUrl: String public init( diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift index 776a93d4..960a5006 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift @@ -82,8 +82,8 @@ public struct DiaryUseCase { return diary } - public func patchDiary(id: Int, reqDTO: DiaryPatchRequestDTO) async throws -> Void { - let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.patchDiary(id: id, reqDto: reqDTO)) + public func patchDiary(id: Int, reqDiary: Diary, deleteImages: [Int]) async throws -> Void { + let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.patchDiary(id: id, reqDto: reqDiary.toPatchDTO(deleteImages: deleteImages))) if response.code != 200 { throw APIError.customError("기록 수정 실패: 응답 코드 \(response.code)") @@ -126,12 +126,21 @@ public struct DiaryUseCase { var uploadedImages: [DiaryImage] = [] for try await (index, uploadedUrl) in group { + // TODO: index 0부터인지 1부터인지 확인 uploadedImages.append(DiaryImage(orderNumber: index, imageUrl: uploadedUrl)) } return uploadedImages } } + + public func deleteDiary(id: Int) async throws -> Void { + let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.deleteDiary(id: id)) + + if response.code != 200 { + throw APIError.customError("기록 삭제 실패: 응답 코드 \(response.code)") + } + } } extension DiaryUseCase: DependencyKey { diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index e0061414..461dffbc 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -21,6 +21,7 @@ extension HomeDiaryEditStore { case saveFailed case addImageFailed case uploadImageFailed + case deleteFailed case custom(String) var content: String { @@ -36,6 +37,8 @@ extension HomeDiaryEditStore { return "사진 추가에 실패했습니다.\n다시 시도해주세요." case .uploadImageFailed: return "사진 업로드에 실패했습니다.\n다시 시도해주세요." + case .deleteFailed: + return "기록 삭제에 실패했습니다.\n다시 시도해주세요." case .custom(let content): return content } @@ -60,7 +63,7 @@ extension HomeDiaryEditStore { case .backWithoutSave: return NamoAlertContent(title: "편집된 내용이 저장되지 않습니다.", message: "정말 나가시겠어요?") case .loadFailed: - return NamoAlertContent(title: "기록 불러오기에 실패했습니다.\n다시 시도해주세요.") + return NamoAlertContent(title: "기록 불러오기에 실패했습니다.", message: "다시 시도해주세요.") case .custom(let content): return NamoAlertContent(title: content.title, message: content.title) } @@ -95,15 +98,17 @@ public struct HomeDiaryEditStore { var dateString: String { "\(schedule.startDate.toYMDEHM()) \n - \(schedule.endDate.toYMDEHM())" } var placeName: String { schedule.locationInfo?.locationName ?? "" } - /// 기록 + // 기록 var initialDiary: Diary = Diary() var diary: Diary = Diary() /// 본문 조건 적합 체크 var isContentValid: Bool = true + // 이미지 var selectedItems: [PhotosPickerItem] = [] var initialImages: [Data] = [] var selectedImages: [Data] = [] + var deletedImages: [Int] = [] /// 저장 버튼 상태 var saveButtonState: NamoButton.NamoButtonType { return isChanged ? .active : .inactive @@ -130,6 +135,7 @@ public struct HomeDiaryEditStore { case tapDeleteDiaryButton case tapSaveDiaryButton case handleAlertConfirm + case updateInitialValues case dismiss case onAppear case showToast(Toast) @@ -141,6 +147,10 @@ public struct HomeDiaryEditStore { case postDiaryImages([UIImage]) case addPostedDiaryImages([DiaryImage]) case postDiary + case patchDiaryImages([UIImage]) + case addPatchedDiaryImages([DiaryImage]) + case patchDiary + case deleteDiary } public var body: some ReducerOf { @@ -186,7 +196,12 @@ public struct HomeDiaryEditStore { return .send(.showToast(.addImageFailed)) case .deleteImage(let index): - state.selectedImages.remove(at: index) + let deletedItem = state.selectedImages.remove(at: index) + // 기존에 로드한 이미지를 삭제하는 경우 + if let initialIndex = state.initialImages.firstIndex(where: { $0 == deletedItem }) { + state.diary.images.remove(at: initialIndex) + state.deletedImages.append(initialIndex) + } return .none case .tapBackButton: @@ -199,19 +214,39 @@ public struct HomeDiaryEditStore { case .tapSaveDiaryButton: guard state.saveButtonState == .active else { return .none } - let images: [UIImage] = state.selectedImages.compactMap { UIImage(data: $0) } + + let imgChanged = state.initialImages != state.selectedImages - return images.count > 0 - ? .send(.postDiaryImages(images)) - : .send(.postDiary) + if state.isRevise { + if imgChanged { + let changedImages = state.selectedImages + .filter { !state.initialImages.contains($0) } + .compactMap { UIImage(data: $0) } + return .send(.patchDiaryImages(changedImages)) + } else { + return .send(.patchDiary) + } + } else { + if imgChanged { + let images: [UIImage] = state.selectedImages + .compactMap { UIImage(data: $0) } + return .send(.postDiaryImages(images)) + } else { + return .send(.postDiary) + } + } + case .updateInitialValues: + state.initialDiary = state.diary + state.initialImages = state.selectedImages + return .none + case .handleAlertConfirm: switch state.alertContent { case .deleteDiary: - print("삭제 api 호출") state.alertContent = .none - return .none + return .send(.deleteDiary) case .backWithoutSave, .loadFailed: state.alertContent = .none return .send(.dismiss) @@ -257,7 +292,7 @@ public struct HomeDiaryEditStore { case .loadDiaryImages(let diary): return .run { send in do { - let updatedImages = try await withThrowingTaskGroup(of: Data.self) { group in + let updatedImages = try await withThrowingTaskGroup(of: (Int,Data).self) { group in for diaryImage in diary.images { group.addTask { @@ -276,17 +311,21 @@ public struct HomeDiaryEditStore { } } } - return data + return (diaryImage.orderNumber, data) } } - var images: [Data] = [] - for try await image in group { - images.append(image) + var imagesDict: [Int: Data] = [:] + for try await (orderNumber, data) in group { + imagesDict[orderNumber] = data } - return images + + let sortedImages = imagesDict.keys.sorted().compactMap { imagesDict[$0] } + return sortedImages + } + // 최종 updatedImages 형태는 orderNumber대로 정렬된 imgData for image in updatedImages { await send(.addImage(.success(image))) } @@ -329,12 +368,69 @@ public struct HomeDiaryEditStore { ] send in do { try await diaryUseCase.postDiary(scheduleId: id, reqDiary: diary) + await send(.updateInitialValues) await send(.showToast(.saveSuccess)) } catch { await send(.showToast(.saveFailed)) } } + + case .patchDiaryImages(let imgs): + return .run { [id = state.schedule.scheduleId] send in + do { + let diaryImgs = try await diaryUseCase.postDiaryImages(scheduleId: id, images: imgs) + await send(.addPatchedDiaryImages(diaryImgs)) + } catch { + print(error.localizedDescription) + await send(.showToast(.uploadImageFailed)) + } + } + + case .addPatchedDiaryImages(let diaryImgs): + + let sortedDiaryImgs = diaryImgs.sorted { $0.orderNumber < $1.orderNumber } + state.diary.images.append(contentsOf: sortedDiaryImgs) + // 기존 이미지 순서 + 새로운 이미지 orderNum 순서 차례대로 새롭게 orderNum 배정 + state.diary.images = state.diary.images.enumerated().map { index, img in + var updatedImg = img + updatedImg.orderNumber = index + return updatedImg + } + + return .send(.patchDiary) + + case .patchDiary: + return .run { [diary = state.diary, deleted = state.deletedImages] send in + + do { + guard let id = diary.id else { + throw NSError.init(domain: "diaryId is nil", code: 1001) + } + + try await diaryUseCase.patchDiary(id: id, reqDiary: diary, deleteImages: deleted) + await send(.updateInitialValues) + await send(.showToast(.saveSuccess)) + } + catch { + await send(.showToast(.saveFailed)) + } + + } + + case .deleteDiary: + return .run { [diary = state.diary] send in + do { + guard let id = diary.id else { + throw NSError.init(domain: "diaryId is nil", code: 1001) + } + try await diaryUseCase.deleteDiary(id: id) + await send(.dismiss) + } + catch { + await send(.showToast(.deleteFailed)) + } + } } } } From d4bdee10d5d5e3babb6a38df0b2f243950bba5a5 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:15:12 +0900 Subject: [PATCH 26/26] =?UTF-8?q?Chore:=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20+=20extension=20=EC=9C=84=EC=B9=98=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Diary/Sources/UseCase/DiaryUseCase.swift | 7 +- .../RecordEdit/HomeDiaryEditStore.swift | 113 +++++++++--------- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift index 960a5006..6d7eb2c2 100644 --- a/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift +++ b/Namo_SwiftUI/Projects/Domain/Diary/Sources/UseCase/DiaryUseCase.swift @@ -68,6 +68,7 @@ public struct DiaryUseCase { } } + /// SchduleId를 통해 해당 일정의 기록을 가져옵니다 public func getDiaryBySchedule(id: Int) async throws -> Diary { let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.getDiaryBySchedule(id: id)) @@ -82,6 +83,7 @@ public struct DiaryUseCase { return diary } + /// 해당 일정의 기록을 수정합니다 public func patchDiary(id: Int, reqDiary: Diary, deleteImages: [Int]) async throws -> Void { let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.patchDiary(id: id, reqDto: reqDiary.toPatchDTO(deleteImages: deleteImages))) @@ -90,6 +92,7 @@ public struct DiaryUseCase { } } + /// 해당 ScheduleId의 일정의 기록을 추가합니다 public func postDiary(scheduleId: Int, reqDiary: Diary) async throws -> Void { let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.postDiary(reqDto: reqDiary.toPostDTO(scheduleId: scheduleId))) @@ -98,6 +101,7 @@ public struct DiaryUseCase { } } + /// 이미지 파일들을 S3에 업로드 후, 순서를 맞춰 DiaryImage 배열로 반환합니다. public func postDiaryImages(scheduleId: Int, images: [UIImage]) async throws -> [DiaryImage] { let compImgs = try images.map { image in guard let compressedData = image.jpegData(compressionQuality: 0.6) else { @@ -133,7 +137,8 @@ public struct DiaryUseCase { return uploadedImages } } - + + /// 해당 일정 기록을 삭제합니다. public func deleteDiary(id: Int) async throws -> Void { let response: BaseResponse = try await APIManager.shared.performRequest(endPoint: DiaryEndPoint.deleteDiary(id: id)) diff --git a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift index 461dffbc..fac759ab 100644 --- a/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift +++ b/Namo_SwiftUI/Projects/Feature/Home/Sources/RecordEdit/HomeDiaryEditStore.swift @@ -14,63 +14,6 @@ import SharedUtil import DomainDiary import Kingfisher -extension HomeDiaryEditStore { - public enum Toast: Equatable { - case none - case saveSuccess - case saveFailed - case addImageFailed - case uploadImageFailed - case deleteFailed - case custom(String) - - var content: String { - switch self { - - case .none: - return "" - case .saveSuccess: - return "기록이 저장되었습니다." - case .saveFailed: - return "기록 저장에 실패했습니다.\n다시 시도해주세요." - case .addImageFailed: - return "사진 추가에 실패했습니다.\n다시 시도해주세요." - case .uploadImageFailed: - return "사진 업로드에 실패했습니다.\n다시 시도해주세요." - case .deleteFailed: - return "기록 삭제에 실패했습니다.\n다시 시도해주세요." - case .custom(let content): - return content - } - } - } -} - -extension HomeDiaryEditStore { - public enum AlertType: Equatable { - case none - case deleteDiary - case backWithoutSave - case loadFailed - case custom(NamoAlertContent) - - public var content: NamoAlertContent { - switch self { - case .none: - return NamoAlertContent() - case .deleteDiary: - return NamoAlertContent(title: "기록을 정말 삭제하시겠어요?") - case .backWithoutSave: - return NamoAlertContent(title: "편집된 내용이 저장되지 않습니다.", message: "정말 나가시겠어요?") - case .loadFailed: - return NamoAlertContent(title: "기록 불러오기에 실패했습니다.", message: "다시 시도해주세요.") - case .custom(let content): - return NamoAlertContent(title: content.title, message: content.title) - } - } - } -} - @Reducer public struct HomeDiaryEditStore { @@ -436,4 +379,60 @@ public struct HomeDiaryEditStore { } } +extension HomeDiaryEditStore { + public enum Toast: Equatable { + case none + case saveSuccess + case saveFailed + case addImageFailed + case uploadImageFailed + case deleteFailed + case custom(String) + + var content: String { + switch self { + + case .none: + return "" + case .saveSuccess: + return "기록이 저장되었습니다." + case .saveFailed: + return "기록 저장에 실패했습니다.\n다시 시도해주세요." + case .addImageFailed: + return "사진 추가에 실패했습니다.\n다시 시도해주세요." + case .uploadImageFailed: + return "사진 업로드에 실패했습니다.\n다시 시도해주세요." + case .deleteFailed: + return "기록 삭제에 실패했습니다.\n다시 시도해주세요." + case .custom(let content): + return content + } + } + } +} + +extension HomeDiaryEditStore { + public enum AlertType: Equatable { + case none + case deleteDiary + case backWithoutSave + case loadFailed + case custom(NamoAlertContent) + + public var content: NamoAlertContent { + switch self { + case .none: + return NamoAlertContent() + case .deleteDiary: + return NamoAlertContent(title: "기록을 정말 삭제하시겠어요?") + case .backWithoutSave: + return NamoAlertContent(title: "편집된 내용이 저장되지 않습니다.", message: "정말 나가시겠어요?") + case .loadFailed: + return NamoAlertContent(title: "기록 불러오기에 실패했습니다.", message: "다시 시도해주세요.") + case .custom(let content): + return NamoAlertContent(title: content.title, message: content.title) + } + } + } +}