From 1f1b1395e7c55fff31912a10e294a012c663f237 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 15:57:10 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=EC=A4=91=EC=9D=BC?= =?UTF-8?q?=20=EB=95=8C=20=EC=B9=B4=EB=93=9C=20=EC=9D=B8=ED=84=B0=EB=9E=99?= =?UTF-8?q?=EC=85=98=20=EB=A7=89=EC=9D=8C=20-=20#330?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 8575065a..87779177 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -149,6 +149,7 @@ private extension GoalDetailView { .gesture( DragGesture() .onChanged { value in + guard !store.isEditing else { return } let translation = value.translation let width = resistedDragWidth( for: translation.width, @@ -169,6 +170,7 @@ private extension GoalDetailView { isCrossingDuringDrag = shouldCrossCards(for: width) } .onEnded { _ in + guard !store.isEditing else { return } withAnimation(.spring(response: 0.2, dampingFraction: 0.94)) { resetDragState() store.send(.view(.cardSwiped)) From ae5f3b56ccacaeaf8b5b1284d09a9db6fa10384e Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 16:30:51 +0900 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20=EB=82=98,=20=EC=83=81=EB=8C=80?= =?UTF-8?q?=EB=B0=A9=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=EB=91=98=20=EB=8B=A4?= =?UTF-8?q?=20=EC=9E=88=EC=9D=84=20=EB=95=8C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=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 --- .../Sources/Detail/GoalDetailView.swift | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 87779177..85662fba 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -274,7 +274,8 @@ private extension GoalDetailView { imageData: imageData, imageURL: imageURL, comment: comment, - showsMyEmoji: showsMyEmoji + showsMyEmoji: showsMyEmoji, + isFront: isFront ) .opacity(isFront ? 1 : 0) } @@ -286,10 +287,12 @@ private extension GoalDetailView { imageData: Data?, imageURL: String?, comment: String, - showsMyEmoji: Bool + showsMyEmoji: Bool, + isFront: Bool ) -> some View { if isCompleted { completedImageCard( + isFront: isFront, imageData: imageData, imageURL: imageURL, comment: comment, @@ -325,6 +328,7 @@ private extension GoalDetailView { @ViewBuilder func completedImageCard( + isFront: Bool, imageData: Data?, imageURL: String?, comment: String, @@ -332,14 +336,22 @@ private extension GoalDetailView { ) -> some View { if let imageData, let editedImage = UIImage(data: imageData) { - completedImageCardContainer(comment: comment, showsMyEmoji: showsMyEmoji) { + completedImageCardContainer( + comment: comment, + showsMyEmoji: showsMyEmoji, + isFront: isFront + ) { Image(uiImage: editedImage) .resizable() .scaledToFill() } } else if let imageURL, let url = URL(string: imageURL) { - completedImageCardContainer(comment: comment, showsMyEmoji: showsMyEmoji) { + completedImageCardContainer( + comment: comment, + showsMyEmoji: showsMyEmoji, + isFront: isFront + ) { KFImage(url) .resizable() .scaledToFill() @@ -406,6 +418,7 @@ private extension GoalDetailView { func completedImageCardContainer( comment: String, showsMyEmoji: Bool, + isFront: Bool, @ViewBuilder content: @escaping () -> Content ) -> some View { let shape = RoundedRectangle(cornerRadius: 20) @@ -421,7 +434,7 @@ private extension GoalDetailView { .overlay(dimmedView) .clipShape(shape) .overlay(alignment: .bottom) { - if !comment.isEmpty { + if isFront, !comment.isEmpty { commentCircle(comment: comment) .padding(.bottom, 26) } From 8a5d088897f550ef1c43cd528afc1ae5a7302e43 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 19:49:07 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20TXCommentCircle=20safeAreaPadding=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20-=20#330?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ProofPhoto/ProofPhotoView.swift | 3 +-- .../Components/TextField/TXCommentCircle.swift | 12 +----------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift b/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift index 19d70e8b..25066860 100644 --- a/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift +++ b/Projects/Feature/ProofPhoto/Sources/ProofPhoto/ProofPhotoView.swift @@ -347,7 +347,7 @@ private extension ProofPhotoView { } .padding(.bottom, 28) .frame(width: rectFrame.width, height: rectFrame.height, alignment: .bottom) - .offset(x: posX, y: posY) + .offset(x: posX, y: posY - keyboardInset) .animation(.easeOut(duration: 0.25), value: keyboardInset) } } @@ -362,7 +362,6 @@ private extension ProofPhotoView { TXCommentCircle( commentText: $store.commentText, isEditable: true, - keyboardInset: keyboardInset, isFocused: $store.isCommentFocused, onFocused: { isFocused in store.send(.view(.focusChanged(isFocused))) diff --git a/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift b/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift index c0f633e3..ab83ae44 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/TextField/TXCommentCircle.swift @@ -12,7 +12,6 @@ public struct TXCommentCircle: View { @Binding private var commentText: String @FocusState private var isFocused: Bool private let isEditable: Bool - private let keyboardInset: CGFloat private let externalFocus: Binding? public var onFocused: ((Bool) -> Void)? @@ -28,13 +27,11 @@ public struct TXCommentCircle: View { public init( commentText: Binding, isEditable: Bool, - keyboardInset: CGFloat, isFocused: Binding? = nil, onFocused: ((Bool) -> Void)? = nil ) { self._commentText = commentText self.isEditable = isEditable - self.keyboardInset = keyboardInset self.externalFocus = isFocused self.onFocused = onFocused } @@ -55,12 +52,6 @@ public struct TXCommentCircle: View { commentText = String(commentText.prefix(Constants.maxCount)) } } - .safeAreaInset(edge: .bottom) { - if isFocused { - Color.clear - .frame(height: keyboardInset) - } - } .onChange(of: isFocused) { onFocused?(isFocused) externalFocus?.wrappedValue = isFocused @@ -189,7 +180,6 @@ private struct PositionedCircleShape: Shape { @Previewable @State var text: String = "" TXCommentCircle( commentText: $text, - isEditable: true, - keyboardInset: .zero + isEditable: true ) } From f7fe5c3a34eceff96e4614d6bcc01325b4409534 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 19:49:40 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=ED=82=A4=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EC=98=AC=EB=9D=BC=EC=98=AC=20=EB=95=8C=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?View=20=EB=B0=80=EB=A6=AC=EB=8A=94=20=EC=9D=B4=EC=8A=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20#330?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 133 ++++++++++-------- 1 file changed, 76 insertions(+), 57 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 85662fba..3c1d42fd 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -62,31 +62,23 @@ public struct GoalDetailView: View { } public var body: some View { - VStack(spacing: 0) { - navigationBar - .zIndex(1) - - if store.item != nil { - cardView - .padding(.horizontal, 27) - .padding(.top, isSEDevice ? 47 : 103) - - if store.isCompleted { - completedBottomContent - } else if store.currentCompletedGoal?.status != .completed { - bottomButton - .padding(.top, 105) - .overlay(alignment: .bottomLeading) { - pokeImage - .offset(x: 79, y: -45) - } + GeometryReader { _ in + ZStack { + mainContent + + if store.isEditing && store.isCommentFocused { + dimmedView + .ignoresSafeArea() + } + + if shouldShowCommentOverlay { + floatingCommentOverlay } } - - Spacer() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } .ignoresSafeArea(.keyboard) - .background(dimmedView) + .background(Color.Common.white) .toolbar(.hidden, for: .navigationBar) .observeKeyboardFrame($keyboardFrame) .onAppear { @@ -121,6 +113,32 @@ public struct GoalDetailView: View { // MARK: - SubViews private extension GoalDetailView { + var mainContent: some View { + VStack(spacing: 0) { + navigationBar + .zIndex(1) + + if store.item != nil { + cardView + .padding(.horizontal, 27) + .padding(.top, isSEDevice ? 47 : 103) + + if store.isCompleted { + completedBottomContent + } else if store.currentCompletedGoal?.status != .completed { + bottomButton + .padding(.top, 105) + .overlay(alignment: .bottomLeading) { + pokeImage + .offset(x: 79, y: -45) + } + } + } + + Spacer() + } + } + var navigationBar: some View { TXNavigationBar( style: .subContent( @@ -135,7 +153,6 @@ private extension GoalDetailView { store.send(.view(.navigationBarTapped(action))) } ) - .overlay(dimmedView) } var cardView: some View { @@ -186,7 +203,6 @@ private extension GoalDetailView { isCompleted: store.myCardIsCompleted, imageData: store.pendingEditedImageData, imageURL: store.myCard?.imageUrl, - comment: store.myCard?.comment ?? "", showsMyEmoji: effectiveIsFrontMyCard && store.selectedReactionEmoji != nil ) .offset(x: cardOffset * (effectiveIsFrontMyCard ? 1 : -1)) @@ -199,7 +215,6 @@ private extension GoalDetailView { isCompleted: store.partnerCardIsCompleted, imageData: nil, imageURL: store.partnerCard?.imageUrl, - comment: store.partnerCard?.comment ?? "", showsMyEmoji: false ) .offset(x: cardOffset * (effectiveIsFrontMyCard ? -1 : 1)) @@ -252,7 +267,6 @@ private extension GoalDetailView { lineWidth: 1.6 ) .frame(width: 336, height: 336) - .overlay(dimmedView) .clipShape(shape) } @@ -262,7 +276,6 @@ private extension GoalDetailView { isCompleted: Bool, imageData: Data?, imageURL: String?, - comment: String, showsMyEmoji: Bool ) -> some View { ZStack { @@ -273,9 +286,7 @@ private extension GoalDetailView { isCompleted: isCompleted, imageData: imageData, imageURL: imageURL, - comment: comment, - showsMyEmoji: showsMyEmoji, - isFront: isFront + showsMyEmoji: showsMyEmoji ) .opacity(isFront ? 1 : 0) } @@ -286,16 +297,12 @@ private extension GoalDetailView { isCompleted: Bool, imageData: Data?, imageURL: String?, - comment: String, - showsMyEmoji: Bool, - isFront: Bool + showsMyEmoji: Bool ) -> some View { if isCompleted { completedImageCard( - isFront: isFront, imageData: imageData, imageURL: imageURL, - comment: comment, showsMyEmoji: showsMyEmoji ) } else { @@ -323,23 +330,18 @@ private extension GoalDetailView { shape: shape, lineWidth: 1.6 ) - .overlay(dimmedView) } @ViewBuilder func completedImageCard( - isFront: Bool, imageData: Data?, imageURL: String?, - comment: String, showsMyEmoji: Bool ) -> some View { if let imageData, let editedImage = UIImage(data: imageData) { completedImageCardContainer( - comment: comment, - showsMyEmoji: showsMyEmoji, - isFront: isFront + showsMyEmoji: showsMyEmoji ) { Image(uiImage: editedImage) .resizable() @@ -348,9 +350,7 @@ private extension GoalDetailView { } else if let imageURL, let url = URL(string: imageURL) { completedImageCardContainer( - comment: comment, - showsMyEmoji: showsMyEmoji, - isFront: isFront + showsMyEmoji: showsMyEmoji ) { KFImage(url) .resizable() @@ -387,22 +387,46 @@ private extension GoalDetailView { ) .perfControl(slug: "goal-detail", element: "primary-cta") } - + @ViewBuilder func commentCircle(comment: String) -> some View { - let keyboardInset = max(0, rectFrame.maxY - keyboardFrame.minY) TXCommentCircle( commentText: store.isEditing ? $store.commentText : .constant(comment), isEditable: store.isEditing, - keyboardInset: keyboardInset, isFocused: $store.isCommentFocused, onFocused: { isFocused in store.send(.view(.focusChanged(isFocused))) } ) - .animation(.easeOut(duration: 0.25), value: keyboardInset) } - + + var shouldShowCommentOverlay: Bool { + guard store.isCompleted, rectFrame != .zero else { return false } + return store.isEditing || !currentFrontComment.isEmpty + } + + var currentFrontComment: String { + if effectiveIsFrontMyCard { + return store.myCard?.comment ?? "" + } else { + return store.partnerCard?.comment ?? "" + } + } + + var floatingCommentOverlay: some View { + GeometryReader { rootGeo in + let rootFrame = rootGeo.frame(in: .global) + let posX = rectFrame.minX - rootFrame.minX + let posY = rectFrame.minY - rootFrame.minY + + commentCircle(comment: currentFrontComment) + .padding(.bottom, 26) + .frame(width: rectFrame.width, height: rectFrame.height, alignment: .bottom) + .offset(x: posX, y: posY - keyboardInset) + .animation(.easeOut(duration: 0.25), value: keyboardInset) + } + } + var dimmedView: some View { Color.Dimmed.dimmed70 .opacity(store.isEditing && store.isCommentFocused ? 1 : 0) @@ -416,9 +440,7 @@ private extension GoalDetailView { } func completedImageCardContainer( - comment: String, showsMyEmoji: Bool, - isFront: Bool, @ViewBuilder content: @escaping () -> Content ) -> some View { let shape = RoundedRectangle(cornerRadius: 20) @@ -431,14 +453,7 @@ private extension GoalDetailView { .frame(maxWidth: .infinity, maxHeight: .infinity) .clipped() } - .overlay(dimmedView) .clipShape(shape) - .overlay(alignment: .bottom) { - if isFront, !comment.isEmpty { - commentCircle(comment: comment) - .padding(.bottom, 26) - } - } .insideBorder( Color.Gray.gray500, shape: shape, @@ -515,6 +530,10 @@ private extension GoalDetailView { isCrossingDuringDrag ? !store.isFrontMyCard : store.isFrontMyCard } + var keyboardInset: CGFloat { + max(0, rectFrame.maxY - keyboardFrame.minY) + } + func repeatedCardOffset(for width: CGFloat) -> CGFloat { let maxOffset = Constants.maxCardOffset let direction: CGFloat = width >= 0 ? 1 : -1 From bbc04f2dac9b578f3beece2783a759d9056c3db0 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 20:33:31 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=EC=B0=8C=EB=A5=B4=EA=B8=B0=20disabl?= =?UTF-8?q?e=EC=83=81=ED=83=9C=EC=9D=BC=20=EB=95=8C=20=EB=88=8C=EB=9F=AC?= =?UTF-8?q?=EB=8F=84=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=82=98=EC=98=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-=20#331?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/Button/Round/TXRoundButton.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift b/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift index cbcebbb3..ea895461 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Button/Round/TXRoundButton.swift @@ -14,9 +14,7 @@ struct TXRoundButton: View { public var body: some View { if case let .round(style, size, state) = shape { Button { - if state != .disabled { - onTap() - } + onTap() } label: { ZStack { Capsule() From e08105f702c72b9a91149273002bd097bba77a05 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 20:51:30 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=ED=86=B5=EA=B3=84=20=ED=83=AD=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=84=B0=EC=B9=98=20=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20#332?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift b/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift index 7eddf617..077a57dd 100644 --- a/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift +++ b/Projects/Shared/DesignSystem/Sources/Components/Tab/TopBar/TXTopTabBar.swift @@ -43,7 +43,6 @@ struct TXTopTabBar: View { } label: { tabItem(item: item, isSelected: selectedItem == item) } - .buttonStyle(.plain) .frame(maxWidth: .infinity) } } From b856600fec36447866c5709170b4d4146a87e97a Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 1 Jun 2026 21:03:12 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EB=AA=A9=ED=91=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9D=B4=EB=AA=A8=EC=A7=80=20=EB=B2=84=ED=8A=BC,?= =?UTF-8?q?=20=EB=93=9C=EB=A1=AD=EB=8B=A4=EC=9A=B4=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=84=B0=EC=B9=98=20=EB=B2=94=EC=9C=84=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?-=20#333?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/MakeGoal/Sources/MakeGoalView.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift b/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift index fd20682d..147d9888 100644 --- a/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift +++ b/Projects/Feature/MakeGoal/Sources/MakeGoalView.swift @@ -122,7 +122,7 @@ private extension MakeGoalView { backgroundColor: Color.Common.white ) ), - onTap: { } + onTap: { store.send(.view(.emojiButtonTapped)) } ) .insideBorder( Color.Gray.gray500, @@ -177,7 +177,6 @@ private extension MakeGoalView { Spacer() if store.showPeriodCount { - valueText(store.periodCountText) dropDownButton { store.send(.view(.periodSelected)) } } } @@ -191,7 +190,6 @@ private extension MakeGoalView { Spacer() - valueText(store.startDateText) dropDownButton { store.send(.view(.startDateTapped)) } } .frame(height: 32) @@ -216,7 +214,6 @@ private extension MakeGoalView { Spacer() - valueText(store.endDateText) dropDownButton { store.send(.view(.endDateTapped)) } } .padding(.vertical, 21.5) @@ -240,11 +237,15 @@ private extension MakeGoalView { } func dropDownButton(_ action: @escaping () -> Void) -> some View { - Button { - action() - } label: { + HStack(spacing: 0) { + Text(store.startDateText) + .typography(.b2_14r) + .foregroundStyle(Color.Gray.gray500) Image.Icon.Symbol.arrow2Down } + .onTapGesture { + action() + } } func sectionTitleText(_ text: String) -> some View {