feat(creator): 시리즈 탭을 추가한다

This commit is contained in:
Yu Sung
2026-07-04 18:50:24 +09:00
parent 0dd111f5d6
commit 8c71b6883b
14 changed files with 1022 additions and 6 deletions

View File

@@ -0,0 +1,137 @@
import SwiftUI
struct CreatorChannelSeriesListItem: View {
let series: CreatorChannelSeriesResponse
let showsOwnershipProgress: Bool
let action: () -> Void
init(
series: CreatorChannelSeriesResponse,
showsOwnershipProgress: Bool,
action: @escaping () -> Void = {}
) {
self.series = series
self.showsOwnershipProgress = showsOwnershipProgress
self.action = action
}
var body: some View {
Button {
action()
} label: {
HStack(alignment: .center, spacing: SodaSpacing.s14) {
thumbnail
VStack(alignment: .leading, spacing: 0) {
titleArea
Spacer(minLength: SodaSpacing.s8)
if showsOwnershipProgress,
let purchasedContentCount = series.purchasedContentCount,
let paidContentCount = series.paidContentCount,
let purchasedPaidContentRate = series.purchasedPaidContentRate {
CreatorChannelSeriesOwnershipRateView(
purchasedContentCount: purchasedContentCount,
paidContentCount: paidContentCount,
purchasedPaidContentRate: purchasedPaidContentRate
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
}
.padding(.horizontal, SodaSpacing.s20)
.padding(.vertical, SodaSpacing.s8)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
private var thumbnail: some View {
ZStack(alignment: .topLeading) {
DownsampledKFImage(
url: series.coverImageUrl.flatMap(URL.init(string:)),
size: CGSize(width: 122, height: 172)
)
.background(Color.gray800)
if series.isOriginal {
originalTag
}
HStack(spacing: 0) {
Spacer()
if series.isAdult {
CreatorChannelAudioAdultTag()
.padding(.top, 6)
.padding(.trailing, 6)
}
}
}
.frame(width: 122, height: 172)
.clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous))
}
private var originalTag: some View {
HStack(spacing: 4) {
Image("ic_series_original")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
Image("img_new_only")
.resizable()
.scaledToFit()
.frame(height: 14)
}
.padding(.horizontal, 8)
.padding(.vertical, 5)
.background(Color.gray900)
}
private var titleArea: some View {
VStack(alignment: .leading, spacing: 2) {
Text(series.title)
.appFont(size: 16, weight: .bold)
.foregroundColor(.white)
.lineLimit(1)
.truncationMode(.tail)
Text(subtitle)
.appFont(size: 14, weight: .medium)
.foregroundColor(Color.gray500)
.lineLimit(1)
.truncationMode(.tail)
}
}
private var subtitle: String {
let status = series.isProceeding ? I18n.CreatorChannelSeries.proceeding : I18n.CreatorChannelSeries.completed
return "\(series.publishedDaysOfWeek)\(I18n.CreatorChannelSeries.totalContentCount(series.contentCount))\(status)"
}
}
struct CreatorChannelSeriesListItem_Previews: PreviewProvider {
static var previews: some View {
CreatorChannelSeriesListItem(
series: CreatorChannelSeriesResponse(
seriesId: 1,
title: "시리즈 이름",
coverImageUrl: "https://picsum.photos/300/420",
publishedDaysOfWeek: "매주 월",
isOriginal: true,
isAdult: true,
isProceeding: false,
contentCount: 45,
purchasedContentCount: 12,
paidContentCount: 45,
purchasedPaidContentRate: 40
),
showsOwnershipProgress: true
)
.background(Color.black)
.previewLayout(.sizeThatFits)
}
}

View File

@@ -0,0 +1,60 @@
import SwiftUI
struct CreatorChannelSeriesOwnershipRateView: View {
let purchasedContentCount: Int
let paidContentCount: Int
let purchasedPaidContentRate: Int
private var progress: Double {
max(0, min(Double(purchasedPaidContentRate) / 100, 1))
}
init(
purchasedContentCount: Int,
paidContentCount: Int,
purchasedPaidContentRate: Int
) {
self.purchasedContentCount = purchasedContentCount
self.paidContentCount = paidContentCount
self.purchasedPaidContentRate = purchasedPaidContentRate
}
var body: some View {
VStack(alignment: .leading, spacing: SodaSpacing.s4) {
HStack(alignment: .center, spacing: SodaSpacing.s8) {
(
Text(purchasedContentCount.comma())
.foregroundColor(.white)
+ Text("/\(paidContentCount.comma())\(I18n.CreatorChannelSeries.contentCountUnit)")
.foregroundColor(Color.gray500)
)
.appFont(size: 14, weight: .medium)
.lineLimit(1)
Spacer(minLength: SodaSpacing.s8)
Text("\(purchasedPaidContentRate)%")
.appFont(size: 14, weight: .medium)
.foregroundColor(Color.soda400)
.lineLimit(1)
}
ProgressView(value: progress)
.progressViewStyle(.linear)
.tint(Color.soda400)
.frame(height: 4)
}
}
}
struct CreatorChannelSeriesOwnershipRateView_Previews: PreviewProvider {
static var previews: some View {
CreatorChannelSeriesOwnershipRateView(
purchasedContentCount: 12,
paidContentCount: 45,
purchasedPaidContentRate: 40
)
.background(Color.black)
.previewLayout(.sizeThatFits)
}
}