From 8c7f8d3cb3cfaa55713bc6559824144d55cef083 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 8 Jun 2026 15:56:37 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=83=B7=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=EB=88=84=EB=A5=BC=20=EC=8B=9C=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=83=B7=20fetch=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20#352?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Root/HomeCoordinator+Impl.swift | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift b/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift index 88025022..28a78cbb 100644 --- a/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift +++ b/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift @@ -29,14 +29,14 @@ extension HomeCoordinator { // swiftlint:disable:next closure_body_length let reducer = Reduce { state, action in switch action { - case let .home(.delegate(.goToGoalDetail(id, owner, verificationDate))): - state.routes.append(.detail) - state.goalDetail = .init( - currentUser: owner, - id: id, - verificationDate: verificationDate + case let .home(.delegate(.goToGoalDetail(id, owner, date))): + return .send( + .navigateToGoalDetail( + id: id, + owner: owner, + date: date + ) ) - return .none case let .home(.delegate(.goToMakeGoal(category))): state.routes.append(.makeGoal) @@ -186,12 +186,13 @@ extension HomeCoordinator { case let .navigateToGoalDetail(id, owner, date): state.routes.append(.detail) + let shouldFetchGoalDetail = state.goalDetail != nil state.goalDetail = .init( currentUser: owner, id: id, verificationDate: date ) - return .none + return shouldFetchGoalDetail ? .send(.goalDetail(.view(.onAppear))) : .none case .delegate: return .none From 8d790c67298608818c63e443c278c05a859c72c9 Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 8 Jun 2026 16:23:15 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=83=B7=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=83=88=EB=A1=9C=20fetch=ED=96=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=9D=B4=EB=AA=A8=EC=A7=80=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=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#352?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GoalDetail/Interface/Sources/GoalDetailReducer.swift | 6 +++++- .../GoalDetail/Sources/Detail/GoalDetailReducer+Impl.swift | 4 ++++ .../Feature/GoalDetail/Sources/Detail/GoalDetailView.swift | 7 ++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Projects/Feature/GoalDetail/Interface/Sources/GoalDetailReducer.swift b/Projects/Feature/GoalDetail/Interface/Sources/GoalDetailReducer.swift index 5ea89f0b..b1f2e9c2 100644 --- a/Projects/Feature/GoalDetail/Interface/Sources/GoalDetailReducer.swift +++ b/Projects/Feature/GoalDetail/Interface/Sources/GoalDetailReducer.swift @@ -90,7 +90,10 @@ public struct GoalDetailReducer { public var isCameraPermissionAlertPresented: Bool = false public var selectedReactionEmoji: ReactionEmoji? - public var myHasEmoji: Bool { isFrontMyCard && selectedReactionEmoji != nil } + public var didPlayMyEmojiAppearAnimation: Bool = false + public var shouldShowMyEmojiAnimation: Bool { + isFrontMyCard && selectedReactionEmoji != nil && !didPlayMyEmojiAppearAnimation + } public var isShowReactionBar: Bool { !isFrontMyCard && isCompleted } public var isLoading: Bool { item == nil } public var isFetchFailed: Bool = false @@ -142,6 +145,7 @@ public struct GoalDetailReducer { case navigationBarTapped(TXNavigationBar.Action) case reactionEmojiTapped(ReactionEmoji) case cardSwiped + case myEmojiAppearAnimationPlayed case focusChanged(Bool) case dimmedBackgroundTapped case proofPhotoDismissed diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailReducer+Impl.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailReducer+Impl.swift index 407f6e78..2997eb3f 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailReducer+Impl.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailReducer+Impl.swift @@ -194,6 +194,10 @@ extension GoalDetailReducer { state.createdAt = timeFormatter.displayText(from: state.currentCard?.createdAt) return .none + + case .view(.myEmojiAppearAnimationPlayed): + state.didPlayMyEmojiAppearAnimation = true + return .none case let .view(.focusChanged(isFocused)): state.isCommentFocused = isFocused diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 9c8af088..700ba65f 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -37,7 +37,6 @@ public struct GoalDetailView: View { @State private var rectFrame: CGRect = .zero @State private var keyboardFrame: CGRect = .zero @StateObject private var myEmojiFlyingReactionEmitter = FlyingReactionEmitter() - @State private var didPlayMyEmojiAppearAnimation = false @State private var cardOffset: CGFloat = .zero @State private var isCrossingDuringDrag: Bool = false @@ -85,7 +84,6 @@ public struct GoalDetailView: View { store.send(.view(.onAppear)) } .onDisappear { - didPlayMyEmojiAppearAnimation = false myEmojiFlyingReactionEmitter.clear() store.send(.view(.onDisappear)) } @@ -538,10 +536,8 @@ private extension GoalDetailView { containerWidth: CGFloat, containerHeight: CGFloat ) { - guard store.myHasEmoji, - !didPlayMyEmojiAppearAnimation, + guard store.shouldShowMyEmojiAnimation, let selectedEmoji = store.selectedReactionEmoji else { return } - didPlayMyEmojiAppearAnimation = true myEmojiFlyingReactionEmitter.emit( emoji: selectedEmoji, config: .goalDetailBottom( @@ -549,6 +545,7 @@ private extension GoalDetailView { height: containerHeight ) ) + store.send(.view(.myEmojiAppearAnimationPlayed)) } } From e36ade17890368b2908d93ae8cacda8e9cef7f3c Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 8 Jun 2026 22:38:53 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=EC=9D=B4=EB=AA=A8=EC=A7=80=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=BD=94?= =?UTF-8?q?=EB=A9=98=ED=8A=B8=20=EC=95=9E=EC=9C=BC=EB=A1=9C=20=EC=A7=80?= =?UTF-8?q?=EB=82=98=EA=B0=80=EA=B2=8C=20=EC=88=98=EC=A0=95=20-=20#352?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Detail/GoalDetailView.swift | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 700ba65f..77690130 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -73,6 +73,10 @@ public struct GoalDetailView: View { if shouldShowCommentOverlay { floatingCommentOverlay } + + if store.isShowReactionBar { + reactionBar + } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } @@ -258,12 +262,6 @@ private extension GoalDetailView { .padding(.top, 14) .padding(.trailing, 36) } - - if store.isShowReactionBar { - reactionBar - .padding(.top, Constants.emojiTopPadding) - .padding(.horizontal, 20) - } } var createdAtText: some View { @@ -280,6 +278,13 @@ private extension GoalDetailView { store.send(.view(.reactionEmojiTapped(emoji))) } ) + .padding(.horizontal, Constants.reactionBarHorizontalPadding) + .position( + x: rectFrame.midX, + y: rectFrame.maxY + + Constants.reactionBarTopPadding + + Constants.reactionBarHeight / 2 + ) } var backgroundCard: some View { @@ -612,7 +617,9 @@ private extension GoalDetailView { static let minimumDragResistance: CGFloat = 0.35 static var cardTopPadding: CGFloat { isSEDevice ? 34 : 89 } static var cardSize: CGFloat { isSEDevice ? 321 : 336 } - static var emojiTopPadding: CGFloat { isSEDevice ? 19 : 69 } + static let reactionBarHeight: CGFloat = 77 + static let reactionBarHorizontalPadding: CGFloat = 20 + static var reactionBarTopPadding: CGFloat { isSEDevice ? 19 : 69 } } } From 0826e92bf0bc6baa58a4ad91a07a7323db491c1c Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 8 Jun 2026 23:11:22 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=83=B7=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=A1=9C=EB=94=A9=20=EC=9D=B8=EB=94=94?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80=20-=20#352?= 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, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift index 77690130..52d8379f 100644 --- a/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift +++ b/Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift @@ -109,7 +109,7 @@ public struct GoalDetailView: View { myEmojiFlyingReactionOverlay } .txToast(item: $store.toast, customPadding: 54) - .txLoading(isPresented: store.isSavingPhotoLog) + .txLoading(isPresented: store.isLoading || store.isSavingPhotoLog) } } From 84787569dc76f06d0cf722ed707f197197c7d9ab Mon Sep 17 00:00:00 2001 From: jihun Date: Mon, 15 Jun 2026 20:39:20 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=EB=B0=98=EC=98=81?= =?UTF-8?q?=20-=20#352?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Sources/Root/HomeCoordinator+Impl.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift b/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift index 28a78cbb..12dc9b3b 100644 --- a/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift +++ b/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift @@ -185,14 +185,20 @@ extension HomeCoordinator { return .none case let .navigateToGoalDetail(id, owner, date): - state.routes.append(.detail) - let shouldFetchGoalDetail = state.goalDetail != nil + let isAlreadyOnDetail = state.routes.last == .detail + + if !isAlreadyOnDetail { + state.routes.append(.detail) + } + state.goalDetail = .init( currentUser: owner, id: id, verificationDate: date ) - return shouldFetchGoalDetail ? .send(.goalDetail(.view(.onAppear))) : .none + return isAlreadyOnDetail + ? .send(.goalDetail(.view(.onAppear))) + : .none case .delegate: return .none