import SwiftUI struct AudioContentThumbnailCardItem: Identifiable { let id: Int let title: String let subtitle: String let imageUrl: String? let price: Int let isAdult: Bool let isPointAvailable: Bool let isFirstContent: Bool let isOriginalSeries: Bool } struct AudioContentThumbnailCard: View { let item: AudioContentThumbnailCardItem let width: CGFloat let titleTypography: SodaTypography let subtitleTypography: SodaTypography let labelHorizontalPadding: CGFloat init( item: AudioContentThumbnailCardItem, size: AudioContentCardSize ) { self.item = item self.width = size.width self.titleTypography = size.titleTypography self.subtitleTypography = size.subtitleTypography self.labelHorizontalPadding = size.labelHorizontalPadding } init( item: AudioContentThumbnailCardItem, width: CGFloat, titleTypography: SodaTypography = .heading4, subtitleTypography: SodaTypography = .body5, labelHorizontalPadding: CGFloat = 0 ) { self.item = item self.width = width self.titleTypography = titleTypography self.subtitleTypography = subtitleTypography self.labelHorizontalPadding = labelHorizontalPadding } var body: some View { VStack(alignment: .leading, spacing: SodaSpacing.s8) { thumbnail labelContent } .frame(width: width, alignment: .leading) } private var thumbnail: some View { ZStack(alignment: .topLeading) { DownsampledKFImage( url: item.imageUrl.flatMap(URL.init(string:)), size: CGSize(width: width, height: width) ) .background(Color.gray800) VStack(alignment: .leading, spacing: 0) { topTagRow Spacer(minLength: 0) bottomTagRow } } .frame(width: width, height: width) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) } private var labelContent: some View { VStack(alignment: .leading, spacing: 2) { Text(item.title) .appFont(titleTypography) .foregroundColor(.white) .lineLimit(1) .truncationMode(.tail) Text(item.subtitle) .appFont(subtitleTypography) .foregroundColor(Color.gray500) .lineLimit(1) .truncationMode(.tail) } .frame(width: width - (labelHorizontalPadding * 2), alignment: .leading) .padding(.horizontal, labelHorizontalPadding) .frame(width: width, alignment: .leading) } @ViewBuilder private var topTagRow: some View { HStack(spacing: 0) { if item.isOriginalSeries { originalTag } if item.isFirstContent { firstTag } Spacer(minLength: 0) if item.isAdult { adultTag .padding(.top, SodaSpacing.s6) .padding(.trailing, SodaSpacing.s6) } } } @ViewBuilder private var bottomTagRow: some View { HStack(spacing: 0) { if item.isPointAvailable { pointTag } if item.price == 0 { freeTag } } } private var originalTag: some View { Image("ic_content_tag_original") .resizable() .scaledToFit() .frame(width: 24, height: 24) } private var firstTag: some View { ZStack { Image("ic_content_tag_first_star") .resizable() .scaledToFit() .frame(width: 17, height: 17) } .padding(4) .background(Color(hex: "FF34B8")) } private var adultTag: some View { ZStack { Color(hex: "FF4C3C") Image("ic_new_shield_small") .resizable() .scaledToFit() .padding(2) } .frame(width: 18, height: 18) .cornerRadius(4) } private var pointTag: some View { Image("ic_content_tag_point") .resizable() .scaledToFit() .frame(width: 24, height: 24) } private var freeTag: some View { Text("무료") .appFont(size: 14, weight: .semibold) .foregroundColor(.white) .lineLimit(1) .padding(.horizontal, SodaSpacing.s4) .frame(height: 24) .background(Color.soda900) } } extension AudioContentThumbnailCardItem { init(audioCard: AudioCardResponse) { self.init( id: audioCard.audioContentId, title: audioCard.title, subtitle: audioCard.creatorNickname, imageUrl: audioCard.imageUrl, price: audioCard.price, isAdult: audioCard.isAdult, isPointAvailable: audioCard.isPointAvailable, isFirstContent: audioCard.isFirstContent, isOriginalSeries: audioCard.isOriginalSeries ) } init(contentOverviewItem: ContentOverviewItemResponse) { self.init( id: contentOverviewItem.contentId, title: contentOverviewItem.title, subtitle: contentOverviewItem.creatorNickname, imageUrl: contentOverviewItem.coverImage, price: contentOverviewItem.price, isAdult: contentOverviewItem.isAdult, isPointAvailable: contentOverviewItem.isPointAvailable, isFirstContent: contentOverviewItem.isFirstContent, isOriginalSeries: contentOverviewItem.isOriginalSeries ) } init(mainContentAudio: MainContentAudioResponse) { self.init( id: mainContentAudio.audioContentId, title: mainContentAudio.title, subtitle: mainContentAudio.creatorNickname, imageUrl: mainContentAudio.imageUrl, price: mainContentAudio.price, isAdult: mainContentAudio.isAdult, isPointAvailable: mainContentAudio.isPointAvailable, isFirstContent: mainContentAudio.isFirstContent, isOriginalSeries: mainContentAudio.isOriginalSeries ) } } struct AudioContentThumbnailCard_Previews: PreviewProvider { static var previews: some View { HStack(alignment: .top, spacing: SodaSpacing.s14) { AudioContentThumbnailCard( item: AudioContentThumbnailCardItem( id: 1, title: "오디오 콘텐츠 제목입니다", subtitle: "크리에이터명", imageUrl: "https://picsum.photos/300/300", price: 0, isAdult: true, isPointAvailable: true, isFirstContent: true, isOriginalSeries: true ), size: .medium ) AudioContentThumbnailCard( item: AudioContentThumbnailCardItem( id: 2, title: "추천 오디오", subtitle: "크리에이터명", imageUrl: "https://picsum.photos/300/300", price: 10, isAdult: false, isPointAvailable: false, isFirstContent: true, isOriginalSeries: true ), width: 170 ) } .padding(SodaSpacing.s20) .background(Color.black) .previewLayout(.sizeThatFits) } }