Skip to content
Merged
126 changes: 80 additions & 46 deletions Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand All @@ -135,7 +153,6 @@ private extension GoalDetailView {
store.send(.view(.navigationBarTapped(action)))
}
)
.overlay(dimmedView)
}

var cardView: some View {
Expand All @@ -149,6 +166,7 @@ private extension GoalDetailView {
.gesture(
DragGesture()
.onChanged { value in
guard !store.isEditing else { return }
let translation = value.translation
let width = resistedDragWidth(
for: translation.width,
Expand All @@ -169,6 +187,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))
Expand All @@ -184,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))
Expand All @@ -197,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))
Expand Down Expand Up @@ -250,7 +267,6 @@ private extension GoalDetailView {
lineWidth: 1.6
)
.frame(width: 336, height: 336)
.overlay(dimmedView)
.clipShape(shape)
}

Expand All @@ -260,7 +276,6 @@ private extension GoalDetailView {
isCompleted: Bool,
imageData: Data?,
imageURL: String?,
comment: String,
showsMyEmoji: Bool
) -> some View {
ZStack {
Expand All @@ -271,7 +286,6 @@ private extension GoalDetailView {
isCompleted: isCompleted,
imageData: imageData,
imageURL: imageURL,
comment: comment,
showsMyEmoji: showsMyEmoji
)
.opacity(isFront ? 1 : 0)
Expand All @@ -283,14 +297,12 @@ private extension GoalDetailView {
isCompleted: Bool,
imageData: Data?,
imageURL: String?,
comment: String,
showsMyEmoji: Bool
) -> some View {
if isCompleted {
completedImageCard(
imageData: imageData,
imageURL: imageURL,
comment: comment,
showsMyEmoji: showsMyEmoji
)
} else {
Expand Down Expand Up @@ -318,26 +330,28 @@ private extension GoalDetailView {
shape: shape,
lineWidth: 1.6
)
.overlay(dimmedView)
}

@ViewBuilder
func completedImageCard(
imageData: Data?,
imageURL: String?,
comment: String,
showsMyEmoji: Bool
) -> some View {
if let imageData,
let editedImage = UIImage(data: imageData) {
completedImageCardContainer(comment: comment, showsMyEmoji: showsMyEmoji) {
completedImageCardContainer(
showsMyEmoji: showsMyEmoji
) {
Image(uiImage: editedImage)
.resizable()
.scaledToFill()
}
} else if let imageURL,
let url = URL(string: imageURL) {
completedImageCardContainer(comment: comment, showsMyEmoji: showsMyEmoji) {
completedImageCardContainer(
showsMyEmoji: showsMyEmoji
) {
KFImage(url)
.resizable()
.scaledToFill()
Expand Down Expand Up @@ -373,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)
Expand All @@ -402,7 +440,6 @@ private extension GoalDetailView {
}

func completedImageCardContainer<Content: View>(
comment: String,
showsMyEmoji: Bool,
@ViewBuilder content: @escaping () -> Content
) -> some View {
Expand All @@ -416,14 +453,7 @@ private extension GoalDetailView {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
.overlay(dimmedView)
.clipShape(shape)
.overlay(alignment: .bottom) {
if !comment.isEmpty {
commentCircle(comment: comment)
.padding(.bottom, 26)
}
}
.insideBorder(
Color.Gray.gray500,
shape: shape,
Expand Down Expand Up @@ -500,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
Expand Down
10 changes: 2 additions & 8 deletions Projects/Feature/MainTab/Sources/View/MainTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public struct MainTabView: View {
}
.txToast(
item: $store.home.home.presentation.toast,
customPadding: Constants.tabBarHeight
customPadding: TXTabBarLayout.height
)
.txLoading(isPresented: isTabLoading)
}
Expand Down Expand Up @@ -94,13 +94,7 @@ private extension MainTabView {
)
.shadow(color: .black.opacity(0.16), radius: 20, x: 2, y: 1)
.padding(.trailing, 16)
.padding(.bottom, 12 + Constants.tabBarHeight)
}
}

private extension MainTabView {
enum Constants {
static let tabBarHeight: CGFloat = 58
.padding(.bottom, 12 + TXTabBarLayout.height)
}
}

Expand Down
15 changes: 8 additions & 7 deletions Projects/Feature/MakeGoal/Sources/MakeGoalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private extension MakeGoalView {
backgroundColor: Color.Common.white
)
),
onTap: { }
onTap: { store.send(.view(.emojiButtonTapped)) }
)
.insideBorder(
Color.Gray.gray500,
Expand Down Expand Up @@ -177,7 +177,6 @@ private extension MakeGoalView {
Spacer()

if store.showPeriodCount {
valueText(store.periodCountText)
dropDownButton { store.send(.view(.periodSelected)) }
}
}
Expand All @@ -191,7 +190,6 @@ private extension MakeGoalView {

Spacer()

valueText(store.startDateText)
dropDownButton { store.send(.view(.startDateTapped)) }
}
.frame(height: 32)
Expand All @@ -216,7 +214,6 @@ private extension MakeGoalView {

Spacer()

valueText(store.endDateText)
dropDownButton { store.send(.view(.endDateTapped)) }
}
.padding(.vertical, 21.5)
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -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)))
Expand Down
Loading
Loading