import SwiftUI struct MainHomeRankingCard: View { let item: MainHomeCreatorRankingItem let showRankChange: Bool let columnsPerRow: Int let onTapCreator: (Int) -> Void init( item: MainHomeCreatorRankingItem, showRankChange: Bool, columnsPerRow: Int = 1, onTapCreator: @escaping (Int) -> Void = { _ in } ) { self.item = item self.showRankChange = showRankChange self.columnsPerRow = columnsPerRow self.onTapCreator = onTapCreator } var body: some View { Button { onTapCreator(item.creatorId) } label: { GeometryReader { proxy in let side = min(proxy.size.width, proxy.size.height) ZStack(alignment: .topLeading) { profileImage(size: CGSize(width: side, height: side)) LinearGradient( colors: [Color.black.opacity(0), Color.black.opacity(0.72)], startPoint: .top, endPoint: .bottom ) rankColumn(side: side) .padding(.leading, rankColumnLeadingPadding(side)) .padding(.top, rankColumnTopPadding(side)) VStack { Spacer() Text(item.nickname) .appFont(size: nicknameFontSize(side), weight: .bold) .foregroundColor(.white) .lineLimit(1) .truncationMode(.tail) .frame(maxWidth: .infinity, alignment: .center) .padding(.horizontal, nicknameHorizontalPadding(side)) .padding(.bottom, nicknameBottomPadding(side)) } } .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) } .aspectRatio(1, contentMode: .fit) } .buttonStyle(.plain) } private func profileImage(size: CGSize) -> some View { DownsampledKFImage(url: URL(string: item.profileImageUrl ?? ""), size: size) .background(Color.gray800) } private func rankText(side: CGFloat) -> some View { Text("\(item.rank)") .appFont(size: rankFontSize(side), family: .pattaya) .foregroundStyle( LinearGradient( colors: [.white, Color.gray200], startPoint: .top, endPoint: .bottom ) ) .shadow(color: Color.black.opacity(0.48), radius: 2) } private func rankColumn(side: CGFloat) -> some View { VStack(alignment: .center, spacing: rankColumnSpacing) { rankText(side: side) .frame(maxWidth: .infinity, alignment: .center) MainHomeRankChangeBadge( showRankChange: showRankChange, rankChange: item.rankChange, isNew: item.isNew ) .frame(maxWidth: .infinity, alignment: .center) } .frame(width: rankColumnWidth(side), alignment: .center) } private var rankColumnSpacing: CGFloat { switch columnsPerRow { case 1: return -20 case 2: return -10 case 3: return -5 default: return -20 } } private func rankFontSize(_ side: CGFloat) -> CGFloat { max(32, side * 0.26) } private func nicknameFontSize(_ side: CGFloat) -> CGFloat { max(14, side * 0.085) } private func rankColumnWidth(_ side: CGFloat) -> CGFloat { max(49, side * 0.19) } private func rankColumnLeadingPadding(_ side: CGFloat) -> CGFloat { max(0, side * 0.035) } private func rankColumnTopPadding(_ side: CGFloat) -> CGFloat { max(-8, side * 0.02) } private func nicknameHorizontalPadding(_ side: CGFloat) -> CGFloat { max(SodaSpacing.s12, side * 0.11) } private func nicknameBottomPadding(_ side: CGFloat) -> CGFloat { max(SodaSpacing.s16, side * 0.09) } } struct MainHomeRankingCard_Previews: PreviewProvider { private static let previewImageUrl = "https://picsum.photos/500/500" static var previews: some View { VStack(spacing: SodaSpacing.s20) { MainHomeRankingCard( item: previewItem(rank: 1, rankChange: 5, isNew: false), showRankChange: true ) .frame(width: 374) HStack(spacing: SodaSpacing.s8) { MainHomeRankingCard( item: previewItem(rank: 2, rankChange: -3, isNew: false), showRankChange: true, columnsPerRow: 2 ) MainHomeRankingCard( item: previewItem(rank: 3, rankChange: 0, isNew: false), showRankChange: true, columnsPerRow: 2 ) } HStack(spacing: SodaSpacing.s8) { MainHomeRankingCard( item: previewItem(rank: 8, rankChange: nil, isNew: true), showRankChange: true, columnsPerRow: 3 ) MainHomeRankingCard( item: previewItem(rank: 9, rankChange: 2, isNew: false), showRankChange: false, columnsPerRow: 3 ) MainHomeRankingCard( item: previewItem(rank: 10, rankChange: nil, isNew: false), showRankChange: true, columnsPerRow: 3 ) } } .padding(SodaSpacing.s20) .background(Color.black) .previewLayout(.sizeThatFits) } private static func previewItem(rank: Int, rankChange: Int?, isNew: Bool) -> MainHomeCreatorRankingItem { MainHomeCreatorRankingItem( rank: rank, rankChange: rankChange, isNew: isNew, creatorId: rank, nickname: "크리에이터이름", profileImageUrl: previewImageUrl ) } }