feat(gift): 선물함 목록 컴포넌트를 추가한다

This commit is contained in:
Yu Sung
2026-10-08 12:32:46 +09:00
parent 9ee23677af
commit 104e6d93ad
3 changed files with 161 additions and 0 deletions
@@ -0,0 +1,100 @@
import SwiftUI
struct GiftBoxCardView: View {
let model: GiftListCardDisplayModel
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
VStack(alignment: .leading, spacing: SodaSpacing.s14) {
HStack(alignment: .top, spacing: SodaSpacing.s8) {
DownsampledKFImage(
url: profileURL,
size: CGSize(width: SodaSpacing.s20, height: SodaSpacing.s20)
)
.background(Color.gray800)
.clipShape(Circle())
.accessibilityHidden(true)
Text(model.counterpartNickname)
.appFont(.caption3)
.foregroundColor(.white)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
if let dateText {
Text(dateText)
.appFont(.caption3)
.foregroundColor(.gray500)
.multilineTextAlignment(.trailing)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(minHeight: SodaSpacing.s20)
HStack(spacing: SodaSpacing.s12) {
VStack(alignment: .leading, spacing: SodaSpacing.s4) {
Text(model.statusDisplay.title)
.appFont(.body4)
.foregroundColor(.white)
if let subtitle {
Text(subtitle)
.appFont(.caption3)
.foregroundColor(.gray400)
}
}
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
Image("ic_chevron_right")
.resizable()
.renderingMode(.template)
.scaledToFit()
.foregroundColor(.white)
.frame(width: SodaSpacing.s20, height: SodaSpacing.s20)
.accessibilityHidden(true)
}
.frame(minHeight: 39)
}
.multilineTextAlignment(.leading)
.padding(SodaSpacing.s14)
.frame(maxWidth: .infinity, minHeight: 101, alignment: .topLeading)
.background(Color.gray900)
.clipShape(RoundedRectangle(cornerRadius: SodaRadius.r14))
.contentShape(RoundedRectangle(cornerRadius: SodaRadius.r14))
}
.buttonStyle(.plain)
.accessibilityElement(children: .ignore)
.accessibilityLabel(accessibilityText)
.accessibilityAddTraits(.isButton)
.padding(.horizontal, SodaSpacing.s14)
}
private var profileURL: URL? {
guard let text = model.counterpartProfileImageUrl,
!text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
let url = URL(string: text),
let scheme = url.scheme?.lowercased(),
["https", "http"].contains(scheme),
let host = url.host, !host.isEmpty else { return nil }
return url
}
private var dateText: String? {
model.createdDateText.map { "\($0) 신청" }
}
private var subtitle: String? {
model.direction == .sent
? "\(model.sizeName) · \(model.categoryName)"
: model.statusDisplay.subtitle
}
private var accessibilityText: String {
[model.counterpartNickname, dateText, model.statusDisplay.title, subtitle,
model.direction == .received ? "\(model.sizeName) · \(model.categoryName)" : nil]
.compactMap { $0 }
.joined(separator: ", ")
}
}
@@ -0,0 +1,31 @@
import SwiftUI
struct GiftBoxEmptyView: View {
let onFeedback: () -> Void
var body: some View {
VStack(spacing: SodaSpacing.s20) {
Text("전달 예정인 선물이 없어요")
.appFont(.heading1)
.foregroundColor(.white)
Text("베타 기간에는 팬이 선물을 보내는 기능만 제공돼요.\n더 다양해진 선물 기능으로 곧 다시 만나요!")
.appFont(.body3)
.foregroundColor(.gray500)
.fixedSize(horizontal: false, vertical: true)
Button(action: onFeedback) {
Text("의견 남기기")
.appFont(.body2)
.foregroundColor(.white)
.padding(SodaSpacing.s12)
.background(Color.gray800)
.clipShape(Capsule())
}
.buttonStyle(.plain)
}
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding(SodaSpacing.s14)
}
}
@@ -0,0 +1,30 @@
import SwiftUI
struct GiftBoxInfoBannerView: View {
let direction: GiftDirection
var body: some View {
VStack(alignment: .leading, spacing: SodaSpacing.s8) {
HStack(spacing: SodaSpacing.s6) {
Image(systemName: "info.circle")
.accessibilityHidden(true)
Text(direction == .received ? "배송지 입력 기간 안내" : "사서함을 통해 안전하게 전달됩니다")
.appFont(.body4)
}
.foregroundColor(.white)
Text(direction == .received
? "알림을 받은 날부터 7일 이내에 배송지를 입력해 주세요. 기한 내 입력하지 않으면 선물이 반송됩니다."
: "크리에이터 및 팬의 주소는 공개되지 않습니다. 선물은 소다라이브 사서함에 도착한 뒤 크리에이터에게 전달됩니다.")
.appFont(.caption3)
.foregroundColor(.gray500)
.fixedSize(horizontal: false, vertical: true)
}
.padding(SodaSpacing.s14)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray900)
.clipShape(RoundedRectangle(cornerRadius: SodaRadius.r14))
.accessibilityElement(children: .combine)
.padding(.horizontal, SodaSpacing.s14)
}
}