feat(creator-profile): 라이브 섹션 UI 변경
This commit is contained in:
@@ -61,6 +61,7 @@ struct LiveRoomResponse: Decodable {
|
||||
let content: String
|
||||
let isPaid: Bool
|
||||
let beginDateTime: String
|
||||
let beginDateTimeUtc: String
|
||||
let coverImageUrl: String
|
||||
let isAdult: Bool
|
||||
let price: Int
|
||||
@@ -68,6 +69,7 @@ struct LiveRoomResponse: Decodable {
|
||||
let managerNickname: String
|
||||
let isReservation: Bool
|
||||
let isActive: Bool
|
||||
let isPrivateRoom: Bool
|
||||
}
|
||||
|
||||
struct GetAudioContentListResponse: Decodable {
|
||||
|
||||
@@ -18,163 +18,136 @@ struct UserProfileLiveView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 13.3) {
|
||||
ForEach(0..<liveRoomList.count, id: \.self) {
|
||||
let liveRoom = liveRoomList[$0]
|
||||
VStack(spacing: 13.3) {
|
||||
HStack(spacing: 20) {
|
||||
ZStack(alignment: .topLeading) {
|
||||
KFImage(URL(string: liveRoom.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(
|
||||
size: CGSize(
|
||||
width: 80,
|
||||
height: 116.7
|
||||
)
|
||||
)
|
||||
let item = liveRoomList[$0]
|
||||
let dateDic = item.beginDateTimeUtc.parseUtcIsoLocalDateTime()
|
||||
HStack(spacing: 16) {
|
||||
ZStack(alignment: .topLeading) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 107, height: 107, alignment: .top)
|
||||
.cornerRadius(16)
|
||||
.clipped()
|
||||
|
||||
if item.isPrivateRoom {
|
||||
Image("ic_lock")
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 80, height: 116.7, alignment: .center)
|
||||
.clipped()
|
||||
.cornerRadius(4.7)
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(4)
|
||||
.background(Color(hex: "333333").opacity(0.7))
|
||||
.clipShape(Circle())
|
||||
.padding(.leading, 4)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(item.managerNickname)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 18))
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Text(item.title)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text("\(dateDic["dayOfWeek"] ?? "")")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
|
||||
if !liveRoom.isActive {
|
||||
Rectangle()
|
||||
.frame(width: 80, height: 116.7, alignment: .top)
|
||||
.foregroundColor(Color.gray90.opacity(0.5))
|
||||
.cornerRadius(4.7)
|
||||
Text("|")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
|
||||
let time = dateDic["time"] ?? ""
|
||||
Text("\(item.isActive && !item.channelName.isNullOrBlank() ? "On Air" : time)")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "98A2F6"))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .trailing, spacing: 8) {
|
||||
if item.isActive && !item.channelName.isNullOrBlank() {
|
||||
Text("ON\nAIR")
|
||||
.font(.custom(Font.preBold.rawValue, size: 14))
|
||||
.foregroundColor(Color.white)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(Color(hex: "ff5c49"))
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
VStack(spacing: 0) {
|
||||
Text("\(dateDic["month"] ?? "")월")
|
||||
.font(.custom(Font.preBold.rawValue, size: 14))
|
||||
.foregroundColor(.white)
|
||||
.padding(.vertical, 6)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(hex: "FF5C49"))
|
||||
.cornerRadius(16, corners: [.topLeft, .topRight])
|
||||
|
||||
Text("\(dateDic["day"] ?? "")")
|
||||
.font(.custom(Font.preBold.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
.padding(.vertical, 6)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(hex: "FFFFFF"))
|
||||
.cornerRadius(16, corners: [.bottomLeft, .bottomRight])
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(liveRoom.beginDateTime)
|
||||
.font(.custom(Font.medium.rawValue, size: 9.3))
|
||||
.foregroundColor(Color(hex: "ffd300"))
|
||||
|
||||
Text(liveRoom.managerNickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.foregroundColor(Color.graybb)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text(liveRoom.title)
|
||||
.font(.custom(Font.medium.rawValue, size: 15.3))
|
||||
.foregroundColor(Color.graye2)
|
||||
.padding(.top, 6.7)
|
||||
.lineLimit(1)
|
||||
}
|
||||
if item.isReservation {
|
||||
Text("예약완료")
|
||||
.font(.custom(Font.preBold.rawValue, size: 12))
|
||||
.foregroundColor(Color.white)
|
||||
.padding(4)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(hex: "2E6279"))
|
||||
.cornerRadius(4)
|
||||
} else if item.price > 0 {
|
||||
HStack(spacing: 2) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 12)
|
||||
|
||||
Spacer()
|
||||
|
||||
if liveRoom.isActive {
|
||||
if liveRoom.channelName != nil {
|
||||
Text("Live")
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.foregroundColor(Color.mainRed)
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 4)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 3.3)
|
||||
.stroke(Color.mainRed, lineWidth: 1)
|
||||
)
|
||||
} else {
|
||||
Text("예정")
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.foregroundColor(Color(hex: "fdca2f"))
|
||||
.padding(.horizontal, 9)
|
||||
.padding(.vertical, 4)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 3.3)
|
||||
.stroke(Color(hex: "fdca2f"), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text("종료")
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.foregroundColor(Color.gray77)
|
||||
.padding(.horizontal, 9)
|
||||
.padding(.vertical, 4)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 3.3)
|
||||
.stroke(Color.gray77, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if liveRoom.isActive {
|
||||
if liveRoom.channelName != nil {
|
||||
if liveRoom.isPaid || liveRoom.price <= 0 {
|
||||
Text("지금 참여하기")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.white)
|
||||
.frame(
|
||||
width: screenSize().width - 26.7 - 100,
|
||||
height: 36.7
|
||||
)
|
||||
.background(Color.mainRed3)
|
||||
.cornerRadius(5.3)
|
||||
.onTapGesture {
|
||||
onClickParticipant(liveRoom)
|
||||
}
|
||||
} else {
|
||||
Text("\(liveRoom.price)캔으로 지금 참여하기")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.white)
|
||||
.frame(
|
||||
width: screenSize().width - 26.7 - 100,
|
||||
height: 36.7
|
||||
)
|
||||
.background(Color.mainRed3)
|
||||
.cornerRadius(5.3)
|
||||
.onTapGesture {
|
||||
onClickParticipant(liveRoom)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if liveRoom.isReservation {
|
||||
Text("예약완료")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.gray77)
|
||||
.frame(
|
||||
width: screenSize().width - 26.7 - 100,
|
||||
height: 36.7
|
||||
)
|
||||
.background(Color.gray52)
|
||||
.cornerRadius(5.3)
|
||||
} else {
|
||||
Text("\(liveRoom.price > 0 ? "\(liveRoom.price)캔으로 " : "")예약하기")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.black)
|
||||
.frame(
|
||||
width: screenSize().width - 26.7 - 100,
|
||||
height: 36.7
|
||||
)
|
||||
.background(Color(hex: "fdca2f"))
|
||||
.cornerRadius(5.3)
|
||||
.onTapGesture {
|
||||
onClickReservation(liveRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("다시듣기를 지원하지 않습니다")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.gray77)
|
||||
.frame(
|
||||
width: screenSize().width - 26.7 - 100,
|
||||
height: 36.7
|
||||
)
|
||||
.background(Color.gray52)
|
||||
.cornerRadius(5.3)
|
||||
Text("\(item.price)")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(4)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(hex: "3b5ff1"))
|
||||
.cornerRadius(4)
|
||||
} else {
|
||||
Text("무료")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "#263238"))
|
||||
.padding(4)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.white)
|
||||
.cornerRadius(4)
|
||||
}
|
||||
}
|
||||
.frame(width: 55)
|
||||
}
|
||||
.padding(10)
|
||||
.background(Color(hex: "263238"))
|
||||
.cornerRadius(16)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
if item.isActive && !item.channelName.isNullOrBlank() {
|
||||
onClickParticipant(item)
|
||||
} else {
|
||||
if !item.isReservation {
|
||||
onClickReservation(item)
|
||||
}
|
||||
}
|
||||
.frame(height: 116.7)
|
||||
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(Color.gray90.opacity(0.5))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,13 @@ struct UserProfileView: View {
|
||||
|
||||
if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || creatorProfile.liveRoomList.count > 0 {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
Text("라이브")
|
||||
.font(.custom(Font.preBold.rawValue, size: 26))
|
||||
.foregroundColor(Color.white)
|
||||
HStack(spacing: 0) {
|
||||
Text("라이브")
|
||||
.font(.custom(Font.preBold.rawValue, size: 26))
|
||||
.foregroundColor(Color.white)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) {
|
||||
HStack(spacing: 8) {
|
||||
@@ -193,6 +197,7 @@ struct UserProfileView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
|
||||
if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || creatorProfile.contentList.count > 0 {
|
||||
@@ -355,6 +360,7 @@ struct UserProfileView: View {
|
||||
startDateTime: viewModel.liveStartDate,
|
||||
nowDateTime: viewModel.nowDate
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
if viewModel.isShowPasswordDialog {
|
||||
@@ -460,10 +466,18 @@ struct UserProfileView: View {
|
||||
viewModel.errorMessage = message
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
.padding(.top, proxy.safeAreaInsets.top)
|
||||
.padding(.bottom, proxy.safeAreaInsets.bottom)
|
||||
.padding(.trailing, proxy.safeAreaInsets.trailing)
|
||||
.padding(.leading, proxy.safeAreaInsets.leading)
|
||||
}
|
||||
|
||||
if isShowMenuSettings {
|
||||
MenuSettingsView(isShowing: $isShowMenuSettings)
|
||||
.padding(.top, proxy.safeAreaInsets.top)
|
||||
.padding(.bottom, proxy.safeAreaInsets.bottom)
|
||||
.padding(.trailing, proxy.safeAreaInsets.trailing)
|
||||
.padding(.leading, proxy.safeAreaInsets.leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,45 @@ extension String {
|
||||
let dec = NSDecimalNumber(string: self)
|
||||
return formatter.string(from: dec) ?? "\(currencyCode) \(self)"
|
||||
}
|
||||
|
||||
func parseUtcIsoLocalDateTime() -> [String: String] {
|
||||
// 1. 서버에서 내려온 포맷: "yyyy-MM-dd'T'HH:mm:ss"
|
||||
let utcFormatter = DateFormatter()
|
||||
utcFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
|
||||
utcFormatter.locale = Locale.current
|
||||
utcFormatter.timeZone = TimeZone(abbreviation: "UTC")
|
||||
|
||||
// 2. 문자열 → Date 객체
|
||||
guard let date = utcFormatter.date(from: self) else {
|
||||
return [:] // 파싱 실패 시 빈 딕셔너리 반환
|
||||
}
|
||||
|
||||
// 3~6. 출력용 Formatter (로컬 시간 기준)
|
||||
let localFormatter = DateFormatter()
|
||||
localFormatter.locale = Locale.autoupdatingCurrent
|
||||
localFormatter.timeZone = TimeZone.current
|
||||
|
||||
// 3. 월 (1~12)
|
||||
localFormatter.dateFormat = "M"
|
||||
let month = localFormatter.string(from: date)
|
||||
|
||||
// 4. 일 (1~31)
|
||||
localFormatter.dateFormat = "d"
|
||||
let day = localFormatter.string(from: date)
|
||||
|
||||
// 5. 요일 (예: "Mon", "목")
|
||||
localFormatter.dateFormat = "E"
|
||||
let dayOfWeek = localFormatter.string(from: date)
|
||||
|
||||
// 6. 시간 (예: "AM 05:00")
|
||||
localFormatter.dateFormat = "a hh:mm"
|
||||
let time = localFormatter.string(from: date)
|
||||
|
||||
return [
|
||||
"month": month,
|
||||
"day": day,
|
||||
"dayOfWeek": dayOfWeek,
|
||||
"time": time
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,50 +117,9 @@ struct LiveReservationItemView: View {
|
||||
.background(Color(hex: "263238"))
|
||||
.cornerRadius(16)
|
||||
.onAppear {
|
||||
self.dateDic = parseUtcIsoLocalDateTime(item.beginDateTimeUtc)
|
||||
self.dateDic = item.beginDateTimeUtc.parseUtcIsoLocalDateTime()
|
||||
}
|
||||
}
|
||||
|
||||
func parseUtcIsoLocalDateTime(_ utcString: String) -> [String: String] {
|
||||
// 1. 서버에서 내려온 포맷: "yyyy-MM-dd'T'HH:mm:ss"
|
||||
let utcFormatter = DateFormatter()
|
||||
utcFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
|
||||
utcFormatter.locale = Locale.current
|
||||
utcFormatter.timeZone = TimeZone(abbreviation: "UTC")
|
||||
|
||||
// 2. 문자열 → Date 객체
|
||||
guard let date = utcFormatter.date(from: utcString) else {
|
||||
return [:] // 파싱 실패 시 빈 딕셔너리 반환
|
||||
}
|
||||
|
||||
// 3~6. 출력용 Formatter (로컬 시간 기준)
|
||||
let localFormatter = DateFormatter()
|
||||
localFormatter.locale = Locale.autoupdatingCurrent
|
||||
localFormatter.timeZone = TimeZone.current
|
||||
|
||||
// 3. 월 (1~12)
|
||||
localFormatter.dateFormat = "M"
|
||||
let month = localFormatter.string(from: date)
|
||||
|
||||
// 4. 일 (1~31)
|
||||
localFormatter.dateFormat = "d"
|
||||
let day = localFormatter.string(from: date)
|
||||
|
||||
// 5. 요일 (예: "Mon", "목")
|
||||
localFormatter.dateFormat = "E"
|
||||
let dayOfWeek = localFormatter.string(from: date)
|
||||
|
||||
// 6. 시간 (예: "AM 05:00")
|
||||
localFormatter.dateFormat = "a hh:mm"
|
||||
let time = localFormatter.string(from: date)
|
||||
|
||||
return [
|
||||
"month": month,
|
||||
"day": day,
|
||||
"dayOfWeek": dayOfWeek,
|
||||
"time": time
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveReservationItemView_Previews: PreviewProvider {
|
||||
|
||||
Reference in New Issue
Block a user