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..52d8379f 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 @@ -74,6 +73,10 @@ public struct GoalDetailView: View { if shouldShowCommentOverlay { floatingCommentOverlay } + + if store.isShowReactionBar { + reactionBar + } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } @@ -85,7 +88,6 @@ public struct GoalDetailView: View { store.send(.view(.onAppear)) } .onDisappear { - didPlayMyEmojiAppearAnimation = false myEmojiFlyingReactionEmitter.clear() store.send(.view(.onDisappear)) } @@ -107,7 +109,7 @@ public struct GoalDetailView: View { myEmojiFlyingReactionOverlay } .txToast(item: $store.toast, customPadding: 54) - .txLoading(isPresented: store.isSavingPhotoLog) + .txLoading(isPresented: store.isLoading || store.isSavingPhotoLog) } } @@ -260,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 { @@ -282,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 { @@ -538,10 +541,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 +550,7 @@ private extension GoalDetailView { height: containerHeight ) ) + store.send(.view(.myEmojiAppearAnimationPlayed)) } } @@ -615,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 } } } diff --git a/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift b/Projects/Feature/Home/Sources/Root/HomeCoordinator+Impl.swift index 88025022..12dc9b3b 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) @@ -185,13 +185,20 @@ extension HomeCoordinator { return .none case let .navigateToGoalDetail(id, owner, date): - state.routes.append(.detail) + let isAlreadyOnDetail = state.routes.last == .detail + + if !isAlreadyOnDetail { + state.routes.append(.detail) + } + state.goalDetail = .init( currentUser: owner, id: id, verificationDate: date ) - return .none + return isAlreadyOnDetail + ? .send(.goalDetail(.view(.onAppear))) + : .none case .delegate: return .none