Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public struct GoalDetailReducer {
}
}

public var currentEditedImageData: Data? {
currentUser == .mySelf ? pendingEditedImageData : nil
}

public var canSwipeLeft: Bool {
switch entryPoint {
case .home:
Expand Down Expand Up @@ -89,7 +93,7 @@ public struct GoalDetailReducer {
}

public var isCompleted: Bool {
pendingEditedImageData != nil || currentCard?.imageUrl != nil
currentEditedImageData != nil || currentCard?.imageUrl != nil
}
public var comment: String { currentCard?.comment ?? "" }
public var naviBarRightText: String {
Expand Down Expand Up @@ -225,7 +229,7 @@ extension GoalDetailReducer.State {
return isEditing ? "다시 찍기" : "업로드하기"

case .you:
return "찔러보세요"
return "찌르기"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,41 @@ import SharedDesignSystem
import SharedUtil
import SharedUtil

private enum PokeCooldownManager {
private static let userDefaultsKey = "pokeCooldownTimestamps"
private static let cooldownInterval: TimeInterval = 3 * 60 * 60

static func remainingCooldown(goalId: Int64) -> TimeInterval? {
guard let timestamps = UserDefaults.standard.dictionary(forKey: userDefaultsKey) as? [String: TimeInterval],
let lastPokeTime = timestamps[String(goalId)] else {
return nil
}
let elapsed = Date().timeIntervalSince1970 - lastPokeTime
let remaining = cooldownInterval - elapsed
return remaining > 0 ? remaining : nil
}

static func formatRemainingTime(_ seconds: TimeInterval) -> String {
let totalMinutes = Int(ceil(seconds / 60))
let hours = totalMinutes / 60
let minutes = totalMinutes % 60

if hours > 0 && minutes > 0 {
return "\(hours)시간 \(minutes)분"
} else if hours > 0 {
return "\(hours)시간"
} else {
return "\(max(1, minutes))분"
}
}

static func recordPoke(goalId: Int64) {
var timestamps = UserDefaults.standard.dictionary(forKey: userDefaultsKey) as? [String: TimeInterval] ?? [:]
timestamps[String(goalId)] = Date().timeIntervalSince1970
UserDefaults.standard.set(timestamps, forKey: userDefaultsKey)
}
}

extension GoalDetailReducer {
// swiftlint: disable function_body_length
/// 실제 로직을 포함한 GoalDetailReducer를 생성합니다.
Expand Down Expand Up @@ -64,7 +99,21 @@ extension GoalDetailReducer {
await send(.authorizationCompleted(isAuthorized: isAuthorized))
}
}
return .none
guard state.currentUser == .you, !state.isCompleted else { return .none }
let goalId = state.currentGoalId
if let remaining = PokeCooldownManager.remainingCooldown(goalId: goalId) {
let timeText = PokeCooldownManager.formatRemainingTime(remaining)
return .send(.showToast(.warning(message: "\(timeText) 뒤에 다시 찌를 수 있어요")))
}
return .run { send in
do {
try await goalClient.pokePartner(goalId)
PokeCooldownManager.recordPoke(goalId: goalId)
await send(.showToast(.poke(message: "상대방을 찔렀어요!")))
} catch {
await send(.showToast(.warning(message: "찌르기에 실패했어요")))
}
}

case let .navigationBarTapped(action):
if case .backTapped = action {
Expand Down
40 changes: 20 additions & 20 deletions Projects/Feature/GoalDetail/Sources/Detail/GoalDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@ public struct GoalDetailView: View {
} else {
bottomButton
.padding(.top, 105)
.frame(maxWidth: .infinity)
.overlay(alignment: .topTrailing) {
.overlay(alignment: .bottomLeading) {
pokeImage
.offset(x: -20, y: -20)
.offset(x: 79, y: -45)
}
}
}
Expand Down Expand Up @@ -115,6 +114,7 @@ public struct GoalDetailView: View {
ProgressView()
}
}
.txToast(item: $store.toast, customPadding: 54)
}
}

Expand Down Expand Up @@ -160,24 +160,26 @@ private extension GoalDetailView {
.animation(.spring(response: 0.36, dampingFraction: 0.86), value: store.currentUser)
}

@ViewBuilder
var backgroundRect: some View {
RoundedRectangle(cornerRadius: 20)
.fill(Color.Gray.gray200)
.insideBorder(
Color.Gray.gray500,
shape: RoundedRectangle(cornerRadius: 20),
lineWidth: 1.6
)
.frame(maxWidth: .infinity)
.aspectRatio(1, contentMode: .fit)
.overlay(dimmedView)
.clipShape(RoundedRectangle(cornerRadius: 20))
.rotationEffect(.degrees(degree(isBackground: true)))
if !store.isEditing {
RoundedRectangle(cornerRadius: 20)
.fill(Color.Gray.gray200)
.insideBorder(
Color.Gray.gray500,
shape: RoundedRectangle(cornerRadius: 20),
lineWidth: 1.6
)
.frame(width: 336, height: 336)
.overlay(dimmedView)
.clipShape(RoundedRectangle(cornerRadius: 20))
.rotationEffect(.degrees(degree(isBackground: true)))
}
}

@ViewBuilder
var completedImageCard: some View {
if let editImageData = store.pendingEditedImageData,
if let editImageData = store.currentEditedImageData,
let editedImage = UIImage(data: editImageData) {
completedImageCardContainer {
Image(uiImage: editedImage)
Expand Down Expand Up @@ -261,8 +263,7 @@ private extension GoalDetailView {
var pokeImage: some View {
Image.Illustration.poke
.resizable()
.frame(width: 173, height: 173)
.allowsHitTesting(false)
.frame(width: 184, height: 160)
}

var bottomButton: some View {
Expand Down Expand Up @@ -307,8 +308,7 @@ private extension GoalDetailView {
let shape = RoundedRectangle(cornerRadius: 20)

return Color.clear
.frame(maxWidth: .infinity)
.aspectRatio(1, contentMode: .fit)
.frame(width: 336, height: 336)
.readSize { rectFrame = $0 }
.overlay {
content()
Expand Down
14 changes: 14 additions & 0 deletions Projects/Feature/GoalDetail/Sources/Detail/SwipeableCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,22 @@ struct SwipeableCardView<Content: View>: View {

// MARK: - Private Methods
private extension SwipeableCardView {
var isSwipeEnabled: Bool {
canSwipeLeft || canSwipeRight
}

var swipeRotation: Double {
max(-8, min(8, Double(cardOffset.width / 28)))
}

var cardSwipeGesture: some Gesture {
DragGesture(minimumDistance: 16)
.onChanged { value in
guard isSwipeEnabled else {
resetCardOffset()
return
}

let translation = value.translation

guard abs(translation.width) >= abs(translation.height) else {
Expand All @@ -74,6 +83,11 @@ private extension SwipeableCardView {
cardOffset = CGSize(width: translation.width, height: 0)
}
.onEnded { value in
guard isSwipeEnabled else {
resetCardOffset()
return
}

guard let direction = swipeDirection(for: value.translation) else {
resetCardOffset()
return
Expand Down
7 changes: 6 additions & 1 deletion Projects/Feature/Home/Sources/Home/HomeReducer+Impl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,12 @@ extension HomeReducer {
case let .setCalendarDate(date):
guard date != state.calendarDate else { return .none }

let today = TXCalendarDate()
let now = state.nowDate
let today = TXCalendarDate(
year: now.year,
month: now.month,
day: now.day
)
let calendar = Calendar(identifier: .gregorian)

state.calendarDate = date
Expand Down
Loading