feat(live-room): 하트 롱프레스 시 가운데 빈 하트가 표시되고 물 채워지는 애니메이션 추가
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
import SDWebImageSwiftUI
|
||||
import Combine
|
||||
|
||||
struct LiveRoomViewV2: View {
|
||||
|
||||
@@ -17,6 +18,14 @@ struct LiveRoomViewV2: View {
|
||||
@State private var textHeight: CGFloat = .zero
|
||||
@State private var menuTextHeight: CGFloat = .zero
|
||||
|
||||
// 롱프레스 하트 물 채우기 상태
|
||||
@State private var isLongPressingHeart: Bool = false
|
||||
@State private var longPressStartAt: Date? = nil
|
||||
@State private var showWaterHeart: Bool = false
|
||||
@State private var waterProgress: CGFloat = 0
|
||||
@State private var wavePhase: CGFloat = 0
|
||||
let heartWaveTimer = Timer.publish(every: 1/60, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.edgesIgnoringSafeArea(.all)
|
||||
@@ -256,6 +265,29 @@ struct LiveRoomViewV2: View {
|
||||
onClick: { viewModel.likeHeart() },
|
||||
onLongPress: { viewModel.likeHeart(messageType: .BIG_HEART_DONATION, heartCount: 100) }
|
||||
)
|
||||
.onLongPressGesture(
|
||||
minimumDuration: 2.0,
|
||||
maximumDistance: 50,
|
||||
pressing: { pressing in
|
||||
if pressing {
|
||||
if !isLongPressingHeart {
|
||||
isLongPressingHeart = true
|
||||
longPressStartAt = Date()
|
||||
// 초기 상태
|
||||
waterProgress = 0
|
||||
wavePhase = 0
|
||||
}
|
||||
} else {
|
||||
isLongPressingHeart = false
|
||||
showWaterHeart = false
|
||||
waterProgress = 0
|
||||
longPressStartAt = nil
|
||||
}
|
||||
},
|
||||
perform: {
|
||||
// perform는 내부 컴포넌트(onLongPress)에서 처리하므로 여기서는 중복 방지용으로 비워둠
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,6 +743,33 @@ struct LiveRoomViewV2: View {
|
||||
LoadingView()
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .center) {
|
||||
WaterHeartView(progress: waterProgress, show: showWaterHeart, phase: wavePhase)
|
||||
.frame(width: 280, height: 210) // 4:3 비율 유지
|
||||
.allowsHitTesting(false)
|
||||
.opacity(showWaterHeart ? 1 : 0)
|
||||
.animation(.easeInOut(duration: 0.2), value: showWaterHeart)
|
||||
}
|
||||
.onReceive(heartWaveTimer) { _ in
|
||||
guard isLongPressingHeart else { return }
|
||||
let now = Date()
|
||||
if longPressStartAt == nil { longPressStartAt = now }
|
||||
let elapsed = now.timeIntervalSince(longPressStartAt!)
|
||||
if elapsed >= 0.5 {
|
||||
if !showWaterHeart {
|
||||
withAnimation(.spring(response: 0.25, dampingFraction: 0.8)) {
|
||||
showWaterHeart = true
|
||||
}
|
||||
}
|
||||
let p = min(max((elapsed - 0.5) / 1.5, 0), 1)
|
||||
waterProgress = p
|
||||
// 파도 위상 진행
|
||||
wavePhase += 0.25
|
||||
} else {
|
||||
showWaterHeart = false
|
||||
waterProgress = 0
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.keyboard)
|
||||
.edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init())
|
||||
.sheet(
|
||||
|
||||
Reference in New Issue
Block a user