Files
sodalive-ios/SodaLive/Sources/Explorer/Profile/Detail/CreatorDetailDialogView.swift

218 lines
7.8 KiB
Swift

//
// CreatorDetailDialogView.swift
// SodaLive
//
// Created by klaus on 2/25/26.
//
import SwiftUI
import UIKit
import Kingfisher
struct CreatorDetailDialogView: View {
@Binding var isShowing: Bool
let creatorDetail: GetCreatorDetailResponse?
let isLoading: Bool
@Environment(\.openURL) private var openURL
private var closeIconAssetName: String {
UIImage(named: "ic_x_white") != nil ? "ic_x_white" : "ic_close_white"
}
private var profileImageSize: CGFloat {
screenSize().width - 26.7 - 48
}
private var snsItems: [CreatorDetailSnsItem] {
guard let creatorDetail else { return [] }
var items = [CreatorDetailSnsItem]()
appendSnsItem(items: &items, iconName: "ic_sns_instagram", url: creatorDetail.instagramUrl)
appendSnsItem(items: &items, iconName: "ic_sns_fancimm", url: creatorDetail.fancimmUrl)
appendSnsItem(items: &items, iconName: "ic_sns_x", url: creatorDetail.xurl)
appendSnsItem(items: &items, iconName: "ic_sns_youtube", url: creatorDetail.youtubeUrl)
appendSnsItem(items: &items, iconName: "ic_sns_kakao", url: creatorDetail.kakaoOpenChatUrl)
return items
}
var body: some View {
ZStack {
Color.black
.opacity(0.7)
.ignoresSafeArea()
.onTapGesture {
isShowing = false
}
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) {
Spacer()
Image(closeIconAssetName)
.resizable()
.frame(width: 24, height: 24)
.contentShape(Rectangle())
.onTapGesture {
isShowing = false
}
}
.padding(.top, 24)
.padding(.trailing, 24)
VStack(alignment: .leading, spacing: 0) {
if isLoading {
LoadingView()
.frame(maxWidth: .infinity)
.frame(height: 240)
} else if let creatorDetail {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
KFImage(URL(string: creatorDetail.profileImageUrl))
.cancelOnDisappear(true)
.downsampling(size: CGSize(width: profileImageSize, height: profileImageSize))
.resizable()
.scaledToFill()
.frame(width: profileImageSize, height: profileImageSize)
.clipped()
.cornerRadius(16)
Text(creatorDetail.nickname)
.appFont(size: 32, weight: .bold)
.foregroundColor(.white)
.padding(.top, 24)
VStack(alignment: .leading, spacing: 30) {
detailSection(
title: I18n.MemberChannel.creatorDetailDebut,
value: debutDisplayValue(creatorDetail: creatorDetail)
)
detailSection(
title: I18n.MemberChannel.creatorDetailTotalLiveCount,
value: creatorDetail.activitySummary.liveCount.comma()
)
detailSection(
title: I18n.MemberChannel.creatorDetailAccumulatedLiveTime,
value: creatorDetail.activitySummary.liveTime.comma()
)
detailSection(
title: I18n.MemberChannel.creatorDetailAccumulatedParticipants,
value: creatorDetail.activitySummary.liveContributorCount.comma()
)
detailSection(
title: I18n.MemberChannel.creatorDetailRegisteredContentCount,
value: creatorDetail.activitySummary.contentCount.comma()
)
if !snsItems.isEmpty {
snsSection(items: snsItems)
}
}
.padding(.top, 30)
}
}
.frame(maxHeight: screenSize().height * 0.72)
}
}
.padding(.horizontal, 24)
.padding(.top, 12)
.padding(.bottom, 24)
}
.background(Color.gray22)
.cornerRadius(8)
.padding(.horizontal, 13.3)
}
}
@ViewBuilder
private func detailSection(title: String, value: String) -> some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.appFont(size: 16, weight: .medium)
.foregroundColor(Color(hex: "B0BEC5"))
Text(value)
.appFont(size: 20, weight: .medium)
.foregroundColor(.white)
}
}
@ViewBuilder
private func snsSection(items: [CreatorDetailSnsItem]) -> some View {
VStack(alignment: .leading, spacing: 16) {
Text(I18n.MemberChannel.creatorDetailSns)
.appFont(size: 16, weight: .medium)
.foregroundColor(Color(hex: "B0BEC5"))
HStack(spacing: 12) {
ForEach(items) { item in
Image(item.iconName)
.resizable()
.frame(width: 32, height: 32)
.contentShape(Rectangle())
.onTapGesture {
openSnsLink(item.url)
}
}
}
}
}
private func debutDisplayValue(creatorDetail: GetCreatorDetailResponse) -> String {
let debutDate = creatorDetail.debutDate.trimmingCharacters(in: .whitespacesAndNewlines)
let dday = creatorDetail.dday.trimmingCharacters(in: .whitespacesAndNewlines)
guard !debutDate.isEmpty, !dday.isEmpty else {
return I18n.MemberChannel.preDebut
}
return "\(debutDate) (\(dday))"
}
private func appendSnsItem(items: inout [CreatorDetailSnsItem], iconName: String, url: String) {
let trimmed = url.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else {
return
}
items.append(CreatorDetailSnsItem(iconName: iconName, url: trimmed))
}
private func openSnsLink(_ urlString: String) {
guard let url = normalizedUrl(urlString) else {
return
}
openURL(url)
}
private func normalizedUrl(_ urlString: String) -> URL? {
let trimmed = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else {
return nil
}
if trimmed.hasPrefix("http://") || trimmed.hasPrefix("https://") {
return URL(string: trimmed)
}
return URL(string: "https://\(trimmed)")
}
}
private struct CreatorDetailSnsItem: Identifiable {
let iconName: String
let url: String
var id: String { iconName }
}