From 6b6207dbe598565e3b7c2d0ada8f925060a6e67f Mon Sep 17 00:00:00 2001 From: thinkyside Date: Sat, 12 Apr 2025 13:39:38 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[#358]=20TodayQuestionViwe=20UI=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SourceCode/Entity/QuestionState.swift | 4 +- .../2-2.TodayQuestion/TodayQuestionView.swift | 94 ++++++++++++++++--- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/Qapple/Qapple/SourceCode/Entity/QuestionState.swift b/Qapple/Qapple/SourceCode/Entity/QuestionState.swift index 51763408..58fe2a0e 100644 --- a/Qapple/Qapple/SourceCode/Entity/QuestionState.swift +++ b/Qapple/Qapple/SourceCode/Entity/QuestionState.swift @@ -40,8 +40,8 @@ enum QuestionState: Codable { /// 질문 및 답변 여부에 따라 변화하는 버튼 문자열 func buttonTitle(isAnswerd: Bool) -> String { switch self { - case .creating: isAnswerd ? "다른 답변 둘러보기" : "이전 질문 답변하기" - case .ready: "질문에 답변하기" + case .creating: isAnswerd ? "다른 답변 둘러보기" : "답변하기" + case .ready: "답변하기" case .complete: "다른 답변 둘러보기" } } diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift index bcdeef8d..f15d743d 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift @@ -13,18 +13,14 @@ struct TodayQuestionView: View { @Bindable var store: StoreOf var body: some View { - ZStack { - Color.second.ignoresSafeArea() + VStack(spacing: 0) { + HeaderView(store: store) ScrollView { - VStack(spacing: 0) { - HeaderView(store: store) - QuestionButton(store: store) - AnswerPreviewList(store: store) - } + AnswerPreviewList(store: store) } - .scrollIndicators(.hidden) } + .scrollIndicators(.hidden) .onAppear { store.send(.onAppear) } @@ -36,10 +32,7 @@ struct TodayQuestionView: View { } .loadingIndicator(isLoading: store.isLoading) .alert($store.scope(state: \.alert, action: \.alert)) - .sheet(item: $store.scope( - state: \.sheet, - action: \.sheet) - ) { store in + .sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { store in switch store.case { case let .seeMore(store): SeeMoreSheet(store: store) } @@ -51,6 +44,81 @@ struct TodayQuestionView: View { private struct HeaderView: View { + let store: StoreOf + + var body: some View { + HStack(spacing: 8) { + Image(store.questionState.graphicImage) + .resizable() + .scaledToFill() + .frame(width: 40, height: 40) + + Text(store.questionState.mainTitle) + .font(.pretendard(.semiBold, size: 18)) + .foregroundStyle(.wh) + .tracking(-1) + + Spacer() + + if store.questionState == .creating { + QuestionTimer() + } else { + AnsweringButton() + } + } + .padding(.horizontal, 24) + .frame(maxWidth: .infinity) + .frame(height: 90) + .background(.second) + .cornerRadius(32, corners: [.bottomLeft, .bottomRight]) + } + + /// 질문 타이머 + private func QuestionTimer() -> some View { + Text(store.timeRemainingForQuestion.timerFormat) + .font(.pretendard(.bold, size: 22)) + .foregroundStyle(LinearGradient.timer) + .monospacedDigit() + .kerning(-2) + } + + /// 답변하기 버튼 + private func AnsweringButton() -> some View { + Button { + store.send(.questionButtonTapped(store.todayQuestion)) + } label: { + Text(title) + .font(.pretendard(.semiBold, size: 15)) + .frame(width: 84) + .padding(.vertical, 10) + .foregroundStyle(.main) + .background(backgroundColor) + .clipShape(RoundedRectangle(cornerRadius: 20)) + } + .opacity(store.isLoading ? 0 : 1) + .buttonStyle(ScalableButtonStyle()) + } + + /// 버튼 제목 + private var title: String { + store.questionState.buttonTitle( + isAnswerd: store.todayQuestion.isAnswered + ) + } + + /// 버튼 배경 색상 + private var backgroundColor: Color { + switch store.questionState { + case .creating: + store.todayQuestion.isAnswered ? .secondaryButton : .button + case .ready: .button + case .complete: .secondaryButton + } + } +} + +private struct LegacyHeaderView: View { + @State private var offsetY: CGFloat = 0 let store: StoreOf @@ -165,7 +233,7 @@ private struct AnswerPreviewList: View { if store.answerPreviewList.isEmpty { Spacer() - Text("아직 답변이 달리지않았어요\n첫 답변을 달아보세요!") + Text("첫 답변을 달아보세요!") .font(.pretendard(.semiBold, size: 14)) .foregroundStyle(.sub4) .multilineTextAlignment(.center) From 188636c4a93e3a900734e381736783c7cb536834 Mon Sep 17 00:00:00 2001 From: thinkyside Date: Sat, 12 Apr 2025 14:05:03 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[#358]=20QPAnswerCell=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2-2.TodayQuestion/TodayQuestionView.swift | 6 + .../2-6.AnswerList/AnswerListView.swift | 6 + .../5-3.MyAnswerList/MyAnswerListView.swift | 6 + .../SourceCode/UIComponent/QPAnswerCell.swift | 104 ++++++++++++++++-- 4 files changed, 113 insertions(+), 9 deletions(-) diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift index f15d743d..a4f8302f 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift @@ -289,6 +289,12 @@ private struct AnswerPreviewList: View { state: .normal, seeMoreAction: { store.send(.seeMoreAnswerButtonTapped(answer)) + }, + likeAction: { + + }, + commentAction: { + } ) } diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift index 5cedce42..1f457ea0 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-6.AnswerList/AnswerListView.swift @@ -139,6 +139,12 @@ private struct AnswerList: View { state: .normal, seeMoreAction: { store.send(.seeMoreAction(answer)) + }, + likeAction: { + + }, + commentAction: { + } ) .configurePagination( diff --git a/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift b/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift index 6e315382..53a8bc86 100644 --- a/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift +++ b/Qapple/Qapple/SourceCode/Feature/5.Profile/5-3.MyAnswerList/MyAnswerListView.swift @@ -70,6 +70,12 @@ private struct MyAnswerList: View { state: .written, seeMoreAction:{ store.send(.seeMoreAction(answer)) + }, + likeAction: { + + }, + commentAction: { + } ) .configurePagination( diff --git a/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift b/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift index 6eee0e95..ab19c530 100644 --- a/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift +++ b/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift @@ -18,6 +18,8 @@ struct QPAnswerCell: View { let index: Int let state: State let seeMoreAction: () -> Void + let likeAction: () -> Void + let commentAction: () -> Void var body: some View { switch state { @@ -33,7 +35,9 @@ struct QPAnswerCell: View { answer: answer, index: index, author: author(index: index), - seeMoreAction: seeMoreAction + seeMoreAction: seeMoreAction, + likeAction: likeAction, + commentAction: commentAction ) } @@ -42,7 +46,9 @@ struct QPAnswerCell: View { answer: answer, index: index, author: answer.authorNickname, - seeMoreAction: seeMoreAction + seeMoreAction: seeMoreAction, + likeAction: likeAction, + commentAction: commentAction ) } } @@ -62,6 +68,8 @@ private struct NormalCell: View { let index: Int let author: String let seeMoreAction: () -> Void + let likeAction: () -> Void + let commentAction: () -> Void var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -70,11 +78,16 @@ private struct NormalCell: View { } Header() - .padding(.top, 18) + .padding(.top, 20) .padding(.horizontal, 16) Content() - .padding(.bottom, 16) + .padding(.top, 8) + .padding(.horizontal, 16) + + Footer() + .padding(.top, 8) + .padding(.bottom, 20) .padding(.horizontal, 16) } .background(.first) @@ -131,6 +144,44 @@ private struct NormalCell: View { .padding(.top, 2) } } + + private func Footer() -> some View { + HStack(spacing: 0) { + Circle() + .foregroundStyle(.clear) + .frame(width: 28, height: 28) + + Button { + likeAction() + } label: { + HStack(spacing: 4) { + Image(true ? .heartActive : .heart) + .resizable() + .frame(width: 18, height: 18) + + Text("32") + .pretendard(.regular, 13) + .foregroundStyle(.sub3) + } + } + .padding(.leading, 8) + + Button { + commentAction() + } label: { + HStack(spacing: 4) { + Image(systemName: "text.bubble.fill") + .resizable() + .frame(width: 15, height: 14) + + Text("32") + .pretendard(.regular, 13) + } + .foregroundStyle(.sub3) + } + .padding(.leading, 12) + } + } } // MARK: - ReportedCell @@ -303,11 +354,46 @@ private struct ReportedCell: View { Color.first.ignoresSafeArea() VStack(alignment: .leading, spacing: 0) { - QPAnswerCell(answer: answers[0], index: 0, state: .normal) {} - QPAnswerCell(answer: answers[1], index: 1, state: .normal) {} - QPAnswerCell(answer: answers[2], index: 2, state: .normal) {} - QPAnswerCell(answer: answers[3], index: 3, state: .normal) {} - QPAnswerCell(answer: answers[1], index: 4, state: .written) {} + QPAnswerCell( + answer: answers[0], + index: 0, + state: .normal, + seeMoreAction: {}, + likeAction: {}, + commentAction: {} + ) + QPAnswerCell( + answer: answers[1], + index: 1, + state: .normal, + seeMoreAction: {}, + likeAction: {}, + commentAction: {} + ) + QPAnswerCell( + answer: answers[2], + index: 2, + state: .normal, + seeMoreAction: {}, + likeAction: {}, + commentAction: {} + ) + QPAnswerCell( + answer: answers[3], + index: 3, + state: .normal, + seeMoreAction: {}, + likeAction: {}, + commentAction: {} + ) + QPAnswerCell( + answer: answers[1], + index: 4, + state: .written, + seeMoreAction: {}, + likeAction: {}, + commentAction: {} + ) } } } From 4fa4d09d01c2760886531b2be8004487a877464d Mon Sep 17 00:00:00 2001 From: thinkyside Date: Sun, 13 Apr 2025 22:06:52 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[#358]=20LargeHeader,=20SmallHeader=20?= =?UTF-8?q?=EA=B0=81=EA=B0=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2-2.TodayQuestion/TodayQuestionView.swift | 70 +++++++++++-------- .../SourceCode/UIComponent/QPAnswerCell.swift | 2 +- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift index a4f8302f..8138129d 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift @@ -13,13 +13,16 @@ struct TodayQuestionView: View { @Bindable var store: StoreOf var body: some View { - VStack(spacing: 0) { - HeaderView(store: store) - - ScrollView { + ScrollView { + VStack(spacing: 0) { + LargeHeaderView(store: store) + LargeQuestionButton(store: store) + // SmallHeaderView(store: store) AnswerPreviewList(store: store) } + } + .background(.second) .scrollIndicators(.hidden) .onAppear { store.send(.onAppear) @@ -40,37 +43,41 @@ struct TodayQuestionView: View { } } -// MARK: - HeaderView +// MARK: - SmallHeaderView -private struct HeaderView: View { +private struct SmallHeaderView: View { let store: StoreOf var body: some View { - HStack(spacing: 8) { - Image(store.questionState.graphicImage) - .resizable() - .scaledToFill() - .frame(width: 40, height: 40) - - Text(store.questionState.mainTitle) - .font(.pretendard(.semiBold, size: 18)) - .foregroundStyle(.wh) - .tracking(-1) - - Spacer() + ZStack { + Color.first.ignoresSafeArea() - if store.questionState == .creating { - QuestionTimer() - } else { - AnsweringButton() + HStack(spacing: 8) { + Image(store.questionState.graphicImage) + .resizable() + .scaledToFill() + .frame(width: 40, height: 40) + + Text(store.questionState.mainTitle) + .font(.pretendard(.semiBold, size: 18)) + .foregroundStyle(.wh) + .tracking(-1) + + Spacer() + + if store.questionState == .creating { + QuestionTimer() + } else { + AnsweringButton() + } } + .padding(.horizontal, 24) + .frame(maxWidth: .infinity) + .frame(height: 90) + .background(.second) + .cornerRadius(32, corners: [.bottomLeft, .bottomRight]) } - .padding(.horizontal, 24) - .frame(maxWidth: .infinity) - .frame(height: 90) - .background(.second) - .cornerRadius(32, corners: [.bottomLeft, .bottomRight]) } /// 질문 타이머 @@ -117,7 +124,9 @@ private struct HeaderView: View { } } -private struct LegacyHeaderView: View { +// MARK: - LargeHeaderView + +private struct LargeHeaderView: View { @State private var offsetY: CGFloat = 0 @@ -161,9 +170,9 @@ private struct LegacyHeaderView: View { } } -// MARK: - QuestionButton +// MARK: - LargeQuestionButton -private struct QuestionButton: View { +private struct LargeQuestionButton: View { let store: StoreOf @@ -209,7 +218,6 @@ private struct QuestionButton: View { } } } - // MARK: - AnswerPreviewList private struct AnswerPreviewList: View { diff --git a/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift b/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift index ab19c530..ac1ae10a 100644 --- a/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift +++ b/Qapple/Qapple/SourceCode/UIComponent/QPAnswerCell.swift @@ -86,7 +86,7 @@ private struct NormalCell: View { .padding(.horizontal, 16) Footer() - .padding(.top, 8) + .padding(.top, 6) .padding(.bottom, 20) .padding(.horizontal, 16) } From 1413f4c13c66474e42185f6c50e6257c766a9986 Mon Sep 17 00:00:00 2001 From: thinkyside Date: Sun, 13 Apr 2025 22:25:26 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[#358]=20TodayQuestionView=20=EC=B2=AB=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85,=20N=EB=B2=88=EC=A7=B8=20=EC=A7=84=EC=9E=85?= =?UTF-8?q?=20=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 --- Qapple/Qapple/Resource/Constant/Constant.swift | 1 + .../2-2.TodayQuestion/TodayQuestionFeature.swift | 10 ++++++++++ .../2-2.TodayQuestion/TodayQuestionView.swift | 10 ++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Qapple/Qapple/Resource/Constant/Constant.swift b/Qapple/Qapple/Resource/Constant/Constant.swift index 2c1e7fe2..0773dbee 100644 --- a/Qapple/Qapple/Resource/Constant/Constant.swift +++ b/Qapple/Qapple/Resource/Constant/Constant.swift @@ -17,5 +17,6 @@ enum Constant { extension Constant { static let isSignIn = "isSignIn" + static let recentQuestionID = "recentQuestionID" static let userRandomID = "userRandomID" } diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionFeature.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionFeature.swift index 33687585..cbf4c0dd 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionFeature.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionFeature.swift @@ -19,8 +19,10 @@ struct TodayQuestionFeature { var timeRemainingForQuestion: TimeInterval = 0 var isLoading = true var isFirstLaunch = true + var isNewQuestion = false @Presents var sheet: Sheet.State? @Presents var alert: AlertState? + @Shared(.appStorage(Constant.recentQuestionID)) var recentQuestionID = 0 } enum Action { @@ -79,6 +81,14 @@ struct TodayQuestionFeature { case let .mainQuestionResponse(mainQuestion): state.isFirstLaunch = false state.todayQuestion = mainQuestion + + if mainQuestion.id != state.recentQuestionID { + state.isNewQuestion = true + state.$recentQuestionID.withLock { $0 = mainQuestion.id } + } else { + state.isNewQuestion = false + } + if isQuestionLiveTime { state.questionState = mainQuestion.isAnswered ? .complete : .ready return .none diff --git a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift index 8138129d..5fabc4fe 100644 --- a/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift +++ b/Qapple/Qapple/SourceCode/Feature/2.QuestionTab/2-2.TodayQuestion/TodayQuestionView.swift @@ -15,12 +15,14 @@ struct TodayQuestionView: View { var body: some View { ScrollView { VStack(spacing: 0) { - LargeHeaderView(store: store) - LargeQuestionButton(store: store) - // SmallHeaderView(store: store) + if store.isNewQuestion { + LargeHeaderView(store: store) + LargeQuestionButton(store: store) + } else { + SmallHeaderView(store: store) + } AnswerPreviewList(store: store) } - } .background(.second) .scrollIndicators(.hidden) From cb0c0bd6afed9b5b0bdac24c08c4c7590df89b43 Mon Sep 17 00:00:00 2001 From: thinkyside Date: Sun, 13 Apr 2025 22:30:14 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[#358]=20QuestionButton=20Title=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Qapple/Qapple/SourceCode/Entity/QuestionState.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Qapple/Qapple/SourceCode/Entity/QuestionState.swift b/Qapple/Qapple/SourceCode/Entity/QuestionState.swift index 58fe2a0e..4a7f74e7 100644 --- a/Qapple/Qapple/SourceCode/Entity/QuestionState.swift +++ b/Qapple/Qapple/SourceCode/Entity/QuestionState.swift @@ -40,9 +40,9 @@ enum QuestionState: Codable { /// 질문 및 답변 여부에 따라 변화하는 버튼 문자열 func buttonTitle(isAnswerd: Bool) -> String { switch self { - case .creating: isAnswerd ? "다른 답변 둘러보기" : "답변하기" + case .creating: isAnswerd ? "둘러보기" : "답변하기" case .ready: "답변하기" - case .complete: "다른 답변 둘러보기" + case .complete: "둘러보기" } } }