feat(creator): 채널 후원 섹션을 연결한다
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CreatorChannelDonationSection: View {
|
||||
let channelDonations: [CreatorChannelDonationResponse]
|
||||
let onSelectTab: (CreatorChannelTab) -> Void
|
||||
let onTapDonate: () -> Void
|
||||
|
||||
init(
|
||||
channelDonations: [CreatorChannelDonationResponse],
|
||||
onSelectTab: @escaping (CreatorChannelTab) -> Void,
|
||||
onTapDonate: @escaping () -> Void = {}
|
||||
) {
|
||||
self.channelDonations = channelDonations
|
||||
self.onSelectTab = onSelectTab
|
||||
self.onTapDonate = onTapDonate
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: SodaSpacing.s14) {
|
||||
SectionTitle(title: I18n.CreatorChannelHome.donation) {
|
||||
onSelectTab(.donation)
|
||||
}
|
||||
|
||||
if channelDonations.isEmpty {
|
||||
CreatorChannelDonationEmptyCard(onTapDonate: onTapDonate)
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
} else {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(alignment: .top, spacing: SodaSpacing.s4) {
|
||||
ForEach(channelDonations.indices, id: \.self) { index in
|
||||
CreatorChannelDonationCard(donation: channelDonations[index])
|
||||
.frame(width: 374)
|
||||
}
|
||||
}
|
||||
.padding(.leading, SodaSpacing.s14)
|
||||
.padding(.trailing, SodaSpacing.s14)
|
||||
}
|
||||
|
||||
Button(action: onTapDonate) {
|
||||
HStack(spacing: SodaSpacing.s6) {
|
||||
Image("ic_new_donation")
|
||||
.resizable()
|
||||
.renderingMode(.template)
|
||||
.foregroundColor(.white)
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text(I18n.ContentDetail.DonationDialog.title)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(SodaSpacing.s12)
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color.white.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
}
|
||||
}
|
||||
.padding(.top, SodaSpacing.s20)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CreatorChannelDonationEmptyCard: View {
|
||||
let onTapDonate: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("처음으로 크리에이터를\n후원해 보세요!")
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineSpacing(0)
|
||||
.padding(.top, SodaSpacing.s20)
|
||||
.padding(.leading, SodaSpacing.s20)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Button(action: onTapDonate) {
|
||||
HStack(spacing: SodaSpacing.s6) {
|
||||
Image("ic_new_donation")
|
||||
.resizable()
|
||||
.renderingMode(.template)
|
||||
.foregroundColor(.black)
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text(I18n.ContentDetail.DonationDialog.title)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(.black)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(SodaSpacing.s12)
|
||||
.background(Color.white)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.horizontal, SodaSpacing.s20)
|
||||
.padding(.bottom, SodaSpacing.s20)
|
||||
}
|
||||
.frame(height: 196)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color.gray900)
|
||||
.clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
private struct CreatorChannelDonationCard: View {
|
||||
let donation: CreatorChannelDonationResponse
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: SodaSpacing.s14) {
|
||||
header
|
||||
|
||||
Text(donation.messageText)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(2)
|
||||
.truncationMode(.tail)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
}
|
||||
.padding(.bottom, SodaSpacing.s14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color.gray900)
|
||||
.clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous))
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack(alignment: .center, spacing: SodaSpacing.s8) {
|
||||
DownsampledKFImage(
|
||||
url: URL(string: donation.profileImageUrl),
|
||||
size: CGSize(width: 42, height: 42)
|
||||
)
|
||||
.background(Color.white.opacity(0.6))
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(donation.nickname)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(.black)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Text(donation.relativeTimeText())
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color.gray700)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
canBadge
|
||||
}
|
||||
.padding(SodaSpacing.s14)
|
||||
.background(donation.tierColor)
|
||||
}
|
||||
|
||||
private var canBadge: some View {
|
||||
HStack(spacing: 2) {
|
||||
Image("ic_bar_cash")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 18, height: 18)
|
||||
|
||||
Text("\(donation.can)캔")
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s6)
|
||||
.padding(.vertical, SodaSpacing.s4)
|
||||
.background(Color.gray900)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
|
||||
private extension CreatorChannelDonationResponse {
|
||||
var messageText: String {
|
||||
let trimmedMessage = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmedMessage.isEmpty ? "\(can)캔을 후원하였습니다" : trimmedMessage
|
||||
}
|
||||
|
||||
func relativeTimeText(now: Date = Date()) -> String {
|
||||
DateParser.relativeTimeText(fromUTC: createdAtUtc, fallback: createdAtUtc, now: now)
|
||||
}
|
||||
|
||||
var tierColor: Color {
|
||||
switch can {
|
||||
case 1...50:
|
||||
return Color(hex: "E2E2E2")
|
||||
case 51...100:
|
||||
return Color(hex: "73EE01")
|
||||
case 101...500:
|
||||
return Color(hex: "00EAFF")
|
||||
default:
|
||||
return Color(hex: "FF4C3C")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CreatorChannelDonationSection_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: SodaSpacing.s20) {
|
||||
CreatorChannelDonationSection(
|
||||
channelDonations: [],
|
||||
onSelectTab: { _ in }
|
||||
)
|
||||
|
||||
CreatorChannelDonationSection(
|
||||
channelDonations: [
|
||||
CreatorChannelDonationResponse(
|
||||
nickname: "팬 이름 501",
|
||||
profileImageUrl: "https://picsum.photos/120/120?4",
|
||||
can: 501,
|
||||
message: "501캔 이상 후원 컬러를 확인하는 Preview 샘플입니다.",
|
||||
createdAtUtc: "2026-07-03T03:41:00Z"
|
||||
),
|
||||
CreatorChannelDonationResponse(
|
||||
nickname: "팬 이름 50",
|
||||
profileImageUrl: "https://picsum.photos/120/120?1",
|
||||
can: 50,
|
||||
message: "크리에이터가 커뮤니티에 올린 글이 보이는 부분 크리에이터가 커뮤니티에 올린 글이 보이는 부분",
|
||||
createdAtUtc: "2026-07-03T03:44:00Z"
|
||||
),
|
||||
CreatorChannelDonationResponse(
|
||||
nickname: "팬 이름 100",
|
||||
profileImageUrl: "https://picsum.photos/120/120?2",
|
||||
can: 100,
|
||||
message: "51~100캔 후원 컬러를 확인하는 Preview 샘플입니다.",
|
||||
createdAtUtc: "2026-07-03T03:43:00Z"
|
||||
),
|
||||
CreatorChannelDonationResponse(
|
||||
nickname: "팬 이름 500",
|
||||
profileImageUrl: "https://picsum.photos/120/120?3",
|
||||
can: 500,
|
||||
message: "101~500캔 후원 컬러를 확인하는 Preview 샘플입니다.",
|
||||
createdAtUtc: "2026-07-03T03:42:00Z"
|
||||
)
|
||||
],
|
||||
onSelectTab: { _ in }
|
||||
)
|
||||
}
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user