라이브 UI 변경

This commit is contained in:
Yu Sung 2024-01-18 17:32:14 +09:00
parent 7ce5a36172
commit 01833fbc1f
37 changed files with 1763 additions and 1135 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_notice_triangle.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -44,7 +44,7 @@ struct LiveRoomDonationDialogView: View {
Text("후원하기")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Spacer()
@ -55,7 +55,7 @@ struct LiveRoomDonationDialogView: View {
Text("\(can)")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Image("ic_forward")
}
@ -69,15 +69,15 @@ struct LiveRoomDonationDialogView: View {
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090"))
.foregroundColor(Color.gray90)
.padding(.top, 16)
TextField("몇 캔을 후원할까요?", text: $donationCan)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
.padding(13.3)
.keyboardType(.numberPad)
.background(Color(hex: "303030"))
.background(Color.gray30)
.cornerRadius(6.7)
.padding(.horizontal, 20)
.padding(.top, 16)
@ -88,7 +88,7 @@ struct LiveRoomDonationDialogView: View {
.foregroundColor(.white)
.padding(.vertical, 12.7)
.frame(width: 74)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(6.7)
.onTapGesture {
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty,
@ -106,7 +106,7 @@ struct LiveRoomDonationDialogView: View {
.foregroundColor(.white)
.padding(.vertical, 12.7)
.frame(width: 74)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(6.7)
.onTapGesture {
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty, let can = Int(donationCan) {
@ -122,7 +122,7 @@ struct LiveRoomDonationDialogView: View {
.foregroundColor(.white)
.padding(.vertical, 12.7)
.frame(width: 74)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(6.7)
.onTapGesture {
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty,
@ -140,7 +140,7 @@ struct LiveRoomDonationDialogView: View {
.foregroundColor(.white)
.padding(.vertical, 12.7)
.frame(width: 74)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(6.7)
.onTapGesture {
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty,
@ -156,7 +156,7 @@ struct LiveRoomDonationDialogView: View {
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090"))
.foregroundColor(Color.gray90)
.padding(.vertical, 18.7)
.padding(.horizontal, 20)
@ -169,14 +169,14 @@ struct LiveRoomDonationDialogView: View {
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color(hex: "bbbbbb"), lineWidth: 1)
.stroke(Color.graybb, lineWidth: 1)
)
TextField("함께 보낼 메시지 입력(최대 50자)", text: $donationMessage)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
.padding(13.3)
.background(Color(hex: "303030"))
.background(Color.gray30)
.cornerRadius(6.7)
.onReceive(Just(donationMessage)) { _ in
limitText()
@ -187,15 +187,15 @@ struct LiveRoomDonationDialogView: View {
HStack(spacing: 13.3) {
Text("취소")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.padding(.vertical, 16)
.frame(width: (screenSize().width - 53.3) / 3)
.background(Color(hex: "9970ff").opacity(0.2))
.background(Color.button.opacity(0.2))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder()
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
)
.onTapGesture {
isShowing = false
@ -206,7 +206,7 @@ struct LiveRoomDonationDialogView: View {
.foregroundColor(.white)
.padding(.vertical, 16)
.frame(width: (screenSize().width - 53.3) * 2 / 3)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(10)
.onTapGesture {
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty,
@ -224,7 +224,7 @@ struct LiveRoomDonationDialogView: View {
}
.padding(.top, 21.3)
.padding(.bottom, 16)
.background(Color(hex: "222222"))
.background(Color.gray22)
.cornerRadius(20, corners: [.topLeft, .topRight])
}
.popup(isPresented: $isShowErrorPopup, type: .toast, position: .bottom, autohideIn: 1.3) {
@ -234,7 +234,7 @@ struct LiveRoomDonationDialogView: View {
.padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.background(Color.button)
.foregroundColor(Color.white)
.multilineTextAlignment(.leading)
.cornerRadius(20)

View File

@ -25,13 +25,13 @@ struct UserProfileDonationView: View {
HStack(spacing: 0) {
Text("후원랭킹")
.font(.custom(Font.bold.rawValue, size: 16.7))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Spacer()
Text("전체보기")
.font(.custom(Font.light.rawValue, size: 11.3))
.foregroundColor(Color(hex: "bbbbbb"))
.foregroundColor(Color.graybb)
.onTapGesture {
AppState.shared.setAppStep(step: .userProfileDonationAll(userId: userId))
}
@ -71,7 +71,7 @@ struct UserProfileDonationView: View {
Text(item.nickname)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(.grayee)
.frame(width: 63)
.lineLimit(1)
}

View File

@ -23,7 +23,7 @@ struct LiveRoomChatItemView: View {
.clipShape(Circle())
case -1:
Color(hex: "6f3dec")
Color.button
.frame(width: 33.3, height: 33.3, alignment: .top)
.clipShape(Circle())
@ -119,7 +119,7 @@ struct LiveRoomChatItemView: View {
.padding(.vertical, 5.3)
.background(
UserDefaults.int(forKey: .userId) == chatMessage.userId ?
Color(hex: "9970ff").opacity(0.6) :
Color.button.opacity(0.5) :
Color.black.opacity(0.6)
)
.cornerRadius(3.3)

View File

@ -58,13 +58,13 @@ struct LiveRoomDonationChatItemView: View {
.padding(13)
.frame(width: screenSize().width - 86, alignment: .leading)
.background(
chatMessage.can >= 10000 ? Color(hex: "c25264") :
chatMessage.can >= 5000 ? Color(hex: "d85e37").opacity(0.9) :
chatMessage.can >= 1000 ? Color(hex: "d38c38").opacity(0.9) :
chatMessage.can >= 500 ? Color(hex: "59548f").opacity(0.9) :
chatMessage.can >= 100 ? Color(hex: "4d6aa4").opacity(0.9) :
chatMessage.can >= 50 ? Color(hex: "2d7390").opacity(0.9) :
Color(hex: "548f7d").opacity(0.9)
chatMessage.can >= 10000 ? Color(hex: "c25264").opacity(0.8) :
chatMessage.can >= 5000 ? Color(hex: "d85e37").opacity(0.8) :
chatMessage.can >= 1000 ? Color(hex: "d38c38").opacity(0.8) :
chatMessage.can >= 500 ? Color(hex: "59548f").opacity(0.8) :
chatMessage.can >= 100 ? Color(hex: "4d6aa4").opacity(0.8) :
chatMessage.can >= 50 ? Color(hex: "2d7390").opacity(0.8) :
Color(hex: "548f7d").opacity(0.8)
)
.cornerRadius(10)
.padding(.leading, 20)

View File

@ -15,19 +15,19 @@ struct LiveRoomJoinChatItemView: View {
HStack(spacing: 0) {
Text("'")
.font(.system(size: 12))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Text(chatMessage.nickname)
.font(.system(size: 12, weight: .bold))
.foregroundColor(Color(hex: "ffdc00"))
.foregroundColor(Color.mainYellow)
Text("'님이 입장하셨습니다.")
.font(.system(size: 12))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
}
.padding(.vertical, 6.7)
.frame(width: screenSize().width - 86)
.background(Color(hex: "3d2a6c"))
.background(Color.button.opacity(0.5))
.cornerRadius(4.7)
.padding(.leading, 20)
}

View File

@ -20,7 +20,7 @@ struct LiveRoomDonationRankingDialog: View {
HStack(spacing: 0) {
Text("현재 라이브 후원랭킹")
.font(.custom(Font.bold.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Spacer()
@ -35,16 +35,16 @@ struct LiveRoomDonationRankingDialog: View {
HStack(spacing: 0) {
Text("전체")
.font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Text("\(donationStatus.totalCount)")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.padding(.leading, 6.7)
Text("")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "777777"))
.foregroundColor(Color.gray77)
Spacer()
}
@ -66,7 +66,7 @@ struct LiveRoomDonationRankingDialog: View {
}
}
.padding(20)
.background(Color(hex: "222222"))
.background(Color.gray22)
.cornerRadius(8)
if viewModel.isLoading {
@ -80,7 +80,7 @@ struct LiveRoomDonationRankingDialog: View {
.padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.background(Color.button)
.foregroundColor(Color.white)
.multilineTextAlignment(.leading)
.cornerRadius(20)

View File

@ -15,22 +15,22 @@ struct LiveRoomDonationRankingTotalCanView: View {
HStack(alignment: .center, spacing: 0) {
Text("합계")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "d2d2d2"))
.foregroundColor(Color.grayd2)
Spacer()
Text("\(totalCan)")
.font(.custom(Font.medium.rawValue, size: 16))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
Text("")
.font(.custom(Font.medium.rawValue, size: 10.7))
.foregroundColor(Color(hex: "bbbbbb"))
.foregroundColor(Color.graybb)
.padding(.leading, 4)
}
.padding(.horizontal, 18.7)
.padding(.vertical, 10.7)
.background(Color(hex: "13181b"))
.background(Color.bg)
.cornerRadius(8)
}
}

View File

@ -43,14 +43,14 @@ struct LiveRoomNoChattingDialogView: View {
HStack(spacing: 13.3) {
Text("취소")
.font(.custom(Font.bold.rawValue, size: 15.3))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.padding(.vertical, 16)
.frame(width: (screenSize().width - 80) / 2)
.background(Color(hex: "9970ff").opacity(0.13))
.background(Color.button.opacity(0.13))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(hex: "9970ff"), lineWidth: 1)
.stroke(Color.button, lineWidth: 1)
)
.onTapGesture { cancelAction() }
@ -59,7 +59,7 @@ struct LiveRoomNoChattingDialogView: View {
.foregroundColor(Color(hex: "ffffff"))
.padding(.vertical, 16)
.frame(width: (screenSize().width - 80) / 2)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(8)
.onTapGesture { confirmAction() }
}
@ -67,7 +67,7 @@ struct LiveRoomNoChattingDialogView: View {
.padding(.top, 40)
.padding(.bottom, 16.7)
.padding(.horizontal, 16.7)
.background(Color(hex: "222222"))
.background(Color.gray22)
.cornerRadius(10)
}
}

View File

@ -46,7 +46,7 @@ struct LiveRoomProfileDialog: View {
if profileInfo.role == .LISTENER, let onClickInviteSpeaker = onClickInviteSpeaker {
Text("스피커로 초대")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(.button)
.padding(.horizontal, 15.4)
.padding(.vertical, 8.3)
.background(Color.white)
@ -61,7 +61,7 @@ struct LiveRoomProfileDialog: View {
let onClickChangeListener = onClickChangeListener {
Text("리스너로 변경")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(.button)
.padding(.horizontal, 15.4)
.padding(.vertical, 8.3)
.background(Color.white)
@ -79,7 +79,7 @@ struct LiveRoomProfileDialog: View {
}
.padding(20)
.frame(width: screenSize().width - 53.4)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(16.7)
}
}

View File

@ -18,19 +18,19 @@ struct LiveRoomProfileItemTitleView: View {
HStack(spacing: 0) {
Text(title)
.font(.custom(Font.bold.rawValue, size: 13))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
if let count = count {
Text("\(count)")
.font(.custom(Font.medium.rawValue, size: 13))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.padding(.leading, 6.7)
}
if let totalCount = totalCount {
Text("/\(totalCount > 4 ? 4 : totalCount - 1)")
.font(.custom(Font.medium.rawValue, size: 13))
.foregroundColor(Color(hex: "bbbbbb"))
.foregroundColor(Color.graybb)
}
Spacer()
@ -59,14 +59,14 @@ struct LiveRoomProfileItemMasterView: View {
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
.padding(.leading, 4)
}
.padding(.horizontal, 16.7)
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.3))
.foregroundColor(Color.gray90.opacity(0.3))
}
}
}
@ -101,7 +101,7 @@ struct LiveRoomProfileItemUserView: View {
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
.lineLimit(2)
.multilineTextAlignment(.leading)
.padding(.leading, 4)
@ -109,7 +109,7 @@ struct LiveRoomProfileItemUserView: View {
} else {
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
.lineLimit(2)
.multilineTextAlignment(.leading)
.padding(.horizontal, 10)
@ -120,14 +120,14 @@ struct LiveRoomProfileItemUserView: View {
if role == .LISTENER && isStaff {
Text("스피커로 초대")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "ffffff"))
.foregroundColor(.white)
.padding(.horizontal, 5.5)
.padding(.vertical, 12)
.background(Color(hex: "9970ff").opacity(0.3))
.background(Color.button.opacity(0.3))
.cornerRadius(6.7)
.overlay(
RoundedRectangle(cornerRadius: 6.7)
.stroke(Color(hex: "9970ff"), lineWidth: 1)
.stroke(Color.button, lineWidth: 1)
)
.onTapGesture {
onClickInviteSpeaker(userId)
@ -137,10 +137,10 @@ struct LiveRoomProfileItemUserView: View {
if role == .SPEAKER && (userId == UserDefaults.int(forKey: .userId) || isStaff) {
Text("리스너로 변경")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "ffffff"))
.foregroundColor(.white)
.padding(.horizontal, 5.5)
.padding(.vertical, 12)
.background(Color(hex: "9970ff"))
.background(Color.button)
.cornerRadius(6.7)
.onTapGesture {
onClickChangeListener(userId)
@ -150,14 +150,14 @@ struct LiveRoomProfileItemUserView: View {
if role != .MANAGER && creatorId == UserDefaults.int(forKey: .userId) {
Text("채금")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "ffffff"))
.foregroundColor(.white)
.padding(.horizontal, 5.5)
.padding(.vertical, 12)
.background(Color(hex: "9970ff").opacity(0.3))
.background(Color.button.opacity(0.3))
.cornerRadius(6.7)
.overlay(
RoundedRectangle(cornerRadius: 6.7)
.stroke(Color(hex: "9970ff"), lineWidth: 1)
.stroke(Color.button, lineWidth: 1)
)
.cornerRadius(6.7)
.padding(.leading, 10)
@ -177,7 +177,7 @@ struct LiveRoomProfileItemUserView: View {
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.3))
.foregroundColor(Color.gray90.opacity(0.3))
}
.padding(.horizontal, 16.7)
}
@ -200,7 +200,7 @@ struct LiveRoomProfileRequestSpeakerView: View {
.padding(.vertical, 8)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.stroke(Color(hex: "909090"), lineWidth: 1)
.stroke(Color.gray90, lineWidth: 1)
)
.onTapGesture {
onClickRequestSpeaker()

View File

@ -175,16 +175,16 @@ struct LiveRoomProfilesDialogView: View {
HStack(spacing: 0) {
Text("참여자")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Text("\(roomInfo.participantsCount)")
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "3bb9f1"))
.foregroundColor(Color.button)
.padding(.leading, 6.7)
Text("/\(roomInfo.totalAvailableParticipantsCount)")
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "bbbbbb"))
.foregroundColor(Color.graybb)
Spacer()
@ -204,7 +204,7 @@ struct LiveRoomProfilesDialogView: View {
}
.padding(.vertical, 26.7)
.padding(.horizontal, 13.3)
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
.background(Color.gray22.edgesIgnoringSafeArea(.all))
.cornerRadius(16.7)
if viewModel.isShowPopup {

View File

@ -31,7 +31,7 @@ struct LiveRoomUserProfileDialogView: View {
HStack(spacing: 0) {
Text("프로필")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Spacer()
@ -46,14 +46,14 @@ struct LiveRoomUserProfileDialogView: View {
HStack(spacing: 8) {
Text(userProfile.nickname)
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "eeeeee"))
.foregroundColor(Color.grayee)
Text(userProfile.gender)
.font(.custom(Font.medium.rawValue, size: 11.3))
.foregroundColor(Color(hex: "ffffff"))
.foregroundColor(.white)
.padding(.horizontal, 5.3)
.padding(.vertical, 3)
.background(Color(hex: "555555"))
.background(Color.gray55)
.cornerRadius(23.3)
if let isFollowing = userProfile.isFollowing {
@ -92,14 +92,14 @@ struct LiveRoomUserProfileDialogView: View {
if let isSpeaker = userProfile.isSpeaker {
Text(isSpeaker ? "리스너 변경" : "스피커 초대")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
)
.onTapGesture {
if isSpeaker {
@ -115,14 +115,14 @@ struct LiveRoomUserProfileDialogView: View {
if let isManager = userProfile.isManager {
Text(isManager ? "스탭 해제" : "스탭 지정")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
)
.onTapGesture {
if isManager {
@ -139,14 +139,14 @@ struct LiveRoomUserProfileDialogView: View {
(userProfile.isSpeaker != nil && userProfile.isManager != nil) {
Text("내보내기")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
)
.onTapGesture {
viewModel.kickOutId = userProfile.userId
@ -160,14 +160,14 @@ struct LiveRoomUserProfileDialogView: View {
if let _ = userProfile.isManager {
Text("3분간 채팅금지")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
)
.onTapGesture { onClickNoChatting(userProfile.userId, userProfile.nickname, userProfile.profileUrl) }
.padding(.top, 21.3)
@ -176,7 +176,7 @@ struct LiveRoomUserProfileDialogView: View {
if let _ = userProfile.isFollowing, !userProfile.tags.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(userProfile.tags)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "9970ff"))
.foregroundColor(Color.button)
.lineSpacing(3)
.padding(.top, 21.3)
}
@ -184,7 +184,7 @@ struct LiveRoomUserProfileDialogView: View {
if let _ = userProfile.isFollowing, !userProfile.introduce.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(userProfile.introduce)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "909090"))
.foregroundColor(Color.button)
.lineLimit(introduceLineLimit)
.lineSpacing(3)
.fixedSize(horizontal: false, vertical: true)
@ -203,7 +203,7 @@ struct LiveRoomUserProfileDialogView: View {
.padding(.horizontal, 13.3)
.padding(.top, 13.3)
.padding(.bottom, 20)
.background(Color(hex: "222222"))
.background(Color.gray22)
.cornerRadius(8)
if viewModel.isShowKickOutPopup {

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,6 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
private let rouletteRepository = RouletteRepository()
private var subscription = Set<AnyCancellable>()
@Published var chatMessage = ""
@Published var isSpeakerMute = false
@Published var isMute = false
@Published var role = LiveRoomMemberRole.LISTENER
@ -68,15 +67,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
}
@Published var selectedProfile: LiveRoomMember?
@Published var isShowNotice = true {
didSet {
if !isShowNotice {
isExpandNotice = false
}
}
}
@Published var isExpandNotice = false
@Published var isShowNotice = false
@Published var isShowDonationPopup = false
@ -108,6 +99,11 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
@Published var donationMessageList = [LiveRoomDonationMessage]()
@Published var donationMessageCount = 0
@Published var isShowingNewChat = false
@Published var isShowPhotoPicker = false
@Published var noticeViewWidth: CGFloat = UIFont.systemFontSize
@Published var noticeViewHeight: CGFloat = UIFont.systemFontSize
@Published var isBgOn = true
@Published var donationStatus: GetLiveRoomDonationStatusResponse?
@ -316,7 +312,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
agora.speakerMute(isSpeakerMute)
}
func sendMessage() {
func sendMessage(chatMessage: String, onSuccess: @escaping () -> Void) {
DispatchQueue.main.async {[unowned self] in
if isNoChatting {
self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다"
@ -334,7 +330,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
}
}
self.chatMessage = ""
onSuccess()
})
}
}

View File

@ -0,0 +1,40 @@
//
// LiveRoomNewChatView.swift
// SodaLive
//
// Created by klaus on 2024/01/18.
//
import SwiftUI
struct LiveRoomNewChatView: View {
let scrollToBottom: () -> Void
var body: some View {
HStack(spacing: 0) {
Spacer()
HStack(spacing: 6.7) {
Image("ic_bottom_white")
Text("새로운 채팅")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color.grayee)
}
.padding(.vertical, 8)
.padding(.horizontal, 13.3)
.background(Color.gray55.opacity(0.8))
.cornerRadius(16.7)
.padding(.bottom, 13.3)
.onTapGesture { scrollToBottom() }
Spacer()
}
}
}
struct LiveRoomNewChatView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomNewChatView {}
}
}

View File

@ -0,0 +1,43 @@
//
// LiveRoomOverlayStrokeImageButton.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomOverlayStrokeImageButton: View {
let imageName: String
let strokeColor: Color
let strokeWidth: CGFloat
let strokeCornerRadius: CGFloat
let onClick: () -> Void
var body: some View {
Image(imageName)
.padding(4)
.overlay(
RoundedRectangle(cornerRadius: strokeCornerRadius)
.stroke(
strokeColor,
lineWidth: strokeWidth
)
)
.onTapGesture { onClick() }
}
}
struct LiveRoomOverlayStrokeImageButton_Previews: PreviewProvider {
static var previews: some View {
LiveRoomOverlayStrokeImageButton(
imageName: "ic_edit",
strokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3,
onClick: {}
)
}
}

View File

@ -0,0 +1,45 @@
//
// LiveRoomOverlayStrokeTextButton.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomOverlayStrokeTextButton: View {
let text: String
let textColor: Color
let strokeColor: Color
let strokeWidth: CGFloat
let strokeCornerRadius: CGFloat
let onClick: () -> Void
var body: some View {
Text(text)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color.red)
.padding(.horizontal, 8)
.padding(.vertical, 6)
.overlay(
RoundedRectangle(cornerRadius: strokeCornerRadius)
.stroke(strokeColor, lineWidth: strokeWidth)
)
.onTapGesture { onClick() }
}
}
struct LiveRoomOverlayStrokeTextButton_Previews: PreviewProvider {
static var previews: some View {
LiveRoomOverlayStrokeTextButton(
text: "라이브 종료",
textColor: Color.mainRed,
strokeColor: Color.mainRed,
strokeWidth: 1,
strokeCornerRadius: 5.3,
onClick: {}
)
}
}

View File

@ -0,0 +1,59 @@
//
// LiveRoomOverlayStrokeTextToggleButton.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomOverlayStrokeTextToggleButton: View {
let isOn: Bool
let onText: String
let onTextColor: Color
let onStrokeColor: Color
let offText: String?
let offTextColor: Color
let offStrokeColor: Color
let strokeWidth: CGFloat
let strokeCornerRadius: CGFloat
let onClick: () -> Void
var body: some View {
Text(isOn ? onText : offText != nil && offText?.count ?? 0 > 0 ? offText! : onText)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(isOn ? onTextColor : offTextColor)
.padding(.horizontal, 8)
.padding(.vertical, 6)
.overlay(
RoundedRectangle(cornerRadius: strokeCornerRadius)
.stroke(
isOn ? onStrokeColor : offStrokeColor,
lineWidth: strokeWidth
)
)
.onTapGesture { onClick() }
}
}
struct LiveRoomOverlayStrokeTextToggleButton_Previews: PreviewProvider {
static var previews: some View {
LiveRoomOverlayStrokeTextToggleButton(
isOn: true,
onText: "배경 ON",
onTextColor: Color.button,
onStrokeColor: Color.button,
offText: "배경 OFF",
offTextColor: Color.grayee,
offStrokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3,
onClick: {}
)
}
}

View File

@ -0,0 +1,33 @@
//
// LiveRoomRightBottomButton.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomRightBottomButton: View {
let imageName: String
let onClick: () -> Void
var body: some View {
Image(imageName)
.resizable()
.frame(width: 24, height: 24)
.padding(10)
.background(Color.gray52.opacity(0.6))
.cornerRadius(10)
.onTapGesture { onClick() }
}
}
struct LiveRoomRightBottomButton_Previews: PreviewProvider {
static var previews: some View {
LiveRoomRightBottomButton(
imageName: "ic_donation",
onClick: {}
)
}
}

View File

@ -0,0 +1,31 @@
//
// TextView.swift
// SodaLive
//
// Created by klaus on 2024/01/18.
//
import SwiftUI
import UIKit
struct DetectableTextView: UIViewRepresentable {
var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isEditable = false // Make it readonly
textView.backgroundColor = .clear
textView.isScrollEnabled = true
textView.dataDetectorTypes = .link
textView.font = UIFont(name: Font.light.rawValue, size: 11.3)
textView.textColor = .white
textView.textContainer.lineFragmentPadding = 0
textView.textContainerInset = .zero
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}

View File

@ -0,0 +1,67 @@
//
// LiveRoomChatView.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomChatView: View {
let messages: [LiveRoomChat]
let getUserProfile: (Int) -> Void
var body: some View {
LazyVStack(alignment: .leading, spacing: 18) {
ForEach(0..<messages.count, id: \.self) { index in
switch (messages[index].type) {
case LiveRoomChatType.ROULETTE_DONATION:
let chatMessage = messages[index] as! LiveRoomRouletteDonationChat
LiveRoomRouletteDonationChatItemView(chatMessage: chatMessage)
case LiveRoomChatType.DONATION:
let chatMessage = messages[index] as! LiveRoomDonationChat
LiveRoomDonationChatItemView(chatMessage: chatMessage)
case LiveRoomChatType.JOIN:
let chatMessage = messages[index] as! LiveRoomJoinChat
LiveRoomJoinChatItemView(chatMessage: chatMessage)
default:
let chatMessage = messages[index] as! LiveRoomNormalChat
LiveRoomChatItemView(
chatMessage: chatMessage,
onClickProfile: {
if chatMessage.userId != UserDefaults.int(forKey: .userId) {
getUserProfile(chatMessage.userId)
}
}
)
}
}
}
}
}
struct LiveRoomChatView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomChatView(
messages: [
LiveRoomRouletteDonationChat(
profileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
nickname: "jkljkljkl",
rouletteResult: "sdfjkldfsjkl",
type: .ROULETTE_DONATION
),
LiveRoomRouletteDonationChat(
profileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
nickname: "jkljkljkl",
rouletteResult: "sdfjkldfsjkl",
type: .ROULETTE_DONATION
)
],
getUserProfile: { _ in }
)
}
}

View File

@ -0,0 +1,103 @@
//
// LiveRoomInfoCreatorView.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
import Kingfisher
struct LiveRoomInfoCreatorView: View {
let roomTitle: String
let creatorNickname: String
let creatorProfileUrl: String
let isMute: Bool
let isAdult: Bool
let isFollowing: Bool
let isActiveSpeaker: Bool
let isShowFollowingButton: Bool
let onClickFollow: () -> Void
let onClickProfile: () -> Void
var body: some View {
HStack(spacing: 5.3) {
ZStack(alignment: .center) {
KFImage(URL(string: creatorProfileUrl))
.resizable()
.frame(width: 33.3, height: 33.3)
.clipShape(Circle())
.overlay(
Circle()
.stroke(
Color.button,
lineWidth: isActiveSpeaker ? 3 : 0
)
)
.onTapGesture { onClickProfile() }
if isMute {
Image("ic_mute")
.resizable()
.frame(width: 33.3, height: 33.3)
}
}
VStack(alignment: .leading, spacing: 2.7) {
HStack(spacing: 2.7) {
if isAdult {
Text("19")
.font(.custom(Font.bold.rawValue, size: 8))
.foregroundColor(.white)
.padding(.vertical, 2.8)
.padding(.horizontal, 2)
.background(Circle().foregroundColor(Color.mainRed2))
}
Text(roomTitle)
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(.grayee)
.lineLimit(1)
}
Text(creatorNickname)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(.gray77)
}
if isShowFollowingButton {
Image(isFollowing ? "btn_select_checked" : "btn_plus_round")
.resizable()
.frame(width: 20, height: 20)
.onTapGesture { onClickFollow() }
}
}
.padding(.vertical, 8)
.padding(.horizontal, 5.3)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.stroke(Color.graybb, lineWidth: 1)
)
}
}
struct LiveRoomInfoCreatorView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomInfoCreatorView(
roomTitle: "오늘 라이브방송은 OOO 입니다.",
creatorNickname: "도화",
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
isMute: false,
isAdult: false,
isFollowing: true,
isActiveSpeaker: true,
isShowFollowingButton: true,
onClickFollow: {},
onClickProfile: {}
)
}
}

View File

@ -0,0 +1,190 @@
//
// LiveRoomInfoGuestView.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomInfoGuestView: View {
let title: String
let totalDonationCan: Int
let isOnBg: Bool
let isOnNotice: Bool
let creatorId: Int
let creatorNickname: String
let creatorProfileUrl: String
let speakerList: [LiveRoomMember]
let muteSpeakerList: [UInt]
let activeSpeakerList: [UInt]
let isFollowing: Bool
let isAdult: Bool
let onClickQuit: () -> Void
let onClickToggleBg: () -> Void
let onClickShare: () -> Void
let onClickFollow: (Bool) -> Void
let onClickProfile: (Int) -> Void
let onClickNotice: () -> Void
let onClickTotalDonation: () -> Void
var body: some View {
ZStack {
VStack(spacing: 13.3) {
HStack(spacing: 5.3) {
LiveRoomOverlayStrokeTextButton(
text: "나가기",
textColor: Color.red,
strokeColor: Color.red,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickQuit() }
Spacer()
LiveRoomOverlayStrokeTextToggleButton(
isOn: isOnBg,
onText: "배경 ON",
onTextColor: Color.button,
onStrokeColor: Color.button,
offText: "배경 OFF",
offTextColor: Color.graybb,
offStrokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickToggleBg() }
LiveRoomOverlayStrokeImageButton(
imageName: "ic_share",
strokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickShare() }
}
HStack(spacing: 8) {
LiveRoomInfoCreatorView(
roomTitle: title,
creatorNickname: creatorNickname,
creatorProfileUrl: creatorProfileUrl,
isMute: muteSpeakerList.contains(UInt(creatorId)),
isAdult: isAdult,
isFollowing: isFollowing,
isActiveSpeaker: activeSpeakerList.contains(UInt(creatorId)),
isShowFollowingButton: true,
onClickFollow: { onClickFollow(isFollowing) },
onClickProfile: { onClickProfile(creatorId) }
)
.frame(width: 180, alignment: .leading)
Spacer()
ForEach(0..<speakerList.count, id: \.self) { index in
let speaker = speakerList[index]
if speaker.id != UInt(creatorId) {
LiveRoomInfoSpeakerView(
nickname: speaker.nickname,
profileUrl: speaker.profileImage,
isMute: muteSpeakerList.contains(UInt(speaker.id)),
isActiveSpeaker: activeSpeakerList.contains(UInt(speaker.id)),
onClickProfile: { onClickProfile(speaker.id) }
)
}
}
}
HStack(spacing: 5.3) {
LiveRoomOverlayStrokeTextToggleButton(
isOn: isOnNotice,
onText: "공지",
onTextColor: .button,
onStrokeColor: .button,
offText: nil,
offTextColor: .graybb,
offStrokeColor: .graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3,
onClick: { onClickNotice() }
)
Spacer()
HStack(spacing: 2.7) {
Image("ic_can")
.resizable()
.frame(width: 12, height: 12)
Text("\(totalDonationCan)")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(.graybb)
}
.padding(.horizontal, 11)
.padding(.vertical, 5.3)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.stroke(Color.graybb, lineWidth: 1)
)
.onTapGesture { onClickTotalDonation() }
}
}
if muteSpeakerList.contains(UInt(creatorId)) {
Image("img_noti_mute")
}
}
.padding(.horizontal, 13.3)
.padding(.top, 16)
.background(Color.gray22)
}
}
struct LiveRoomInfoGuestView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomInfoGuestView(
title: "오늘의 라이브방송은 OOO입니다.",
totalDonationCan: 123456,
isOnBg: true,
isOnNotice: false,
creatorId: 1,
creatorNickname: "도화",
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
speakerList: [
LiveRoomMember(
id: 1,
nickname: "도화",
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
role: .SPEAKER
),
LiveRoomMember(
id: 2,
nickname: "청령",
profileImage: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417",
role: .SPEAKER
),
LiveRoomMember(
id: 3,
nickname: "LUNA",
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
role: .SPEAKER
),
],
muteSpeakerList: [],
activeSpeakerList: [],
isFollowing: false,
isAdult: false,
onClickQuit: {},
onClickToggleBg: {},
onClickShare: {},
onClickFollow: { _ in },
onClickProfile: { _ in },
onClickNotice: {},
onClickTotalDonation: {}
)
}
}

View File

@ -0,0 +1,217 @@
//
// LiveRoomInfoHostView.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
import Kingfisher
struct LiveRoomInfoHostView: View {
let title: String
let totalDonationCan: Int
let participantsCount: Int
let isOnBg: Bool
let isOnNotice: Bool
let creatorId: Int
let creatorNickname: String
let creatorProfileUrl: String
let speakerList: [LiveRoomMember]
let muteSpeakerList: [UInt]
let activeSpeakerList: [UInt]
let isAdult: Bool
let onClickQuit: () -> Void
let onClickToggleBg: () -> Void
let onClickShare: () -> Void
let onClickEdit: () -> Void
let onClickProfile: (Int) -> Void
let onClickNotice: () -> Void
let onClickTotalDonation: () -> Void
let onClickParticipants: () -> Void
var body: some View {
ZStack {
VStack(alignment: .leading, spacing: 13.3) {
HStack(spacing: 5.3) {
LiveRoomOverlayStrokeTextButton(
text: "라이브 종료",
textColor: Color.red,
strokeColor: Color.red,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickQuit() }
Spacer()
LiveRoomOverlayStrokeTextToggleButton(
isOn: isOnBg,
onText: "배경 ON",
onTextColor: Color.button,
onStrokeColor: Color.button,
offText: "배경 OFF",
offTextColor: Color.graybb,
offStrokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickToggleBg() }
LiveRoomOverlayStrokeImageButton(
imageName: "ic_share",
strokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickShare() }
LiveRoomOverlayStrokeImageButton(
imageName: "ic_edit",
strokeColor: Color.graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3
) { onClickEdit() }
}
HStack(spacing: 8) {
LiveRoomInfoCreatorView(
roomTitle: title,
creatorNickname: creatorNickname,
creatorProfileUrl: creatorProfileUrl,
isMute: muteSpeakerList.contains(UInt(creatorId)),
isAdult: isAdult,
isFollowing: false,
isActiveSpeaker: activeSpeakerList.contains(UInt(creatorId)),
isShowFollowingButton: false,
onClickFollow: {},
onClickProfile: {}
)
.frame(width: 180, alignment: .leading)
Spacer()
ForEach(0..<speakerList.count, id: \.self) { index in
let speaker = speakerList[index]
if speaker.id != UInt(creatorId) {
LiveRoomInfoSpeakerView(
nickname: speaker.nickname,
profileUrl: speaker.profileImage,
isMute: muteSpeakerList.contains(UInt(speaker.id)),
isActiveSpeaker: activeSpeakerList.contains(UInt(speaker.id)),
onClickProfile: { onClickProfile(speaker.id) }
)
}
}
}
HStack(spacing: 5.3) {
LiveRoomOverlayStrokeTextToggleButton(
isOn: isOnNotice,
onText: "공지",
onTextColor: .button,
onStrokeColor: .button,
offText: nil,
offTextColor: .graybb,
offStrokeColor: .graybb,
strokeWidth: 1,
strokeCornerRadius: 5.3,
onClick: { onClickNotice() }
)
Spacer()
HStack(spacing: 2.7) {
Image("ic_can")
.resizable()
.frame(width: 12, height: 12)
Text("\(totalDonationCan)")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(.graybb)
}
.padding(.horizontal, 11)
.padding(.vertical, 5.3)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.stroke(Color.graybb, lineWidth: 1)
)
.onTapGesture { onClickTotalDonation() }
HStack(spacing: 6.7) {
Text("참여자")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(.graybb)
Text("\(participantsCount)")
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(.graybb)
}
.padding(.horizontal, 11)
.padding(.vertical, 5.3)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.stroke(Color.graybb, lineWidth: 1)
)
.onTapGesture { onClickParticipants() }
}
}
if muteSpeakerList.contains(UInt(creatorId)) {
Image("img_noti_mute")
}
}
.padding(.horizontal, 13.3)
.padding(.top, 16)
.background(Color.gray22)
}
}
struct LiveRoomInfoHostView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomInfoHostView(
title: "오늘의 라이브방송은 OOO입니다.",
totalDonationCan: 123456,
participantsCount: 18,
isOnBg: true,
isOnNotice: true,
creatorId: 1,
creatorNickname: "도화",
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
speakerList: [
LiveRoomMember(
id: 1,
nickname: "도화",
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
role: .SPEAKER
),
LiveRoomMember(
id: 2,
nickname: "청령",
profileImage: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417",
role: .SPEAKER
),
LiveRoomMember(
id: 3,
nickname: "LUNA",
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
role: .SPEAKER
),
],
muteSpeakerList: [],
activeSpeakerList: [],
isAdult: false,
onClickQuit: {},
onClickToggleBg: {},
onClickShare: {},
onClickEdit: {},
onClickProfile: { _ in },
onClickNotice: {},
onClickTotalDonation: {},
onClickParticipants: {}
)
}
}

View File

@ -0,0 +1,62 @@
//
// LiveRoomInfoSpeakerView.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
import Kingfisher
struct LiveRoomInfoSpeakerView: View {
let nickname: String
let profileUrl: String
let isMute: Bool
let isActiveSpeaker: Bool
let onClickProfile: () -> Void
var body: some View {
VStack(spacing: 2.7) {
ZStack(alignment: .center) {
KFImage(URL(string: profileUrl))
.resizable()
.frame(width: 26.7, height: 26.7)
.clipShape(Circle())
.overlay(
Circle()
.stroke(
Color.button,
lineWidth: isActiveSpeaker ? 3 : 0
)
)
if isMute {
Image("ic_mute")
.resizable()
.frame(width: 30, height: 30)
}
}
Text(nickname)
.font(.custom(Font.medium.rawValue, fixedSize: 10.7))
.foregroundColor(.gray77)
}
.frame(width: 30, height: 30)
.onTapGesture { onClickProfile() }
}
}
struct LiveRoomInfoSpeakerView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomInfoSpeakerView(
nickname: "청령",
profileUrl: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417",
isMute: false,
isActiveSpeaker: true,
onClickProfile: {}
)
}
}

View File

@ -0,0 +1,55 @@
//
// LiveRoomInputChatView.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
struct LiveRoomInputChatView: View {
@State private var chatMessage = ""
let sendMessage: (String) -> Bool
var body: some View {
HStack(spacing: 6.7) {
TextField("채팅을 입력하세요", text: $chatMessage)
.autocapitalization(.none)
.disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.graybb)
.accentColor(.button)
.keyboardType(.default)
.padding(.horizontal, 13.3)
.padding(.vertical, 18.3)
.background(Color.gray22)
.cornerRadius(5.3)
.frame(maxWidth: .infinity)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.strokeBorder(lineWidth: 1)
.foregroundColor(.gray77)
)
Image("btn_message_send")
.resizable()
.frame(width: 35, height: 35)
.padding(6.7)
.onTapGesture {
if sendMessage(chatMessage) {
chatMessage = ""
}
}
}
.padding(13.3)
}
}
struct LiveRoomInputChatView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomInputChatView(sendMessage: { _ in return true })
}
}

View File

@ -0,0 +1,668 @@
//
// LiveRoomViewV2.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import SwiftUI
import Kingfisher
struct LiveRoomViewV2: View {
@StateObject var keyboardHandler = KeyboardHandler()
@StateObject var viewModel = LiveRoomViewModel()
@State private var textHeight: CGFloat = .zero
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
if let liveRoomInfo = viewModel.liveRoomInfo {
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
LiveRoomInfoHostView(
title: liveRoomInfo.title,
totalDonationCan: viewModel.totalDonationCan,
participantsCount: liveRoomInfo.participantsCount,
isOnBg: viewModel.isBgOn,
isOnNotice: viewModel.isShowNotice,
creatorId: liveRoomInfo.creatorId,
creatorNickname: liveRoomInfo.creatorNickname,
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
speakerList: liveRoomInfo.speakerList,
muteSpeakerList: viewModel.muteSpeakers,
activeSpeakerList: viewModel.activeSpeakers,
isAdult: liveRoomInfo.isAdult,
onClickQuit: {
viewModel.isShowLiveEndPopup = true
},
onClickToggleBg: {
viewModel.isBgOn.toggle()
},
onClickShare: {
viewModel.shareRoom()
},
onClickEdit: {
viewModel.isShowEditRoomInfoDialog = true
},
onClickProfile: {
if $0 != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: $0)
}
},
onClickNotice: {
viewModel.isShowNotice.toggle()
},
onClickTotalDonation: {
viewModel.isShowDonationRankingPopup = true
},
onClickParticipants: {
viewModel.isShowProfileList = true
}
)
} else {
LiveRoomInfoGuestView(
title: liveRoomInfo.title,
totalDonationCan: viewModel.totalDonationCan,
isOnBg: viewModel.isBgOn,
isOnNotice: viewModel.isShowNotice,
creatorId: liveRoomInfo.creatorId,
creatorNickname: liveRoomInfo.creatorNickname,
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
speakerList: liveRoomInfo.speakerList,
muteSpeakerList: viewModel.muteSpeakers,
activeSpeakerList: viewModel.activeSpeakers,
isFollowing: liveRoomInfo.isFollowing,
isAdult: liveRoomInfo.isAdult,
onClickQuit: {
viewModel.isShowQuitPopup = true
},
onClickToggleBg: {
viewModel.isBgOn.toggle()
},
onClickShare: {
viewModel.shareRoom()
},
onClickFollow: {
if $0 {
viewModel.creatorUnFollow()
} else {
viewModel.creatorFollow()
}
},
onClickProfile: {
if $0 != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: $0)
}
},
onClickNotice: {
viewModel.isShowNotice.toggle()
},
onClickTotalDonation: {
viewModel.isShowDonationRankingPopup = true
}
)
}
ZStack(alignment: .topLeading) {
Rectangle()
.foregroundColor(.gray22)
.frame(height: 16)
.frame(maxWidth: .infinity)
ScrollViewReader { proxy in
ZStack(alignment: .bottom) {
ZStack {
if viewModel.isBgOn {
KFImage(URL(string: liveRoomInfo.coverImageUrl))
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
Rectangle()
.foregroundColor(.black.opacity(0.25))
.frame(maxWidth: .infinity)
ScrollView(.vertical, showsIndicators: false) {
scrollObservableView
LiveRoomChatView(messages: viewModel.messages) {
if $0 != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: $0)
}
}
.frame(width: screenSize().width)
.rotationEffect(Angle(degrees: 180))
.valueChanged(value: viewModel.messageChangeFlag) { _ in
if viewModel.offset - viewModel.originOffset > (56.7 * 2) {
viewModel.isShowingNewChat = true
}
}
}
.rotationEffect(Angle(degrees: 180))
.onTapGesture { hideKeyboard() }
.onPreferenceChange(ScrollOffsetKey.self) {
viewModel.setOffset($0)
}
.padding(.bottom, 70)
}
.padding(.top, 16)
VStack(alignment: .trailing, spacing: 0) {
Spacer()
VStack(spacing: 13.3) {
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
Image("ic_roulette_settings")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.isShowRouletteSettings = true
}
} else if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) && viewModel.isActiveRoulette {
Image("ic_roulette")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.showRoulette()
}
}
if viewModel.role == .SPEAKER {
Image(viewModel.isMute ? "ic_mic_off" : "ic_mic_on")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.toggleMute()
}
}
Image(viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.toggleSpeakerMute()
}
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) &&
UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
Image("ic_donation_message_list")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.isShowDonationMessagePopup = true
}
} else {
Image("ic_donation")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.isShowDonationPopup = true
}
}
}
.padding(.trailing, 13.3)
LiveRoomInputChatView {
viewModel.sendMessage(chatMessage: $0) {
viewModel.isShowingNewChat = false
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
}
return true
}
.padding(.bottom, 10)
}
if viewModel.isShowingNewChat {
LiveRoomNewChatView{
viewModel.isShowingNewChat = false
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
}.padding(.bottom, 70)
}
}
}
if viewModel.isShowNotice {
VStack(alignment: .leading, spacing: 0) {
Image("ic_notice_triangle")
.padding(.leading, 13.3)
VStack(alignment: .leading, spacing: 8) {
Text("[방송공지]")
.font(.custom(Font.bold.rawValue, size: 11.3))
.foregroundColor(.white)
DetectableTextView(text: liveRoomInfo.notice)
.frame(
width: 280,
height: textHeight > 450 ? 450 : textHeight
)
.onAppear {
self.textHeight = self.estimatedHeight(
for: liveRoomInfo.notice,
width: 280
)
}
.onChange(of: liveRoomInfo.notice) { newText in
self.textHeight = self.estimatedHeight(
for: newText,
width: 280
)
}
}
.padding(8)
.background(Color.gray33)
.padding(.horizontal, 13.3)
.padding(.bottom, 120)
}
}
}
}
}
.popup(isPresented: $viewModel.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1.3) {
GeometryReader { geo in
HStack {
Spacer()
Text(viewModel.errorMessage)
.padding(.vertical, 13.3)
.frame(width: geo.size.width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color.button)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.cornerRadius(20)
.padding(.top, 66.7)
Spacer()
}
.onDisappear {
viewModel.quitRoom()
}
}
}
.cornerRadius(16.7, corners: [.topLeft, .topRight])
.offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0))
.onAppear {
UIApplication.shared.isIdleTimerDisabled = true
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
viewModel.getMemberCan()
viewModel.initAgoraEngine()
viewModel.getRoomInfo()
NotificationCenter.default.addObserver(
forName: UIApplication.willTerminateNotification,
object: nil,
queue: .main) { _ in
viewModel.quitRoom()
sleep(3)
}
}
.onDisappear {
UIApplication.shared.isIdleTimerDisabled = false
NotificationCenter.default.removeObserver(self)
}
ZStack {
if viewModel.isShowProfilePopup, let liveRoomInfo = viewModel.liveRoomInfo, let selectedProfile = viewModel.selectedProfile {
LiveRoomProfileDialog(
isShowing: $viewModel.isShowProfilePopup,
profileInfo: selectedProfile,
creatorId: liveRoomInfo.creatorId,
isSpeaker: viewModel.role == .SPEAKER,
onClickInviteSpeaker: { inviteSpeaker(peerId: $0) },
onClickChangeListener: {
if $0 == UserDefaults.int(forKey: .userId) {
viewModel.setListener()
return
}
viewModel.changeListener(peerId: $0)
}
)
}
if viewModel.isShowDonationPopup {
LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: false) { can, message in
viewModel.donation(can: can, message: message)
}
}
if viewModel.isShowQuitPopup {
SodaDialog(
title: "라이브 나가기",
desc: "라이브에서 나가시겠습니까?",
confirmButtonTitle: "",
confirmButtonAction: {
viewModel.isShowQuitPopup = false
viewModel.quitRoom()
},
cancelButtonTitle: "아니오",
cancelButtonAction: {
viewModel.isShowQuitPopup = false
}
)
}
if viewModel.isShowLiveEndPopup {
SodaDialog(
title: "라이브 종료",
desc: "라이브를 종료하시겠습니까?\n" +
"라이브를 종료하면 대화내용은\n" +
"저장되지 않고 사라집니다.\n" +
"참여자들 또한 라이브가 종료되어\n" +
"강제퇴장 됩니다.",
confirmButtonTitle: "",
confirmButtonAction: {
viewModel.isShowLiveEndPopup = false
viewModel.quitRoom()
},
cancelButtonTitle: "아니오",
cancelButtonAction: {
viewModel.isShowLiveEndPopup = false
}
)
}
}
ZStack {
if viewModel.isShowProfileList, let liveRoomInfo = viewModel.liveRoomInfo {
LiveRoomProfilesDialogView(
isShowing: $viewModel.isShowProfileList,
viewModel: viewModel,
roomInfo: liveRoomInfo,
registerNotification: { viewModel.creatorFollow() },
unRegisterNotification: { viewModel.creatorUnFollow() },
onClickProfile: {
if $0 != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: $0)
}
},
onClickNoChatting: { userId, nickname, profileUrl in
viewModel.noChattingUserId = userId
viewModel.noChattingUserNickname = nickname
viewModel.noChattingUserProfileUrl = profileUrl
viewModel.isShowNoChattingConfirm = true
}
)
}
if viewModel.isShowUserProfilePopup, let userProfile = viewModel.userProfile {
Color.black.opacity(0.7)
.edgesIgnoringSafeArea(.all)
LiveRoomUserProfileDialogView(
isShowing: $viewModel.isShowUserProfilePopup,
viewModel: viewModel,
userProfile: userProfile,
onClickSetManager: {
viewModel.setManagerMessageToPeer(userId: $0)
viewModel.setManager(userId: $0)
},
onClickReleaseManager: { viewModel.changeListener(peerId: $0, isFromManager: true) },
onClickFollow: { viewModel.creatorFollow(creatorId: $0, isGetUserProfile: true) },
onClickUnFollow: { viewModel.creatorUnFollow(creatorId: $0, isGetUserProfile: true) },
onClickInviteSpeaker: { inviteSpeaker(peerId: $0) },
onClickChangeListener: {
viewModel.changeListener(peerId: $0)
},
onClickMenu: { userId, userNickname, isBlocked in
viewModel.reportUserId = userId
viewModel.reportUserNickname = userNickname
viewModel.reportUserIsBlocked = isBlocked
viewModel.isShowReportMenu = true
},
onClickNoChatting: { userId, nickname, profileUrl in
viewModel.noChattingUserId = userId
viewModel.noChattingUserNickname = nickname
viewModel.noChattingUserProfileUrl = profileUrl
viewModel.isShowNoChattingConfirm = true
}
)
.padding(20)
.popup(isPresented: $viewModel.isShowReportPopup, type: .toast, position: .top, autohideIn: 1.3) {
GeometryReader { geo in
HStack {
Spacer()
Text(viewModel.reportMessage)
.padding(.vertical, 13.3)
.frame(width: geo.size.width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color.button)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.cornerRadius(20)
.padding(.top, 66.7)
Spacer()
}
}
}
}
if viewModel.isShowReportMenu {
VStack(spacing: 0) {
ProfileReportMenuView(
isShowing: $viewModel.isShowReportMenu,
isBlockedUser: viewModel.reportUserIsBlocked,
userBlockAction: { viewModel.isShowUesrBlockConfirm = true },
userUnBlockAction: { viewModel.userUnBlock() },
userReportAction: { viewModel.isShowUesrReportView = true },
profileReportAction: { viewModel.isShowProfileReportConfirm = true }
)
Rectangle()
.foregroundColor(Color(hex: "222222"))
.frame(width: screenSize().width, height: 15.3)
}
.ignoresSafeArea()
}
if viewModel.isShowUesrBlockConfirm {
UserBlockConfirmDialogView(
isShowing: $viewModel.isShowUesrBlockConfirm,
nickname: viewModel.reportUserNickname,
confirmAction: {
viewModel.userBlock { userId in
viewModel.kickOutId = userId
viewModel.kickOut()
}
}
)
}
if viewModel.isShowUesrReportView {
UserReportDialogView(
isShowing: $viewModel.isShowUesrReportView,
confirmAction: { reason in
viewModel.report(type: .USER, reason: reason)
}
)
}
if viewModel.isShowProfileReportConfirm {
ProfileReportDialogView(
isShowing: $viewModel.isShowProfileReportConfirm,
confirmAction: {
viewModel.report(type: .PROFILE)
}
)
}
if viewModel.isShowNoChattingConfirm && viewModel.noChattingUserId > 0 {
LiveRoomNoChattingDialogView(
nickname: viewModel.noChattingUserNickname,
profileUrl: viewModel.noChattingUserProfileUrl,
confirmAction: {
viewModel.isShowNoChattingConfirm = false
viewModel.setNoChatting()
},
cancelAction: {
viewModel.noChattingUserId = 0
viewModel.noChattingUserNickname = ""
viewModel.noChattingUserProfileUrl = ""
viewModel.isShowNoChattingConfirm = false
}
)
}
if viewModel.isShowPopup {
LiveRoomDialogView(
content: viewModel.popupContent,
cancelTitle: viewModel.popupCancelTitle,
cancelAction: viewModel.popupCancelAction,
confirmTitle: viewModel.popupConfirmTitle,
confirmAction: viewModel.popupConfirmAction
).onAppear {
if viewModel.popupConfirmTitle == nil && viewModel.popupConfirmAction == nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
viewModel.isShowPopup = false
viewModel.popupCancelTitle = nil
viewModel.popupCancelAction = nil
viewModel.popupConfirmTitle = nil
viewModel.popupConfirmAction = nil
}
}
}
}
}
if viewModel.isShowRouletteSettings {
RouletteSettingsView(isShowing: $viewModel.isShowRouletteSettings) { isActiveRoulette in
self.viewModel.setActiveRoulette(isActiveRoulette: isActiveRoulette)
}
}
if let preview = viewModel.roulettePreview, viewModel.isShowRoulettePreview {
RoulettePreviewDialog(
isShowing: $viewModel.isShowRoulettePreview,
title: nil,
onClickSpin: { viewModel.spinRoulette() },
preview: preview
)
}
if viewModel.isShowRoulette {
RouletteViewDialog(isShowing: $viewModel.isShowRoulette, options: viewModel.rouletteItems, selectedOption: viewModel.rouletteSelectedItem) {
viewModel.sendRouletteDonation()
}
}
if viewModel.isLoading && viewModel.liveRoomInfo == nil {
LoadingView()
}
}
.ignoresSafeArea(.keyboard)
.edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init())
.sheet(
isPresented: $viewModel.isShowShareView,
onDismiss: { viewModel.shareMessage = "" },
content: {
ActivityViewController(activityItems: [viewModel.shareMessage])
}
)
.sheet(isPresented: $viewModel.isShowPhotoPicker) {
ImagePicker(
isShowing: $viewModel.isShowPhotoPicker,
selectedImage: $viewModel.coverImage,
sourceType: .photoLibrary
)
}
.sheet(isPresented: $viewModel.isShowEditRoomInfoDialog) {
if let liveRoomInfo = viewModel.liveRoomInfo {
LiveRoomInfoEditDialog(
isShowing: $viewModel.isShowEditRoomInfoDialog,
isShowPhotoPicker: $viewModel.isShowPhotoPicker,
viewModel: viewModel,
isLoading: viewModel.isLoading,
currentTitle: liveRoomInfo.title,
currentNotice: liveRoomInfo.notice,
coverImageUrl: liveRoomInfo.coverImageUrl,
coverImage: viewModel.coverImage
) { newTitle, newNotice in
self.viewModel.editLiveRoomInfo(
title: newTitle,
notice: newNotice
)
}
} else {
EmptyView()
.onAppear {
viewModel.isShowEditRoomInfoDialog = false
}
}
}
.sheet(isPresented: $viewModel.isShowDonationRankingPopup) {
LiveRoomDonationRankingDialog(isShowing: $viewModel.isShowDonationRankingPopup)
}
.sheet(isPresented: $viewModel.isShowDonationMessagePopup) {
LiveRoomDonationMessageDialog(isShowing: $viewModel.isShowDonationMessagePopup)
}
}
private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat {
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))
textView.font = UIFont.systemFont(ofSize: 11.3)
textView.text = text
return textView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)).height
}
private func inviteSpeaker(peerId: Int) {
if viewModel.liveRoomInfo!.speakerList.count <= 4 {
viewModel.inviteSpeaker(peerId: peerId)
self.viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
self.viewModel.isShowPopup = true
} else {
viewModel.popupContent = "스피커 정원을 초과했습니다."
viewModel.isShowPopup = true
}
}
private var scrollObservableView: some View {
GeometryReader { proxy in
let offsetY = proxy.frame(in: .global).origin.y
Color.clear
.preference(
key: ScrollOffsetKey.self,
value: offsetY
)
.onAppear {
viewModel.setOriginOffset(offsetY)
}
}
.frame(height: 0)
}
struct ScrollOffsetKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
}
struct LiveRoomViewV2_Previews: PreviewProvider {
static var previews: some View {
LiveRoomViewV2()
}
}

View File

@ -120,7 +120,7 @@ struct HomeView: View {
}
if appState.isShowPlayer {
LiveRoomView()
LiveRoomViewV2()
}
if appState.isShowNotificationSettingsDialog {

View File

@ -0,0 +1,31 @@
//
// Color.swift
// SodaLive
//
// Created by klaus on 2024/01/17.
//
import Foundation
import SwiftUI
extension Color {
static let main = Color(hex: "80D8FF")
static let sub = Color(hex: "1313BC")
static let button = Color(hex: "3bb9f1")
static let bg = Color(hex: "13181B")
static let gray11 = Color(hex: "111111")
static let gray22 = Color(hex: "222222")
static let gray30 = Color(hex: "303030")
static let gray33 = Color(hex: "333333")
static let gray52 = Color(hex: "525252")
static let gray55 = Color(hex: "555555")
static let gray77 = Color(hex: "777777")
static let gray90 = Color(hex: "909090")
static let graybb = Color(hex: "bbbbbb")
static let grayd2 = Color(hex: "d2d2d2")
static let grayee = Color(hex: "eeeeee")
static let mainRed = Color(hex: "ff5c49")
static let mainRed2 = Color(hex: "ea3a25")
static let mainYellow = Color(hex: "ffdc00")
}