From 104e6d93ad7a45a75b293f84c52ee3ba2389922e Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 8 Oct 2026 12:32:46 +0900 Subject: [PATCH] =?UTF-8?q?feat(gift):=20=EC=84=A0=EB=AC=BC=ED=95=A8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../List/Components/GiftBoxCardView.swift | 100 ++++++++++++++++++ .../List/Components/GiftBoxEmptyView.swift | 31 ++++++ .../Components/GiftBoxInfoBannerView.swift | 30 ++++++ 3 files changed, 161 insertions(+) create mode 100644 SodaLive/Sources/V2/Gift/List/Components/GiftBoxCardView.swift create mode 100644 SodaLive/Sources/V2/Gift/List/Components/GiftBoxEmptyView.swift create mode 100644 SodaLive/Sources/V2/Gift/List/Components/GiftBoxInfoBannerView.swift diff --git a/SodaLive/Sources/V2/Gift/List/Components/GiftBoxCardView.swift b/SodaLive/Sources/V2/Gift/List/Components/GiftBoxCardView.swift new file mode 100644 index 00000000..cde48d69 --- /dev/null +++ b/SodaLive/Sources/V2/Gift/List/Components/GiftBoxCardView.swift @@ -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: ", ") + } +} diff --git a/SodaLive/Sources/V2/Gift/List/Components/GiftBoxEmptyView.swift b/SodaLive/Sources/V2/Gift/List/Components/GiftBoxEmptyView.swift new file mode 100644 index 00000000..79e5a583 --- /dev/null +++ b/SodaLive/Sources/V2/Gift/List/Components/GiftBoxEmptyView.swift @@ -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) + } +} diff --git a/SodaLive/Sources/V2/Gift/List/Components/GiftBoxInfoBannerView.swift b/SodaLive/Sources/V2/Gift/List/Components/GiftBoxInfoBannerView.swift new file mode 100644 index 00000000..3d1b7bb1 --- /dev/null +++ b/SodaLive/Sources/V2/Gift/List/Components/GiftBoxInfoBannerView.swift @@ -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) + } +}