import SwiftUI import Kingfisher struct ChannelDonationItemView: View { let item: GetChannelDonationListItem let previewLimit: Int? let isShowFullMessageOnTap: Bool @State private var isExpanded = false init( item: GetChannelDonationListItem, previewLimit: Int? = nil, isShowFullMessageOnTap: Bool = false ) { self.item = item self.previewLimit = previewLimit self.isShowFullMessageOnTap = isShowFullMessageOnTap } private var donationBackgroundColor: Color { if item.isSecret { return Color(hex: "59548f").opacity(0.8) } if item.can >= 10000 { return Color(hex: "c25264").opacity(0.8) } if item.can >= 5000 { return Color(hex: "d85e37").opacity(0.8) } if item.can >= 1000 { return Color(hex: "d38c38").opacity(0.8) } if item.can >= 500 { return Color(hex: "c25264").opacity(0.8) } if item.can >= 100 { return Color(hex: "4d6aa4").opacity(0.8) } if item.can >= 50 { return Color(hex: "2d7390").opacity(0.8) } return Color(hex: "548f7d").opacity(0.8) } var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 11) { KFImage(URL(string: item.profileUrl)) .cancelOnDisappear(true) .downsampling(size: CGSize(width: 40, height: 40)) .resizable() .scaledToFill() .frame(width: 40, height: 40) .clipShape(Circle()) VStack(alignment: .leading, spacing: 2) { Text(item.nickname) .appFont(size: 18, weight: .bold) .foregroundColor(.white) .lineLimit(1) Text(item.relativeTimeText()) .appFont(size: 14, weight: .regular) .foregroundColor(Color(hex: "78909C")) } Spacer() } highlightedMessageText(displayMessage) .appFont(size: 16, weight: .regular) .lineLimit(isExpanded ? nil : 2) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(donationBackgroundColor) .cornerRadius(16) .onTapGesture { guard isShowFullMessageOnTap else { return } guard isTruncated else { return } isExpanded = true } } } private extension ChannelDonationItemView { var normalizedMessage: String { item.message.trimmingCharacters(in: .whitespacesAndNewlines) } var displayMessage: String { guard !isExpanded else { return normalizedMessage } guard let previewLimit else { return normalizedMessage } if normalizedMessage.count > previewLimit { return String(normalizedMessage.prefix(previewLimit)) + "..." } return normalizedMessage } var isTruncated: Bool { guard let previewLimit else { return false } return normalizedMessage.count > previewLimit } func highlightedMessageText(_ message: String) -> Text { let plainCanToken = "\(item.can)캔" let commaCanToken = "\(item.can.comma())캔" let range = message.range(of: commaCanToken) ?? message.range(of: plainCanToken) guard let range else { return Text(message).foregroundColor(Color(hex: "CFD8DC")) } let prefixText = String(message[..