feat(explorer): 채널 후원 목록/등록 기능을 추가한다

This commit is contained in:
Yu Sung
2026-02-25 20:57:23 +09:00
parent e9bd1e7396
commit 32d1d970e4
17 changed files with 853 additions and 58 deletions

View File

@@ -0,0 +1,136 @@
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[..<range.lowerBound])
let canText = String(message[range])
let suffixText = String(message[range.upperBound...])
return Text(prefixText).foregroundColor(Color(hex: "CFD8DC"))
+ Text(canText).foregroundColor(Color(hex: "FDCA2F"))
+ Text(suffixText).foregroundColor(Color(hex: "CFD8DC"))
}
}