From e6f8c3a1f49e74b9779963332203896395d9631b Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Tue, 29 Aug 2023 13:26:19 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat=20#84:=20=EC=8A=A4=EC=BD=94=EC=96=B4?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=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 --- .../GoogleService-Info.plist.encrypted | Bin 1136 -> 1104 bytes .../Sources/MyPage/MyPageFeature.swift | 14 +++- .../Features/Sources/MyPage/MyPageView.swift | 7 ++ .../MyPage/ScoreList/ScoreListView.swift | 28 +++++-- Projects/Features/Sources/Root/RootView.swift | 76 +++++++++--------- 5 files changed, 78 insertions(+), 47 deletions(-) diff --git a/Encrypted/Secrets/GoogleService-Info.plist.encrypted b/Encrypted/Secrets/GoogleService-Info.plist.encrypted index 58ede7d001cfafc39c7cc8ab647682ca495f8654..dbe4cb732ff1ca1e93aa85b1713bd24bea66e0ca 100644 GIT binary patch delta 910 zcmV;919AND2+#{Z z|HRnX>JDy2`Vh}tG66C1F>bQZ1$6@sGEq+-*$w6j9l)uOLLxT?>+jKjRZ$8ur;hDotqED;&CGzW*9hOh_@l(FY5!frsxewvWZ z!wwaG=*SmIO8+sFXSyc3ll~!Pr3by1uy^RQ=vFzP%YSDRC+f_E*MgDB8O*Y5ifRUL z`pD$pFG7b*ReBXItx2^%6?zvXFO@3kD~nr{)Jm>rVmjVGL=6sBm!R{5Hc{WoQKI23 z=sN%eFIyLI4peX3NZE(c1NYM!O^PYlek$G6edcWv@cw(wH_cyQkp;4-;~nr^)yP3Q zARFgHCx3|z47Y+rkE}*Uw~#A*K=#@clCb@L6)9Epb$h(GGNOe5r#`f+Mz!k-Vm9Hr zFZl!j3@%yCNA(JnY`qYlS`-EIimA&IO2aXjUSX^ZhpmO~1@YNcLYeoz;8r<43~?_` z^L^_lv`C)>QLEi3Bu-GpCkk3XquC@dT?3O_`G2RqN4UtbE!dzT47xtnhSxqF-!)&0Av-@h5}csr&jJ5dj%HuW%4LJ#3QbWKbVJ;Wxw8y5N3-B)8Abe4 zC@F8Uh}FxHtCFYLm&y$iZG6(`{Jgk5V*o7vJsIp#$~IZbKlKFd95u%WM(Ozyy|ZIM zUVk4wn+PA$D?JR?^^*Q1a_#I#1pqnEE_Krn4(`wIJhB_dzMw}1C^b{3FPLT7T|wOV6O z3xMs)8k+x53px32IC19*{^m|q~5vQHB~0)@iUdx-VX?xnLu5g zEH%~P%!t*(Rnnm`_9-zzb8h(S6zOo;D6iS#&#DkA3E=@T}e>^fwT9@ zpwea;(3hN>g^%ZCXq=O^2kmbP)Kh?vG`PAQo%JU4_{j-blCst#tZhh|%_}k4m}gml z9lRCDn~ctd5Vgs~0_?>=fjs{^lth#1+FLt3k=gB^U>&R8M6VsgWIJUiP-R^SHUHUuT`p_hmwztB?fML zUVs1j0PHs7e4orm!2dEJ%le2@(N1R8rIw^VoA@0@C{EtjoBl>mY10blHOYMc@Jwph z7$BliN*3kD&-m+%99_+=$^Sf&qmFS{`o{om7zEb3e~a4i0w2`tUfxe-nSrK>BN6(1 z@9cv{5;lM@#yPmhS%;btXbXi!ROJV7n|+dV=w7lVj3sbmhC>2LJ#7 diff --git a/Projects/Features/Sources/MyPage/MyPageFeature.swift b/Projects/Features/Sources/MyPage/MyPageFeature.swift index 988a075b..cdecc8aa 100644 --- a/Projects/Features/Sources/MyPage/MyPageFeature.swift +++ b/Projects/Features/Sources/MyPage/MyPageFeature.swift @@ -31,6 +31,8 @@ struct MyPageFeature: Reducer { var differentCircleDataList: [CircleData] = [] var shownCircleDatalist: [CircleData] = [] var circleShown = false + + var scoreListState: ScoreListFeature.State = .init() } enum Action: Equatable { case selectSegement(MyPageSegment) @@ -39,10 +41,16 @@ struct MyPageFeature: Reducer { case markViewAsShown case circleTapped case circleDismissed + + case scoreListAction(ScoreListFeature.Action) } public var body: some ReducerOf { - Reduce { state, action in + Scope(state: \.scoreListState, action: /Action.scoreListAction) { + ScoreListFeature() + } + + Reduce { state, action in switch action { case .selectSegement(let segment): state.selectedSegment = segment @@ -108,6 +116,10 @@ struct MyPageFeature: Reducer { case .circleDismissed: state.circleShown = false return .none + + case .scoreListAction: + print("score") + return .none } } } diff --git a/Projects/Features/Sources/MyPage/MyPageView.swift b/Projects/Features/Sources/MyPage/MyPageView.swift index 4b0b8be8..35fa9892 100644 --- a/Projects/Features/Sources/MyPage/MyPageView.swift +++ b/Projects/Features/Sources/MyPage/MyPageView.swift @@ -39,10 +39,17 @@ struct MyPageView: View { namespace: namespace, data: viewStore.shownCircleDatalist, detailViewBuilder: { data in + let scoreListStore = store.scope( + state: \.scoreListState, + action: MyPageFeature.Action.scoreListAction) + ScoreListView( nickname: "키미", keyword: data.metadata.keyword, store: scoreListStore) + .onAppear { + scoreListStore.send(.loadScores) + } }) .graphBackgroundColor(DSKitAsset.Color.keymeBlack.swiftUIColor) .activateCircleBlink(viewStore.state.shownFirstTime) diff --git a/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift b/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift index cf034cc4..04c5f211 100644 --- a/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift +++ b/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift @@ -15,6 +15,7 @@ import DSKit struct ScoreListFeature: Reducer { public struct State: Equatable { + var canFetch = true var totalCount: Int? var scores: [CharacterScore] @@ -32,20 +33,26 @@ struct ScoreListFeature: Reducer { Reduce { state, action in switch action { case .loadScores: + state.canFetch = false + return .run { send in - try await Task.sleep(until: .now + .seconds(0.5), clock: .continuous) await send(.saveScores( - totalCount: 42, - scores: (0..<42).map { i in + totalCount: 120, + scores: (0..<15).map { i in let randomInterval = TimeInterval(-2 * i) - return CharacterScore(score: Int.random(in: 1...5), date: Date().addingTimeInterval(randomInterval + Double(Int.random(in: 0...1)))) + return CharacterScore( + score: Int.random(in: 1...5), + date: Date().addingTimeInterval(randomInterval + Double(Int.random(in: 0...1))) + ) } )) } case .saveScores(let totalCount, let data): state.totalCount = totalCount - state.scores = data + state.scores.append(contentsOf: data) + + state.canFetch = true } return .none } @@ -104,16 +111,21 @@ struct ScoreListView: View { .cornerRadius(16) .onAppear { if - let thirdToLast = viewStore.state.scores.dropLast(2).last, - thirdToLast == scoreData + let thirdToLastItem = viewStore.state.scores.dropLast(2).last, + thirdToLastItem == scoreData { -// viewStore.send(.loadScores) + guard viewStore.canFetch else { return } + viewStore.send(.loadScores) } } } } .padding(.horizontal, 17) } + .onAppear { + guard viewStore.canFetch else { return } + viewStore.send(.loadScores) + } } } diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index a19c81af..0cd01905 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -22,50 +22,50 @@ public struct RootView: View { public var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in - if viewStore.logInStatus == .notDetermined { - // 여기 걸리면 에러임. 조심하셈. - EmptyView() - } else if viewStore.logInStatus == .loggedOut { - // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 - let loginStore = store.scope( - state: \.$logInStatus, - action: RootFeature.Action.login) - - IfLetStore(loginStore) { store in - SignInView(store: store) - } - } else if viewStore.registrationState?.status == .notDetermined { - // 개인정보 등록 상태를 로딩 중 - ProgressView() - } else if viewStore.registrationState?.status == .needsRegister { - // 개인정보 등록 - let registrationStore = store.scope( - state: \.$registrationState, - action: RootFeature.Action.registration) - - IfLetStore(registrationStore) { store in - RegistrationView(store: store) - } - } else if viewStore.onboardingStatus?.status == .notDetermined { - // 온보딩 상태를 로딩 중 - ProgressView() - } else if viewStore.onboardingStatus?.status == .needsOnboarding { - // 가입했지만 온보딩을 하지 않고 종료했던 유저 - let onboardingStore = store.scope( - state: \.$onboardingStatus, - action: RootFeature.Action.onboarding) - - IfLetStore(onboardingStore) { store in - OnboardingView(store: store) - } - } else { +// if viewStore.logInStatus == .notDetermined { +// // 여기 걸리면 에러임. 조심하셈. +// EmptyView() +// } else if viewStore.logInStatus == .loggedOut { +// // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 +// let loginStore = store.scope( +// state: \.$logInStatus, +// action: RootFeature.Action.login) +// +// IfLetStore(loginStore) { store in +// SignInView(store: store) +// } +// } else if viewStore.registrationState?.status == .notDetermined { +// // 개인정보 등록 상태를 로딩 중 +// ProgressView() +// } else if viewStore.registrationState?.status == .needsRegister { +// // 개인정보 등록 +// let registrationStore = store.scope( +// state: \.$registrationState, +// action: RootFeature.Action.registration) +// +// IfLetStore(registrationStore) { store in +// RegistrationView(store: store) +// } +// } else if viewStore.onboardingStatus?.status == .notDetermined { +// // 온보딩 상태를 로딩 중 +// ProgressView() +// } else if viewStore.onboardingStatus?.status == .needsOnboarding { +// // 가입했지만 온보딩을 하지 않고 종료했던 유저 +// let onboardingStore = store.scope( +// state: \.$onboardingStatus, +// action: RootFeature.Action.onboarding) +// +// IfLetStore(onboardingStore) { store in +// OnboardingView(store: store) +// } +// } else { // 가입했고 온보딩을 진행한 유저 KeymeMainView(store: Store( initialState: MainPageFeature.State()) { MainPageFeature() }) .transition(.opacity) - } +// } } } } From 3e818e03ef73147aa8615368567b024e4efd15e9 Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Tue, 29 Aug 2023 17:51:01 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat=20#84:=20=EC=8A=A4=EC=BD=94=EC=96=B4?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/CirclePack/CharacterScore.swift | 9 +++ .../Features/Sources/MyPage/MyPageView.swift | 6 +- .../MyPage/ScoreList/ScoreListView.swift | 36 ++++++----- .../Sources/DTO/QuestionResultScoresDTO.swift | 27 +++++++++ .../Sources/Network/API/KeymeAPI.swift | 7 +++ .../Sources/Network/API/QuestionAPI.swift | 60 +++++++++++++++++++ .../Network/Manager/KeymeAPIManager.swift | 1 + 7 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 Projects/Network/Sources/DTO/QuestionResultScoresDTO.swift create mode 100644 Projects/Network/Sources/Network/API/QuestionAPI.swift diff --git a/Projects/Domain/Sources/Model/CirclePack/CharacterScore.swift b/Projects/Domain/Sources/Model/CirclePack/CharacterScore.swift index f00bacd5..589cda57 100644 --- a/Projects/Domain/Sources/Model/CirclePack/CharacterScore.swift +++ b/Projects/Domain/Sources/Model/CirclePack/CharacterScore.swift @@ -6,6 +6,7 @@ // Copyright © 2023 team.humanwave. All rights reserved. // +import Network import Foundation public struct CharacterScore: Identifiable, Equatable { @@ -18,3 +19,11 @@ public struct CharacterScore: Identifiable, Equatable { self.date = date } } + +public extension QuestionResultScoresDTO { + func toCharacterScores() -> [CharacterScore] { + return self.data.results.map { resultItem in + CharacterScore(score: resultItem.score, date: resultItem.createdAt) + } + } +} diff --git a/Projects/Features/Sources/MyPage/MyPageView.swift b/Projects/Features/Sources/MyPage/MyPageView.swift index 35fa9892..4b4d85b8 100644 --- a/Projects/Features/Sources/MyPage/MyPageView.swift +++ b/Projects/Features/Sources/MyPage/MyPageView.swift @@ -28,8 +28,6 @@ struct MyPageView: View { store.send(.requestCircle(.low5)) store.send(.selectSegement(.similar)) - - scoreListStore.send(.loadScores) } public var body: some View { @@ -44,12 +42,10 @@ struct MyPageView: View { action: MyPageFeature.Action.scoreListAction) ScoreListView( + questionId: data.metadata.questionId, nickname: "키미", keyword: data.metadata.keyword, store: scoreListStore) - .onAppear { - scoreListStore.send(.loadScores) - } }) .graphBackgroundColor(DSKitAsset.Color.keymeBlack.swiftUIColor) .activateCircleBlink(viewStore.state.shownFirstTime) diff --git a/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift b/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift index 04c5f211..bb3069e2 100644 --- a/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift +++ b/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift @@ -12,8 +12,11 @@ import SwiftUI import ComposableArchitecture import Domain import DSKit +import Network struct ScoreListFeature: Reducer { + @Dependency(\.keymeAPIManager) private var network + public struct State: Equatable { var canFetch = true var totalCount: Int? @@ -25,27 +28,24 @@ struct ScoreListFeature: Reducer { } public enum Action: Equatable { - case loadScores + case loadScores(questionId: Int) case saveScores(totalCount: Int, scores: [CharacterScore]) } public var body: some ReducerOf { Reduce { state, action in switch action { - case .loadScores: + case .loadScores(let id): state.canFetch = false return .run { send in + let questionScores = try await network.request( + .question(.scores(questionId: id)), object: QuestionResultScoresDTO.self) + .toCharacterScores() + await send(.saveScores( - totalCount: 120, - scores: (0..<15).map { i in - let randomInterval = TimeInterval(-2 * i) - return CharacterScore( - score: Int.random(in: 1...5), - date: Date().addingTimeInterval(randomInterval + Double(Int.random(in: 0...1))) - ) - } - )) + totalCount: questionScores.count, + scores: questionScores)) } case .saveScores(let totalCount, let data): @@ -61,15 +61,23 @@ struct ScoreListFeature: Reducer { struct ScoreListView: View { private let formatter: RelativeDateTimeFormatter + + private let questionId: Int private let nickname: String private let keyword: String private let store: StoreOf - init(nickname: String, keyword: String, store: StoreOf) { + init( + questionId: Int, + nickname: String, + keyword: String, + store: StoreOf + ) { self.formatter = RelativeDateTimeFormatter() formatter.locale = Locale(identifier: "ko_KR") formatter.dateTimeStyle = .named + self.questionId = questionId self.nickname = nickname self.keyword = keyword self.store = store @@ -115,7 +123,7 @@ struct ScoreListView: View { thirdToLastItem == scoreData { guard viewStore.canFetch else { return } - viewStore.send(.loadScores) + viewStore.send(.loadScores(questionId: self.questionId)) } } } @@ -124,7 +132,7 @@ struct ScoreListView: View { } .onAppear { guard viewStore.canFetch else { return } - viewStore.send(.loadScores) + viewStore.send(.loadScores(questionId: self.questionId)) } } } diff --git a/Projects/Network/Sources/DTO/QuestionResultScoresDTO.swift b/Projects/Network/Sources/DTO/QuestionResultScoresDTO.swift new file mode 100644 index 00000000..64ef5cab --- /dev/null +++ b/Projects/Network/Sources/DTO/QuestionResultScoresDTO.swift @@ -0,0 +1,27 @@ +// +// QuestionResultScoresDTO.swift +// Network +// +// Created by 이영빈 on 2023/08/29. +// Copyright © 2023 team.humanwave. All rights reserved. +// + +import Foundation + +public struct QuestionResultScoresDTO: Decodable { + let code: Int + public let data: DataResponse + let message: String + + public struct DataResponse: Decodable { + public let hasNext: Bool + public let results: [ResultItem] + public let totalCount: Int + } + + public struct ResultItem: Decodable { + public let createdAt: Date + public let id: Int + public let score: Int + } +} diff --git a/Projects/Network/Sources/Network/API/KeymeAPI.swift b/Projects/Network/Sources/Network/API/KeymeAPI.swift index d69fd5a0..9a59e201 100644 --- a/Projects/Network/Sources/Network/API/KeymeAPI.swift +++ b/Projects/Network/Sources/Network/API/KeymeAPI.swift @@ -16,6 +16,7 @@ public enum KeymeAPI { case registration(RegistrationAPI) case member(MemberAPI) case test(KeymeTestsAPI) + case question(QuestionAPI) } extension KeymeAPI: BaseAPI { @@ -33,6 +34,8 @@ extension KeymeAPI: BaseAPI { return api.path case .test(let api): return api.path + case .question(let api): + return api.path } } @@ -50,6 +53,8 @@ extension KeymeAPI: BaseAPI { return api.method case .test(let api): return api.method + case .question(let api): + return api.method } } @@ -67,6 +72,8 @@ extension KeymeAPI: BaseAPI { return api.task case .test(let api): return api.task + case .question(let api): + return api.task } } diff --git a/Projects/Network/Sources/Network/API/QuestionAPI.swift b/Projects/Network/Sources/Network/API/QuestionAPI.swift new file mode 100644 index 00000000..839734e0 --- /dev/null +++ b/Projects/Network/Sources/Network/API/QuestionAPI.swift @@ -0,0 +1,60 @@ +// +// QuestionAPI.swift +// Network +// +// Created by 이영빈 on 2023/08/29. +// Copyright © 2023 team.humanwave. All rights reserved. +// + +import Moya +import Foundation + +public enum QuestionAPI { + case scores(questionId: Int) +} + +extension QuestionAPI: BaseAPI { + public var path: String { + switch self { + case .scores(let questionId): + return "questions/\(questionId)/result/scores" + } + } + + public var method: Moya.Method { + switch self { + case .scores: + return .get + } + } + + public var sampleData: Data { + switch self { + case .scores: + return """ + { + "code": 200, + "data": { + "hasNext": true, + "results": [ + { + "createdAt": "2023-08-29T04:30:18.366Z", + "id": 0, + "score": 0 + } + ], + "totalCount": 0 + }, + "message": "SUCCESS" + } + """.data(using: .utf8)! + } + } + + public var task: Task { + switch self { + case .scores: + return .requestPlain + } + } +} diff --git a/Projects/Network/Sources/Network/Manager/KeymeAPIManager.swift b/Projects/Network/Sources/Network/Manager/KeymeAPIManager.swift index 9cf8f4c5..8ebfbe38 100644 --- a/Projects/Network/Sources/Network/Manager/KeymeAPIManager.swift +++ b/Projects/Network/Sources/Network/Manager/KeymeAPIManager.swift @@ -27,6 +27,7 @@ public class KeymeAPIManager { init(core: CoreNetworkService) { self.core = core + decoder.dateDecodingStrategy = .iso8601 } } From 3dc955911bf2a500daa58c44f4ecbfc553ccee33 Mon Sep 17 00:00:00 2001 From: YoungBin Lee Date: Wed, 30 Aug 2023 08:06:48 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix=20#84:=20=EC=8A=A4=EC=BD=94=EC=96=B4=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20API=20=EB=94=94=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MyPage/MyPageFeature.swift | 17 ++++- .../Features/Sources/MyPage/MyPageView.swift | 24 +++---- .../MyPage/ScoreList/ScoreListFeature.swift | 51 +++++++++++++++ .../MyPage/ScoreList/ScoreListView.swift | 63 +++++-------------- .../Sources/Network/API/QuestionAPI.swift | 13 ++-- 5 files changed, 102 insertions(+), 66 deletions(-) diff --git a/Projects/Features/Sources/MyPage/MyPageFeature.swift b/Projects/Features/Sources/MyPage/MyPageFeature.swift index cdecc8aa..c022885a 100644 --- a/Projects/Features/Sources/MyPage/MyPageFeature.swift +++ b/Projects/Features/Sources/MyPage/MyPageFeature.swift @@ -6,6 +6,7 @@ // Copyright © 2023 team.humanwave. All rights reserved. // +import Core import ComposableArchitecture import Domain import DSKit @@ -22,7 +23,6 @@ struct Coordinate { struct MyPageFeature: Reducer { @Dependency(\.keymeAPIManager) private var network - @Dependency(\.userStorage) private var userStorage struct State: Equatable { var selectedSegment: MyPageSegment = .similar @@ -33,7 +33,17 @@ struct MyPageFeature: Reducer { var circleShown = false var scoreListState: ScoreListFeature.State = .init() + + @BindingState var userId = { + @Dependency(\.userStorage.userId) var userId + return userId + }() + @BindingState var nickname = { + @Dependency(\.userStorage.nickname) var nickname + return nickname + }() } + enum Action: Equatable { case selectSegement(MyPageSegment) case requestCircle(MatchRate) @@ -67,7 +77,7 @@ struct MyPageFeature: Reducer { case .requestCircle(let rate): switch rate { case .top5: - guard let userId = userStorage.userId else { + guard let userId = state.userId else { // TODO: Throw return .none } @@ -81,7 +91,7 @@ struct MyPageFeature: Reducer { } case .low5: - guard let userId = userStorage.userId else { + guard let userId = state.userId else { // TODO: Throw return .none } @@ -110,6 +120,7 @@ struct MyPageFeature: Reducer { return .none case .circleTapped: + HapticManager.shared.tok() state.circleShown = true return .none diff --git a/Projects/Features/Sources/MyPage/MyPageView.swift b/Projects/Features/Sources/MyPage/MyPageView.swift index 4b4d85b8..96d39f6b 100644 --- a/Projects/Features/Sources/MyPage/MyPageView.swift +++ b/Projects/Features/Sources/MyPage/MyPageView.swift @@ -7,7 +7,6 @@ // import ComposableArchitecture -import Core import Domain import DSKit import SwiftUI @@ -16,13 +15,9 @@ struct MyPageView: View { @Namespace private var namespace private let store: StoreOf - private let scoreListStore: StoreOf init(store: StoreOf) { self.store = store - self.scoreListStore = Store(initialState: ScoreListFeature.State(), reducer: { - ScoreListFeature() - }) store.send(.requestCircle(.top5)) store.send(.requestCircle(.low5)) @@ -41,17 +36,24 @@ struct MyPageView: View { state: \.scoreListState, action: MyPageFeature.Action.scoreListAction) - ScoreListView( - questionId: data.metadata.questionId, - nickname: "키미", - keyword: data.metadata.keyword, - store: scoreListStore) + if + let userId = viewStore.userId, + let nickname = viewStore.nickname + { + ScoreListView( + ownerId: userId, + questionId: data.metadata.questionId, + nickname: nickname, + keyword: data.metadata.keyword, + store: scoreListStore) + } else { + // TODO: Show alert + } }) .graphBackgroundColor(DSKitAsset.Color.keymeBlack.swiftUIColor) .activateCircleBlink(viewStore.state.shownFirstTime) .onCircleTapped { _ in viewStore.send(.circleTapped) - HapticManager.shared.tok() } .onCircleDismissed { _ in withAnimation(Animation.customInteractiveSpring()) { diff --git a/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift b/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift index ec1b1f39..ff3c7dd6 100644 --- a/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift +++ b/Projects/Features/Sources/MyPage/ScoreList/ScoreListFeature.swift @@ -7,3 +7,54 @@ // import Foundation +import ComposableArchitecture +import Domain +import Network + +struct ScoreListFeature: Reducer { + @Dependency(\.keymeAPIManager) private var network + + public struct State: Equatable { + var canFetch = true + var totalCount: Int? + var scores: [CharacterScore] + + public init(totalCount: Int? = nil, scores: [CharacterScore] = []) { + self.scores = [] + } + } + + public enum Action: Equatable { + case loadScores(ownerId: Int, questionId: Int, limit: Int) + case saveScores(totalCount: Int, scores: [CharacterScore]) + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case let .loadScores(ownerId, questionId, limit): + state.canFetch = false + + return .run { send in + let questionScores = try await network.request( + .question( + .scores(ownerId: ownerId, questionId: questionId, limit: limit) + ), + object: QuestionResultScoresDTO.self + ).toCharacterScores() + + await send(.saveScores( + totalCount: questionScores.count, + scores: questionScores)) + } + + case let .saveScores(totalCount, data): + state.totalCount = totalCount + state.scores.append(contentsOf: data) + + state.canFetch = true + } + return .none + } + } +} diff --git a/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift b/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift index bb3069e2..5cf6d385 100644 --- a/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift +++ b/Projects/Features/Sources/MyPage/ScoreList/ScoreListView.swift @@ -12,62 +12,20 @@ import SwiftUI import ComposableArchitecture import Domain import DSKit -import Network - -struct ScoreListFeature: Reducer { - @Dependency(\.keymeAPIManager) private var network - - public struct State: Equatable { - var canFetch = true - var totalCount: Int? - var scores: [CharacterScore] - - public init(totalCount: Int? = nil, scores: [CharacterScore] = []) { - self.scores = [] - } - } - - public enum Action: Equatable { - case loadScores(questionId: Int) - case saveScores(totalCount: Int, scores: [CharacterScore]) - } - - public var body: some ReducerOf { - Reduce { state, action in - switch action { - case .loadScores(let id): - state.canFetch = false - - return .run { send in - let questionScores = try await network.request( - .question(.scores(questionId: id)), object: QuestionResultScoresDTO.self) - .toCharacterScores() - - await send(.saveScores( - totalCount: questionScores.count, - scores: questionScores)) - } - - case .saveScores(let totalCount, let data): - state.totalCount = totalCount - state.scores.append(contentsOf: data) - - state.canFetch = true - } - return .none - } - } -} struct ScoreListView: View { + private let scoreFetchLimit = 20 + private let formatter: RelativeDateTimeFormatter + private let ownerId: Int private let questionId: Int private let nickname: String private let keyword: String private let store: StoreOf init( + ownerId: Int, questionId: Int, nickname: String, keyword: String, @@ -77,6 +35,7 @@ struct ScoreListView: View { formatter.locale = Locale(identifier: "ko_KR") formatter.dateTimeStyle = .named + self.ownerId = ownerId self.questionId = questionId self.nickname = nickname self.keyword = keyword @@ -123,7 +82,11 @@ struct ScoreListView: View { thirdToLastItem == scoreData { guard viewStore.canFetch else { return } - viewStore.send(.loadScores(questionId: self.questionId)) + viewStore.send( + .loadScores( + ownerId: self.ownerId, + questionId: self.questionId, + limit: scoreFetchLimit)) } } } @@ -132,7 +95,11 @@ struct ScoreListView: View { } .onAppear { guard viewStore.canFetch else { return } - viewStore.send(.loadScores(questionId: self.questionId)) + viewStore.send( + .loadScores( + ownerId: self.ownerId, + questionId: self.questionId, + limit: scoreFetchLimit)) } } } diff --git a/Projects/Network/Sources/Network/API/QuestionAPI.swift b/Projects/Network/Sources/Network/API/QuestionAPI.swift index 839734e0..1dde1ac9 100644 --- a/Projects/Network/Sources/Network/API/QuestionAPI.swift +++ b/Projects/Network/Sources/Network/API/QuestionAPI.swift @@ -10,13 +10,13 @@ import Moya import Foundation public enum QuestionAPI { - case scores(questionId: Int) + case scores(ownerId: Int, questionId: Int, limit: Int) } extension QuestionAPI: BaseAPI { public var path: String { switch self { - case .scores(let questionId): + case .scores(_, let questionId, _): return "questions/\(questionId)/result/scores" } } @@ -53,8 +53,13 @@ extension QuestionAPI: BaseAPI { public var task: Task { switch self { - case .scores: - return .requestPlain + case let .scores(ownerId, _, limit): + return .requestParameters( + parameters: [ + "limit": limit, + "ownerId": ownerId + ], + encoding: URLEncoding.queryString) } } } From 41d4ef8a3a4e84cebf79e12679005255351cac58 Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Wed, 30 Aug 2023 10:46:11 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix=20#84:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EA=B5=AC=EC=A1=B0=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Home/KeymeTestsStartFeature.swift | 11 ++++--- .../Sources/MainPage/MainPageFeature.swift | 15 +++++++-- .../Sources/MainPage/MainPageView.swift | 31 ++++++++++--------- .../Sources/MyPage/MyPageFeature.swift | 31 ++++++++----------- .../Features/Sources/MyPage/MyPageView.swift | 21 +++++-------- .../Features/Sources/Root/RootFeature.swift | 18 +++++++++-- Projects/Features/Sources/Root/RootView.swift | 13 +++++--- 7 files changed, 78 insertions(+), 62 deletions(-) diff --git a/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift b/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift index 98daceff..374ba19f 100644 --- a/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift +++ b/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift @@ -13,13 +13,16 @@ import Domain public struct KeymeTestsStartFeature: Reducer { public struct State: Equatable { + public let nickname: String + public var testId: Int? + public var keymeTests: KeymeTestsFeature.State? public var isAnimating: Bool = false - public var nickname: String? - public var testId: Int = 18 // TODO: change public var icon: IconModel = .EMPTY - public init() { } + public init(nickname: String) { + self.nickname = nickname + } } public enum Action { @@ -49,14 +52,12 @@ public struct KeymeTestsStartFeature: Reducer { } case let .fetchDailyTests(.success(tests)): - state.nickname = tests.nickname state.testId = tests.testId state.isAnimating = true return .send(.startAnimation(tests.icons)) case .fetchDailyTests(.failure): - state.nickname = "키미" // TODO: 변경 return .send(.startAnimation([IconModel.EMPTY])) case .startAnimation(let icons): diff --git a/Projects/Features/Sources/MainPage/MainPageFeature.swift b/Projects/Features/Sources/MainPage/MainPageFeature.swift index 3d401171..80fd4e0b 100644 --- a/Projects/Features/Sources/MainPage/MainPageFeature.swift +++ b/Projects/Features/Sources/MainPage/MainPageFeature.swift @@ -11,13 +11,22 @@ import ComposableArchitecture public struct MainPageFeature: Reducer { public struct State: Equatable { - public init() { } + let userId: Int + let nickname: String + + public init(userId: Int, nickname: String) { + self.userId = userId + self.nickname = nickname + } } - public enum Action { } + public enum Action { + case logout + case changeNickname(String) + } public var body: some Reducer { - Reduce { _, action in + Reduce { _, _ in return .none } } diff --git a/Projects/Features/Sources/MainPage/MainPageView.swift b/Projects/Features/Sources/MainPage/MainPageView.swift index 1218bbcf..5b3de75b 100644 --- a/Projects/Features/Sources/MainPage/MainPageView.swift +++ b/Projects/Features/Sources/MainPage/MainPageView.swift @@ -20,19 +20,17 @@ struct KeymeMainView: View { self.store = store } - private var myPageStore = Store(initialState: MyPageFeature.State()) { - MyPageFeature() - } - enum Tab: Int { case home, myPage } var body: some View { - WithViewStore(store, observe: { $0 }) { _ in + WithViewStore(store, observe: { $0 }) { viewStore in TabView(selection: $selectedTab) { KeymeTestsStartView(store: Store( - initialState: KeymeTestsStartFeature.State()) { + initialState: KeymeTestsStartFeature.State( + nickname: viewStore.nickname + )) { KeymeTestsStartFeature() }) .tabItem { @@ -43,14 +41,19 @@ struct KeymeMainView: View { } .tag(Tab.home) - MyPageView(store: myPageStore) - .tabItem { - myPageTabImage - .resizable() - .frame(width: 24, height: 24) - .aspectRatio(contentMode: .fit) - } - .tag(Tab.myPage) + MyPageView(store: Store( + initialState: MyPageFeature.State( + userId: viewStore.state.userId, + nickname: viewStore.state.nickname)) { + MyPageFeature() + }) + .tabItem { + myPageTabImage + .resizable() + .frame(width: 24, height: 24) + .aspectRatio(contentMode: .fit) + } + .tag(Tab.myPage) } .introspect(.tabView, on: .iOS(.v16, .v17)) { tabViewController in let tabBar = tabViewController.tabBar diff --git a/Projects/Features/Sources/MyPage/MyPageFeature.swift b/Projects/Features/Sources/MyPage/MyPageFeature.swift index c022885a..8184a1c2 100644 --- a/Projects/Features/Sources/MyPage/MyPageFeature.swift +++ b/Projects/Features/Sources/MyPage/MyPageFeature.swift @@ -34,14 +34,13 @@ struct MyPageFeature: Reducer { var scoreListState: ScoreListFeature.State = .init() - @BindingState var userId = { - @Dependency(\.userStorage.userId) var userId - return userId - }() - @BindingState var nickname = { - @Dependency(\.userStorage.nickname) var nickname - return nickname - }() + let userId: Int + let nickname: String + + init(userId: Int, nickname: String) { + self.userId = userId + self.nickname = nickname + } } enum Action: Equatable { @@ -55,6 +54,10 @@ struct MyPageFeature: Reducer { case scoreListAction(ScoreListFeature.Action) } + // 마이페이지를 사용할 수 없는 케이스 + // 1. 유저 아이디나 닉네임이 정의되지 않음 -> 에러페이지 + // 2. 원 그래프가 아직 집계되지 않음 -> 빈 화면 페이지 + // 3. 네트워크가 연결되지 않음 public var body: some ReducerOf { Scope(state: \.scoreListState, action: /Action.scoreListAction) { ScoreListFeature() @@ -75,13 +78,10 @@ struct MyPageFeature: Reducer { // 서버 부하가 있으므로 웬만하면 한 번만 콜 할 것 case .requestCircle(let rate): + let userId = state.userId + switch rate { case .top5: - guard let userId = state.userId else { - // TODO: Throw - return .none - } - return .run { send in let response = try await network.request( .myPage(.statistics(userId, .similar)), @@ -91,11 +91,6 @@ struct MyPageFeature: Reducer { } case .low5: - guard let userId = state.userId else { - // TODO: Throw - return .none - } - return .run { send in let response = try await network.request( .myPage(.statistics(userId, .different)), diff --git a/Projects/Features/Sources/MyPage/MyPageView.swift b/Projects/Features/Sources/MyPage/MyPageView.swift index 96d39f6b..4706c8d6 100644 --- a/Projects/Features/Sources/MyPage/MyPageView.swift +++ b/Projects/Features/Sources/MyPage/MyPageView.swift @@ -36,19 +36,12 @@ struct MyPageView: View { state: \.scoreListState, action: MyPageFeature.Action.scoreListAction) - if - let userId = viewStore.userId, - let nickname = viewStore.nickname - { - ScoreListView( - ownerId: userId, - questionId: data.metadata.questionId, - nickname: nickname, - keyword: data.metadata.keyword, - store: scoreListStore) - } else { - // TODO: Show alert - } + ScoreListView( + ownerId: viewStore.userId, + questionId: data.metadata.questionId, + nickname: viewStore.nickname, + keyword: data.metadata.keyword, + store: scoreListStore) }) .graphBackgroundColor(DSKitAsset.Color.keymeBlack.swiftUIColor) .activateCircleBlink(viewStore.state.shownFirstTime) @@ -89,7 +82,7 @@ struct MyPageView: View { .padding(.horizontal, 17) .padding(.top, 25) - Text.keyme("친구들이 생각하는\n키미님의 성격은?", font: .heading1) // TODO: Change nickname + Text.keyme("친구들이 생각하는\n\(viewStore.nickname)님의 성격은?", font: .heading1) .padding(17) .transition(.opacity) } diff --git a/Projects/Features/Sources/Root/RootFeature.swift b/Projects/Features/Sources/Root/RootFeature.swift index 999380e3..ea4e001d 100644 --- a/Projects/Features/Sources/Root/RootFeature.swift +++ b/Projects/Features/Sources/Root/RootFeature.swift @@ -24,6 +24,7 @@ public struct RootFeature: Reducer { @PresentationState public var logInStatus: SignInFeature.State? @PresentationState public var registrationState: RegistrationFeature.State? @PresentationState public var onboardingStatus: OnboardingFeature.State? + @PresentationState public var mainPageState: MainPageFeature.State? public init( isLoggedIn: Bool? = nil, @@ -57,10 +58,9 @@ public struct RootFeature: Reducer { case login(PresentationAction) case registration(PresentationAction) case onboarding(PresentationAction) + case mainPage(PresentationAction) case onboardingChecked(TaskResult) - case mainPage(MainPageFeature.Action) - case checkUserStatus case checkLoginStatus @@ -68,6 +68,7 @@ public struct RootFeature: Reducer { case checkOnboardingStatus case updateMemberInformation + case startMainPage(userId: Int, nickname: String) } public var body: some ReducerOf { @@ -173,7 +174,7 @@ public struct RootFeature: Reducer { return .none case .updateMemberInformation: - return .run(priority: .userInitiated) { _ in + return .run(priority: .userInitiated) { send in let memberInformation = try await network.request( .member(.fetch), object: MemberUpdateDTO.self).data @@ -202,8 +203,16 @@ public struct RootFeature: Reducer { _ = try await network.request(.registerPushToken(.register(token))) } + + await send(.startMainPage( + userId: memberInformation.id, + nickname: memberInformation.nickname)) } + case .startMainPage(let userId, let nickname): + state.mainPageState = MainPageFeature.State(userId: userId, nickname: nickname) + return .none + default: return .none } @@ -217,5 +226,8 @@ public struct RootFeature: Reducer { .ifLet(\.$onboardingStatus, action: /Action.onboarding) { OnboardingFeature() } + .ifLet(\.$mainPageState, action: /Action.mainPage) { + MainPageFeature() + } } } diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index 0cd01905..455aa770 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -60,11 +60,14 @@ public struct RootView: View { // } // } else { // 가입했고 온보딩을 진행한 유저 - KeymeMainView(store: Store( - initialState: MainPageFeature.State()) { - MainPageFeature() - }) - .transition(.opacity) + let mainPageStore = store.scope(state: \.$mainPageState, action: RootFeature.Action.mainPage) + + IfLetStore(mainPageStore) { store in + KeymeMainView(store: store) + .transition(.opacity) + } else: { + Text("앗 죄송!") + } // } } } From 20ef761a6216528dd74f1a220e8586be50a18e6c Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Wed, 30 Aug 2023 14:53:26 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor=20#84:=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=86=A0=EC=96=B4=20?= =?UTF-8?q?=EA=B3=B5=EA=B0=9C=EB=B2=94=EC=9C=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MyPage/MyPageFeature.swift | 83 ++++++++++--------- .../Features/Sources/MyPage/MyPageView.swift | 6 +- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Projects/Features/Sources/MyPage/MyPageFeature.swift b/Projects/Features/Sources/MyPage/MyPageFeature.swift index 8184a1c2..23a3b48c 100644 --- a/Projects/Features/Sources/MyPage/MyPageFeature.swift +++ b/Projects/Features/Sources/MyPage/MyPageFeature.swift @@ -25,39 +25,44 @@ struct MyPageFeature: Reducer { @Dependency(\.keymeAPIManager) private var network struct State: Equatable { - var selectedSegment: MyPageSegment = .similar - var shownFirstTime = true var similarCircleDataList: [CircleData] = [] var differentCircleDataList: [CircleData] = [] - var shownCircleDatalist: [CircleData] = [] - var circleShown = false - + var view: View var scoreListState: ScoreListFeature.State = .init() - let userId: Int - let nickname: String + struct View: Equatable { + let userId: Int + let nickname: String + + var circleShown = false + var selectedSegment: MyPageSegment = .similar + var shownFirstTime = true + var shownCircleDatalist: [CircleData] = [] + } init(userId: Int, nickname: String) { - self.userId = userId - self.nickname = nickname + self.view = View(userId: userId, nickname: nickname) } } enum Action: Equatable { - case selectSegement(MyPageSegment) - case requestCircle(MatchRate) case saveCircle([CircleData], MatchRate) - case markViewAsShown - case circleTapped - case circleDismissed - + case showCircle(MyPageSegment) + case requestCircle(MatchRate) + case view(View) case scoreListAction(ScoreListFeature.Action) + + enum View: Equatable { + case markViewAsShown + case circleTapped + case circleDismissed + case selectSegement(MyPageSegment) + } } // 마이페이지를 사용할 수 없는 케이스 - // 1. 유저 아이디나 닉네임이 정의되지 않음 -> 에러페이지 - // 2. 원 그래프가 아직 집계되지 않음 -> 빈 화면 페이지 - // 3. 네트워크가 연결되지 않음 + // 1. 원 그래프가 아직 집계되지 않음 -> 빈 화면 페이지 + // 2. 네트워크가 연결되지 않음 -> 네트워크 미연결 안내 public var body: some ReducerOf { Scope(state: \.scoreListState, action: /Action.scoreListAction) { ScoreListFeature() @@ -65,24 +70,17 @@ struct MyPageFeature: Reducer { Reduce { state, action in switch action { - case .selectSegement(let segment): - state.selectedSegment = segment - switch segment { - case .different : - state.shownCircleDatalist = state.differentCircleDataList - case .similar : - state.shownCircleDatalist = state.similarCircleDataList - } - - return .none - + case .view(.selectSegement(let segment)): + state.view.selectedSegment = segment + return .send(.showCircle(state.view.selectedSegment)) + // 서버 부하가 있으므로 웬만하면 한 번만 콜 할 것 case .requestCircle(let rate): - let userId = state.userId + let userId = state.view.userId switch rate { case .top5: - return .run { send in + return .run(priority: .userInitiated) { send in let response = try await network.request( .myPage(.statistics(userId, .similar)), object: CircleData.NetworkResult.self) @@ -91,7 +89,7 @@ struct MyPageFeature: Reducer { } case .low5: - return .run { send in + return .run(priority: .userInitiated) { send in let response = try await network.request( .myPage(.statistics(userId, .different)), object: CircleData.NetworkResult.self) @@ -108,19 +106,28 @@ struct MyPageFeature: Reducer { state.differentCircleDataList = data } + return .send(.showCircle(state.view.selectedSegment)) + + case .showCircle(let segment): + switch segment { + case .similar: + state.view.shownCircleDatalist = state.similarCircleDataList + case .different: + state.view.shownCircleDatalist = state.differentCircleDataList + } return .none - case .markViewAsShown: - state.shownFirstTime = false + case .view(.markViewAsShown): + state.view.shownFirstTime = false return .none - case .circleTapped: + case .view(.circleTapped): HapticManager.shared.tok() - state.circleShown = true + state.view.circleShown = true return .none - case .circleDismissed: - state.circleShown = false + case .view(.circleDismissed): + state.view.circleShown = false return .none case .scoreListAction: diff --git a/Projects/Features/Sources/MyPage/MyPageView.swift b/Projects/Features/Sources/MyPage/MyPageView.swift index 4706c8d6..c630bc45 100644 --- a/Projects/Features/Sources/MyPage/MyPageView.swift +++ b/Projects/Features/Sources/MyPage/MyPageView.swift @@ -18,15 +18,15 @@ struct MyPageView: View { init(store: StoreOf) { self.store = store - + store.send(.requestCircle(.top5)) store.send(.requestCircle(.low5)) - store.send(.selectSegement(.similar)) + store.send(.view(.selectSegement(.similar))) } public var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in + WithViewStore(store, observe: \.view, send: MyPageFeature.Action.view) { viewStore in ZStack(alignment: .topLeading) { CirclePackView( namespace: namespace, From d1aad3b1bdee925de0992d5ad21b041196556a68 Mon Sep 17 00:00:00 2001 From: Young Bin Lee Date: Wed, 30 Aug 2023 17:41:50 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor=20#84:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B7=B0(=ED=99=88)=20=EB=A1=9C=EC=A7=81=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 --- .../Sources/Home/KeymeTestHomeFeature.swift | 60 +++++++++++++++++++ .../Sources/Home/KeymeTestHomeView.swift | 34 +++++++++++ .../Sources/Home/KeymeTestsStartFeature.swift | 5 +- .../Sources/MainPage/MainPageView.swift | 25 ++++---- 4 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 Projects/Features/Sources/Home/KeymeTestHomeFeature.swift create mode 100644 Projects/Features/Sources/Home/KeymeTestHomeView.swift diff --git a/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift b/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift new file mode 100644 index 00000000..20612cc0 --- /dev/null +++ b/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift @@ -0,0 +1,60 @@ +// +// KeymeTestHomeFeature.swift +// Features +// +// Created by 이영빈 on 2023/08/30. +// Copyright © 2023 team.humanwave. All rights reserved. +// + +import ComposableArchitecture +import Network + +struct KeymeTestsHomeFeature: Reducer { + @Dependency(\.keymeAPIManager) private var network + + // 테스트를 아직 풀지 않았거나, 풀었거나 2가지 케이스만 존재 + struct State: Equatable { + @PresentationState var testStartViewState: KeymeTestsStartFeature.State? + var view: View + + struct View: Equatable { + let nickname: String + var dailyTestId: Int? + } + + init(nickname: String) { + self.view = View(nickname: nickname) + } + } + + enum Action { + case fetchDailyTests + case showTestStartView(testId: Int) + case startTest(PresentationAction) + + enum View {} + } + + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .fetchDailyTests: + return .run { send in + let fetchedTest = try await network.request(.test(.daily), object: KeymeTestsDTO.self) + let testId = fetchedTest.data.testId + + await send(.showTestStartView(testId: testId)) + } + + case .showTestStartView(let testId): + state.view.dailyTestId = testId + state.testStartViewState = .init(nickname: state.view.nickname, testId: testId) + + default: + break + } + + return .none + } + } +} diff --git a/Projects/Features/Sources/Home/KeymeTestHomeView.swift b/Projects/Features/Sources/Home/KeymeTestHomeView.swift new file mode 100644 index 00000000..1069c688 --- /dev/null +++ b/Projects/Features/Sources/Home/KeymeTestHomeView.swift @@ -0,0 +1,34 @@ +// +// KeymeTestHomeView.swift +// Features +// +// Created by 이영빈 on 2023/08/30. +// Copyright © 2023 team.humanwave. All rights reserved. +// + +import ComposableArchitecture +import SwiftUI + +struct KeymeTestsHomeView: View { + var store: StoreOf + + init(store: StoreOf) { + self.store = store + } + + var body: some View { + WithViewStore(store, observe: { $0 }) { viewStore in + let startTestStore = store.scope( + state: \.$testStartViewState, + action: KeymeTestsHomeFeature.Action.startTest) + + IfLetStore(startTestStore) { store in + KeymeTestsStartView(store: store) + } else: { + EmptyView() + } + + // 결과 화면 표시도 생각 + } + } +} diff --git a/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift b/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift index 374ba19f..abfebb64 100644 --- a/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift +++ b/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift @@ -14,14 +14,15 @@ import Domain public struct KeymeTestsStartFeature: Reducer { public struct State: Equatable { public let nickname: String - public var testId: Int? + public var testId: Int public var keymeTests: KeymeTestsFeature.State? public var isAnimating: Bool = false public var icon: IconModel = .EMPTY - public init(nickname: String) { + public init(nickname: String, testId: Int) { self.nickname = nickname + self.testId = testId } } diff --git a/Projects/Features/Sources/MainPage/MainPageView.swift b/Projects/Features/Sources/MainPage/MainPageView.swift index 5b3de75b..e8d2b9c8 100644 --- a/Projects/Features/Sources/MainPage/MainPageView.swift +++ b/Projects/Features/Sources/MainPage/MainPageView.swift @@ -27,31 +27,26 @@ struct KeymeMainView: View { var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in TabView(selection: $selectedTab) { - KeymeTestsStartView(store: Store( - initialState: KeymeTestsStartFeature.State( - nickname: viewStore.nickname - )) { - KeymeTestsStartFeature() - }) + KeymeTestsHomeView(store: Store( + initialState: KeymeTestsHomeFeature.State( + nickname: viewStore.nickname) + ) { + KeymeTestsHomeFeature() + }) .tabItem { homeTabImage - .resizable() - .frame(width: 24, height: 24) - .aspectRatio(contentMode: .fit) } .tag(Tab.home) MyPageView(store: Store( initialState: MyPageFeature.State( userId: viewStore.state.userId, - nickname: viewStore.state.nickname)) { - MyPageFeature() - }) + nickname: viewStore.state.nickname) + ) { + MyPageFeature() + }) .tabItem { myPageTabImage - .resizable() - .frame(width: 24, height: 24) - .aspectRatio(contentMode: .fit) } .tag(Tab.myPage) } From 7949bfe7e0edc8de0bd96a3380aa34bb76746f67 Mon Sep 17 00:00:00 2001 From: YoungBin Lee Date: Thu, 31 Aug 2023 10:01:14 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor=20#84:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B7=B0(=ED=99=88)=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Client/KeymeTestsClient.swift | 4 +- .../Domain/Sources/Client/TestClient.swift | 35 --------- .../Sources/Model/KeymeTestsModel.swift | 9 +-- Projects/Domain/Sources/Model/TestModel.swift | 21 ----- .../Sources/Home/KeymeTestHomeFeature.swift | 13 ++-- .../Sources/Home/KeymeTestHomeView.swift | 57 +++++++++++--- .../Sources/Home/KeymeTestsStartFeature.swift | 29 ++----- .../Sources/Home/KeymeTestsStartView.swift | 56 ++++--------- Projects/Features/Sources/Root/RootView.swift | 78 +++++++++---------- .../Features/Sources/Test/TestStore.swift | 48 ------------ Projects/Features/Sources/Test/TestView.swift | 38 --------- .../Network/Sources/DTO/KeymeTestsDTO.swift | 50 ++++++------ Projects/Network/Sources/DTO/TestDTO.swift | 17 ---- 13 files changed, 148 insertions(+), 307 deletions(-) delete mode 100644 Projects/Domain/Sources/Client/TestClient.swift delete mode 100644 Projects/Domain/Sources/Model/TestModel.swift delete mode 100644 Projects/Features/Sources/Test/TestStore.swift delete mode 100644 Projects/Features/Sources/Test/TestView.swift delete mode 100644 Projects/Network/Sources/DTO/TestDTO.swift diff --git a/Projects/Domain/Sources/Client/KeymeTestsClient.swift b/Projects/Domain/Sources/Client/KeymeTestsClient.swift index f4be857b..1e508f62 100644 --- a/Projects/Domain/Sources/Client/KeymeTestsClient.swift +++ b/Projects/Domain/Sources/Client/KeymeTestsClient.swift @@ -35,12 +35,12 @@ private func getClient(with network: KeymeAPIManager) -> KeymeTestsClient { let api = KeymeAPI.test(.onboarding) let response = try await network.request(api, object: KeymeTestsDTO.self) - return response.toIconModel() + return response.toKeymeTestsModel() }, fetchDailyTests: { let api = KeymeAPI.test(.daily) let response = try await network.request(api, object: KeymeTestsDTO.self) - return response.toIconModel() + return response.toKeymeTestsModel() }, fetchTestResult: { testResultId in let api = KeymeAPI.test(.result(testResultId)) let response = try await network.request(api, object: TestResultDTO.self) diff --git a/Projects/Domain/Sources/Client/TestClient.swift b/Projects/Domain/Sources/Client/TestClient.swift deleted file mode 100644 index 6e98be09..00000000 --- a/Projects/Domain/Sources/Client/TestClient.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// TestClient.swift -// Domain -// -// Created by 김영인 on 2023/07/29. -// Copyright © 2023 team.humanwave. All rights reserved. -// - -import ComposableArchitecture - -import Network - -public struct TestClient { - public var fetchTest: @Sendable () async throws -> TestModel -} - -extension DependencyValues { - public var testClient: TestClient { - get { self[TestClient.self] } - set { self[TestClient.self] = newValue } - } -} - -extension TestClient: DependencyKey { - public static var liveValue = TestClient( - fetchTest: { -// let api = TestAPI.hello -// let response = try await TestAPIManager.shared.request(api) -// let decoded = String(data: response.data, encoding: .utf8)! -// -// return TestDTO(hello: decoded).toModel() - return .init(hello: "") - } - ) -} diff --git a/Projects/Domain/Sources/Model/KeymeTestsModel.swift b/Projects/Domain/Sources/Model/KeymeTestsModel.swift index 78d29139..348baf68 100644 --- a/Projects/Domain/Sources/Model/KeymeTestsModel.swift +++ b/Projects/Domain/Sources/Model/KeymeTestsModel.swift @@ -13,7 +13,6 @@ import Network import Kingfisher public struct KeymeTestsModel: Equatable { - public let nickname: String public let testId: Int public let icons: [IconModel] } @@ -26,14 +25,12 @@ public struct IconModel: Equatable, Hashable { } public extension KeymeTestsDTO { - func toIconModel() -> KeymeTestsModel { - let nickname = data.owner.nickname + func toKeymeTestsModel() -> KeymeTestsModel { let icons = data.questions.map { IconModel(imageURL: $0.category.iconUrl, color: Color.hex($0.category.color)) } - return KeymeTestsModel(nickname: nickname ?? "키미", - testId: data.testId, - icons: icons) + + return KeymeTestsModel(testId: data.testId, icons: icons) } } diff --git a/Projects/Domain/Sources/Model/TestModel.swift b/Projects/Domain/Sources/Model/TestModel.swift deleted file mode 100644 index dd6acd9c..00000000 --- a/Projects/Domain/Sources/Model/TestModel.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// TestModel.swift -// Domain -// -// Created by 김영인 on 2023/07/29. -// Copyright © 2023 team.humanwave. All rights reserved. -// - -import Foundation - -import Network - -public struct TestModel { - public let hello: String -} - -public extension TestDTO { - func toModel() -> TestModel { - return .init(hello: hello) - } -} diff --git a/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift b/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift index 20612cc0..2a80651c 100644 --- a/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift +++ b/Projects/Features/Sources/Home/KeymeTestHomeFeature.swift @@ -7,6 +7,7 @@ // import ComposableArchitecture +import Domain import Network struct KeymeTestsHomeFeature: Reducer { @@ -29,7 +30,7 @@ struct KeymeTestsHomeFeature: Reducer { enum Action { case fetchDailyTests - case showTestStartView(testId: Int) + case showTestStartView(testData: KeymeTestsModel) case startTest(PresentationAction) enum View {} @@ -41,14 +42,14 @@ struct KeymeTestsHomeFeature: Reducer { case .fetchDailyTests: return .run { send in let fetchedTest = try await network.request(.test(.daily), object: KeymeTestsDTO.self) - let testId = fetchedTest.data.testId + let testData = fetchedTest.toKeymeTestsModel() - await send(.showTestStartView(testId: testId)) + await send(.showTestStartView(testData: testData)) } - case .showTestStartView(let testId): - state.view.dailyTestId = testId - state.testStartViewState = .init(nickname: state.view.nickname, testId: testId) + case .showTestStartView(let testData): + state.view.dailyTestId = testData.testId + state.testStartViewState = .init(nickname: state.view.nickname, testData: testData) default: break diff --git a/Projects/Features/Sources/Home/KeymeTestHomeView.swift b/Projects/Features/Sources/Home/KeymeTestHomeView.swift index 1069c688..a4bae6ab 100644 --- a/Projects/Features/Sources/Home/KeymeTestHomeView.swift +++ b/Projects/Features/Sources/Home/KeymeTestHomeView.swift @@ -7,6 +7,7 @@ // import ComposableArchitecture +import DSKit import SwiftUI struct KeymeTestsHomeView: View { @@ -14,21 +15,57 @@ struct KeymeTestsHomeView: View { init(store: StoreOf) { self.store = store + store.send(.fetchDailyTests) } var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in - let startTestStore = store.scope( - state: \.$testStartViewState, - action: KeymeTestsHomeFeature.Action.startTest) - - IfLetStore(startTestStore) { store in - KeymeTestsStartView(store: store) - } else: { - EmptyView() + WithViewStore(store, observe: { $0.view }) { viewStore in + ZStack(alignment: .center) { + DSKitAsset.Color.keymeBlack.swiftUIColor.ignoresSafeArea() + + VStack(alignment: .leading) { + // Filler + Spacer().frame(height: 75) + + welcomeText(nickname: viewStore.nickname) + .foregroundColor(DSKitAsset.Color.keymeWhite.swiftUIColor) + + Spacer() + } + .fullFrame() + .padding(.horizontal, 16) + + // 테스트 뷰 + testView + + // 결과 화면 표시도 생각 + } + } + } +} - // 결과 화면 표시도 생각 +extension KeymeTestsHomeView { + var testView: some View { + let startTestStore = store.scope( + state: \.$testStartViewState, + action: KeymeTestsHomeFeature.Action.startTest) + + return IfLetStore(startTestStore) { store in + KeymeTestsStartView(store: store) + } else: { + Circle() + .strokeBorder(.white.opacity(0.3), lineWidth: 1) + .background(Circle().foregroundColor(.white.opacity(0.3))) + .frame(width: 280, height: 280) } } + + func welcomeText(nickname: String) -> some View { + Text.keyme( + "환영해요 \(nickname)님!", +// "환영해요 \(viewStore.nickname)님!\n이제 문제를 풀어볼까요?", + font: .heading1) + } + } diff --git a/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift b/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift index abfebb64..3f74eed0 100644 --- a/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift +++ b/Projects/Features/Sources/Home/KeymeTestsStartFeature.swift @@ -14,21 +14,20 @@ import Domain public struct KeymeTestsStartFeature: Reducer { public struct State: Equatable { public let nickname: String - public var testId: Int - + public let testData: KeymeTestsModel + + public var icon: IconModel = .EMPTY public var keymeTests: KeymeTestsFeature.State? public var isAnimating: Bool = false - public var icon: IconModel = .EMPTY - public init(nickname: String, testId: Int) { + public init(nickname: String, testData: KeymeTestsModel) { self.nickname = nickname - self.testId = testId + self.testData = testData } } public enum Action { case viewWillAppear - case fetchDailyTests(TaskResult) case startAnimation([IconModel]) case setIcon(IconModel) case startButtonDidTap @@ -44,22 +43,8 @@ public struct KeymeTestsStartFeature: Reducer { Reduce { state, action in switch action { case .viewWillAppear: - return .run { send in - await send(.fetchDailyTests( - TaskResult { - try await self.keymeTestsClient.fetchDailyTests() - } - )) - } - - case let .fetchDailyTests(.success(tests)): - state.testId = tests.testId state.isAnimating = true - - return .send(.startAnimation(tests.icons)) - - case .fetchDailyTests(.failure): - return .send(.startAnimation([IconModel.EMPTY])) + return .send(.startAnimation(state.testData.icons)) case .startAnimation(let icons): return .run { send in @@ -76,7 +61,7 @@ public struct KeymeTestsStartFeature: Reducer { state.isAnimating = true case .startButtonDidTap: - let url = "https://keyme-frontend.vercel.app/test/\(state.testId)" + let url = "https://keyme-frontend.vercel.app/test/\(state.testData.testId)" state.keymeTests = KeymeTestsFeature.State(url: url) case .keymeTests: diff --git a/Projects/Features/Sources/Home/KeymeTestsStartView.swift b/Projects/Features/Sources/Home/KeymeTestsStartView.swift index 56b07c8d..f69d9742 100644 --- a/Projects/Features/Sources/Home/KeymeTestsStartView.swift +++ b/Projects/Features/Sources/Home/KeymeTestsStartView.swift @@ -24,48 +24,26 @@ public struct KeymeTestsStartView: View { public var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in - VStack { - IfLetStore( - self.store.scope( - state: \.keymeTests, - action: KeymeTestsStartFeature.Action.keymeTests - ), - then: { store in - KeymeTestsView(store: store) - .ignoresSafeArea(.all) - .transition(.scale.animation(.easeIn)) - }, - else: { - Spacer() - .frame(height: 75) - - welcomeText(viewStore) - - Spacer() - - startTestsButton(viewStore) - .onTapGesture { - viewStore.send(.startButtonDidTap) - } - - Spacer() - } - ) - } - .frame(maxWidth: .infinity) - .background(DSKitAsset.Color.keymeBlack.swiftUIColor) + IfLetStore( + self.store.scope( + state: \.keymeTests, + action: KeymeTestsStartFeature.Action.keymeTests + ), + then: { store in + KeymeTestsView(store: store) + .ignoresSafeArea(.all) + .transition(.scale.animation(.easeIn)) + }, + else: { + startTestsButton(viewStore) + .onTapGesture { + viewStore.send(.startButtonDidTap) + } + } + ) } } - func welcomeText(_ viewStore: ViewStore) -> some View { - Text.keyme( - "환영해요 \(viewStore.nickname ?? "키미")님!\n이제 문제를 풀어볼까요?", - font: .heading1) // TODO: 닉변 - .foregroundColor(DSKitAsset.Color.keymeWhite.swiftUIColor) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(Padding.insets(leading: 16)) - } - func startTestsButton(_ viewStore: ViewStore) -> some View { ZStack { diff --git a/Projects/Features/Sources/Root/RootView.swift b/Projects/Features/Sources/Root/RootView.swift index 455aa770..56d26c43 100644 --- a/Projects/Features/Sources/Root/RootView.swift +++ b/Projects/Features/Sources/Root/RootView.swift @@ -22,43 +22,43 @@ public struct RootView: View { public var body: some View { WithViewStore(store, observe: { $0 }) { viewStore in -// if viewStore.logInStatus == .notDetermined { -// // 여기 걸리면 에러임. 조심하셈. -// EmptyView() -// } else if viewStore.logInStatus == .loggedOut { -// // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 -// let loginStore = store.scope( -// state: \.$logInStatus, -// action: RootFeature.Action.login) -// -// IfLetStore(loginStore) { store in -// SignInView(store: store) -// } -// } else if viewStore.registrationState?.status == .notDetermined { -// // 개인정보 등록 상태를 로딩 중 -// ProgressView() -// } else if viewStore.registrationState?.status == .needsRegister { -// // 개인정보 등록 -// let registrationStore = store.scope( -// state: \.$registrationState, -// action: RootFeature.Action.registration) -// -// IfLetStore(registrationStore) { store in -// RegistrationView(store: store) -// } -// } else if viewStore.onboardingStatus?.status == .notDetermined { -// // 온보딩 상태를 로딩 중 -// ProgressView() -// } else if viewStore.onboardingStatus?.status == .needsOnboarding { -// // 가입했지만 온보딩을 하지 않고 종료했던 유저 -// let onboardingStore = store.scope( -// state: \.$onboardingStatus, -// action: RootFeature.Action.onboarding) -// -// IfLetStore(onboardingStore) { store in -// OnboardingView(store: store) -// } -// } else { + if viewStore.logInStatus == .notDetermined { + // 여기 걸리면 에러임. 조심하셈. + EmptyView() + } else if viewStore.logInStatus == .loggedOut { + // 회원가입을 하지 않았거나 로그인을 하지 않은 유저 + let loginStore = store.scope( + state: \.$logInStatus, + action: RootFeature.Action.login) + + IfLetStore(loginStore) { store in + SignInView(store: store) + } + } else if viewStore.registrationState?.status == .notDetermined { + // 개인정보 등록 상태를 로딩 중 + ProgressView() + } else if viewStore.registrationState?.status == .needsRegister { + // 개인정보 등록 + let registrationStore = store.scope( + state: \.$registrationState, + action: RootFeature.Action.registration) + + IfLetStore(registrationStore) { store in + RegistrationView(store: store) + } + } else if viewStore.onboardingStatus?.status == .notDetermined { + // 온보딩 상태를 로딩 중 + ProgressView() + } else if viewStore.onboardingStatus?.status == .needsOnboarding { + // 가입했지만 온보딩을 하지 않고 종료했던 유저 + let onboardingStore = store.scope( + state: \.$onboardingStatus, + action: RootFeature.Action.onboarding) + + IfLetStore(onboardingStore) { store in + OnboardingView(store: store) + } + } else { // 가입했고 온보딩을 진행한 유저 let mainPageStore = store.scope(state: \.$mainPageState, action: RootFeature.Action.mainPage) @@ -66,9 +66,9 @@ public struct RootView: View { KeymeMainView(store: store) .transition(.opacity) } else: { - Text("앗 죄송!") + Text("에러") } -// } + } } } } diff --git a/Projects/Features/Sources/Test/TestStore.swift b/Projects/Features/Sources/Test/TestStore.swift deleted file mode 100644 index 9fa659e2..00000000 --- a/Projects/Features/Sources/Test/TestStore.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// TestStore.swift -// Features -// -// Created by 김영인 on 2023/07/29. -// Copyright © 2023 team.humanwave. All rights reserved. -// - -import ComposableArchitecture - -import Domain - -public struct TestStore: ReducerProtocol { - public struct State: Equatable { - var text: String? - - public init(text: String? = nil) { - self.text = text - } - } - - public enum Action { - case testResponse(TaskResult) - case buttonDidTap - } - - @Dependency(\.testClient) var testClient - - public init() { } - - public var body: some ReducerProtocol { - Reduce { state, action in - switch action { - case let .testResponse(.success(textModel)): - state.text = textModel.hello - case .testResponse(.failure): - state.text = nil - case .buttonDidTap: - return .run { send in - await send(.testResponse( - TaskResult { try await self.testClient.fetchTest() } - )) - } - } - return .none - } - } -} diff --git a/Projects/Features/Sources/Test/TestView.swift b/Projects/Features/Sources/Test/TestView.swift deleted file mode 100644 index 4f489cdb..00000000 --- a/Projects/Features/Sources/Test/TestView.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// TestView.swift -// Features -// -// Created by 김영인 on 2023/07/29. -// Copyright © 2023 team.humanwave. All rights reserved. -// - -import SwiftUI - -import ComposableArchitecture - -import DSKit - -public struct TestView: View { - public let store: StoreOf - - public init(store: StoreOf) { - self.store = store - } - - public var body: some View { - WithViewStore(self.store, observe: { $0 }) { viewStore in - VStack(spacing: 50) { - Text(viewStore.text ?? "") - .font(Font.Keyme.body2) - .foregroundColor(DSKitAsset.Color.keymeBlack.swiftUIColor) - - DSKitAsset.Image.camera.swiftUIImage - .frame(width: 40, height: 40) - - Button("테스트 서버 호출하기") { - viewStore.send(.buttonDidTap) - } - } - } - } -} diff --git a/Projects/Network/Sources/DTO/KeymeTestsDTO.swift b/Projects/Network/Sources/DTO/KeymeTestsDTO.swift index 49c52f48..0aca7b0b 100644 --- a/Projects/Network/Sources/DTO/KeymeTestsDTO.swift +++ b/Projects/Network/Sources/DTO/KeymeTestsDTO.swift @@ -9,32 +9,34 @@ import Foundation public struct KeymeTestsDTO: Codable { - public let data: DataDTO - let message: String let code: Int -} - -public struct DataDTO: Codable { - public let testResultId: Int? - public let owner: PresenterProfileDTO - public let questions: [QuestionDTO] - let solvedCount: Int - public let testId: Int - let title: String -} + let message: String + public let data: TestData + + public struct TestData: Codable { + public let owner: Owner + public let questions: [Question] + public let testId: Int + let testResultId: Int? + public let title: String + } -public struct PresenterProfileDTO: Codable { - let id: Int - public let nickname: String? - let profileThumbnail: String -} + public struct Owner: Codable { + let id: Int + public let nickname: String + let profileThumbnail: String + } -public struct QuestionDTO: Codable { - public let category: CategoryDTO - let title, keyword: String - let questionId: Int -} + public struct Question: Codable { + public let category: Category + let keyword: String + let questionId: Int + let title: String + } -public struct CategoryDTO: Codable { - public let color, iconUrl, name: String + public struct Category: Codable { + public let color: String + public let iconUrl: String + let name: String + } } diff --git a/Projects/Network/Sources/DTO/TestDTO.swift b/Projects/Network/Sources/DTO/TestDTO.swift deleted file mode 100644 index 19f99121..00000000 --- a/Projects/Network/Sources/DTO/TestDTO.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// TestDTO.swift -// Network -// -// Created by 김영인 on 2023/07/29. -// Copyright © 2023 team.humanwave. All rights reserved. -// - -import Foundation - -public struct TestDTO { - public let hello: String - - public init(hello: String) { - self.hello = hello - } -}