Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 107 additions & 55 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, Constants.cardTopPadding)

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,11 +153,12 @@ private extension GoalDetailView {
store.send(.view(.navigationBarTapped(action)))
}
)
.overlay(dimmedView)
}

var cardView: some View {
ZStack {
cardFrameReader

myCard
.zIndex(effectiveIsFrontMyCard ? 1 : 0)

Expand All @@ -149,6 +168,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,13 +189,20 @@ 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))
}
}
)
}

var cardFrameReader: some View {
Color.clear
.frame(width: Constants.cardSize, height: Constants.cardSize)
.readSize { rectFrame = $0 }
}

@ViewBuilder
var myCard: some View {
Expand All @@ -184,7 +211,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 +223,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 All @@ -218,7 +243,7 @@ private extension GoalDetailView {

if store.isShowReactionBar {
reactionBar
.padding(.top, isSEDevice ? 23 : 73)
.padding(.top, Constants.emojiTopPadding)
.padding(.horizontal, 20)
}
}
Expand Down Expand Up @@ -249,8 +274,7 @@ private extension GoalDetailView {
shape: shape,
lineWidth: 1.6
)
.frame(width: 336, height: 336)
.overlay(dimmedView)
.frame(width: Constants.cardSize, height: Constants.cardSize)
.clipShape(shape)
}

Expand All @@ -260,7 +284,6 @@ private extension GoalDetailView {
isCompleted: Bool,
imageData: Data?,
imageURL: String?,
comment: String,
showsMyEmoji: Bool
) -> some View {
ZStack {
Expand All @@ -271,7 +294,6 @@ private extension GoalDetailView {
isCompleted: isCompleted,
imageData: imageData,
imageURL: imageURL,
comment: comment,
showsMyEmoji: showsMyEmoji
)
.opacity(isFront ? 1 : 0)
Expand All @@ -283,14 +305,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 +338,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 +395,47 @@ 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 effectiveFrontCardIsCompleted, 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)
.rotationEffect(frontCardRotation)
.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,28 +449,19 @@ private extension GoalDetailView {
}

func completedImageCardContainer<Content: View>(
comment: String,
showsMyEmoji: Bool,
@ViewBuilder content: @escaping () -> Content
) -> some View {
let shape = RoundedRectangle(cornerRadius: 20)

return Color.clear
.frame(width: 336, height: 336)
.readSize { rectFrame = $0 }
.frame(width: Constants.cardSize, height: Constants.cardSize)
.overlay {
content()
.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 +538,18 @@ private extension GoalDetailView {
isCrossingDuringDrag ? !store.isFrontMyCard : store.isFrontMyCard
}

var keyboardInset: CGFloat {
max(0, rectFrame.maxY - keyboardFrame.minY)
}

var frontCardRotation: Angle {
effectiveIsFrontMyCard ? .degrees(0) : .degrees(-8)
}

var effectiveFrontCardIsCompleted: Bool {
effectiveIsFrontMyCard ? store.myCardIsCompleted : store.partnerCardIsCompleted
}

func repeatedCardOffset(for width: CGFloat) -> CGFloat {
let maxOffset = Constants.maxCardOffset
let direction: CGFloat = width >= 0 ? 1 : -1
Expand Down Expand Up @@ -531,19 +581,21 @@ private extension GoalDetailView {
cardOffset = .zero
isCrossingDuringDrag = false
}

// 다른곳에서도 쓸 때 Util로 빼기
private var isSEDevice: Bool {
UIScreen.main.bounds.height <= 667
}
}

// MARK: - Constants
private extension GoalDetailView {
enum Constants {
static var isSEDevice: Bool {
UIScreen.main.bounds.height <= 667
}

static let maxCardOffset: CGFloat = 100
static let dragVelocityThreshold: CGFloat = 1200
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 }
}
}

Expand Down
Loading
Loading