diff --git a/SodaLive/Resources/Assets.xcassets/btn_plus_round.imageset/btn_plus_round.png b/SodaLive/Resources/Assets.xcassets/btn_plus_round.imageset/btn_plus_round.png index d66ac27..a14e131 100644 Binary files a/SodaLive/Resources/Assets.xcassets/btn_plus_round.imageset/btn_plus_round.png and b/SodaLive/Resources/Assets.xcassets/btn_plus_round.imageset/btn_plus_round.png differ diff --git a/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png index ee6d977..167a0fb 100644 Binary files a/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png and b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_crown.imageset/ic_crown.png b/SodaLive/Resources/Assets.xcassets/ic_crown.imageset/ic_crown.png index 003ab3c..ae5bce0 100644 Binary files a/SodaLive/Resources/Assets.xcassets/ic_crown.imageset/ic_crown.png and b/SodaLive/Resources/Assets.xcassets/ic_crown.imageset/ic_crown.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/ic_kick_out.png b/SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/ic_kick_out.png index 0594439..2e08969 100644 Binary files a/SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/ic_kick_out.png and b/SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/ic_kick_out.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/Contents.json new file mode 100644 index 0000000..8778208 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/Contents.json @@ -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 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/ic_notice_triangle.png b/SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/ic_notice_triangle.png new file mode 100644 index 0000000..aaba260 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/ic_notice_triangle.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_request_speak.imageset/ic_request_speak.png b/SodaLive/Resources/Assets.xcassets/ic_request_speak.imageset/ic_request_speak.png index 52a3b13..a7d8c46 100644 Binary files a/SodaLive/Resources/Assets.xcassets/ic_request_speak.imageset/ic_request_speak.png and b/SodaLive/Resources/Assets.xcassets/ic_request_speak.imageset/ic_request_speak.png differ diff --git a/SodaLive/Resources/Assets.xcassets/ic_speaker_on.imageset/ic_speaker_on.png b/SodaLive/Resources/Assets.xcassets/ic_speaker_on.imageset/ic_speaker_on.png index be48c2d..c3de9c6 100644 Binary files a/SodaLive/Resources/Assets.xcassets/ic_speaker_on.imageset/ic_speaker_on.png and b/SodaLive/Resources/Assets.xcassets/ic_speaker_on.imageset/ic_speaker_on.png differ diff --git a/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift b/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift index 04892b1..b957f71 100644 --- a/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift +++ b/SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift @@ -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) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift index 69f66f3..3a48f27 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift @@ -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) } diff --git a/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift b/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift index 7928737..c7d706c 100644 --- a/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift +++ b/SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift @@ -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) diff --git a/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift b/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift index d67feab..b228a76 100644 --- a/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift +++ b/SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift @@ -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) diff --git a/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift b/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift index 4301635..ac1e19a 100644 --- a/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift +++ b/SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift @@ -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) } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift index 76abc18..650fef8 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift @@ -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) diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift index 43073a3..82312ea 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift @@ -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) } } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift index 92a10f3..24d8a2a 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift @@ -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) } } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift index fc60a40..e63f8e1 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift @@ -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) } } diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift index 77a3e90..38bfaa0 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift @@ -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() diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift index c871879..c958054 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift @@ -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 { diff --git a/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift b/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift index 517c0c7..5b051ae 100644 --- a/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift +++ b/SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift @@ -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 { diff --git a/SodaLive/Sources/Live/Room/LiveRoomView.swift b/SodaLive/Sources/Live/Room/LiveRoomView.swift deleted file mode 100644 index d832c1b..0000000 --- a/SodaLive/Sources/Live/Room/LiveRoomView.swift +++ /dev/null @@ -1,1033 +0,0 @@ -// -// LiveRoomView.swift -// SodaLive -// -// Created by klaus on 2023/08/14. -// - -import SwiftUI -import Kingfisher -import PopupView - -struct LiveRoomView: View { - @State private var isShowingNewChat = false - @State private var isShowPhotoPicker = false - @State private var noticeViewHeight: CGFloat = UIFont.systemFontSize - - let columns = [ - GridItem(.flexible()), - GridItem(.flexible()), - GridItem(.flexible()), - GridItem(.flexible()), - GridItem(.flexible()) - ] - - let chatColumns = [GridItem(.flexible())] - - @StateObject var keyboardHandler = KeyboardHandler() - @StateObject var viewModel = LiveRoomViewModel() - - var body: some View { - ZStack { - Color.black.edgesIgnoringSafeArea(.all) - - VStack(spacing: 0) { - LazyVStack(alignment: .leading, spacing: 0) { - HStack(spacing: 6.7) { - Text( - UserDefaults.int(forKey: .userId) == viewModel.liveRoomInfo?.creatorId ? - "라이브 종료": - "나가기" - ) - .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: "ff5c49")) - .padding(.horizontal, 14.3) - .padding(.vertical, 8.3) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: "ff5c49"), lineWidth: 1) - ) - .onTapGesture { - viewModel.isExpandNotice = false - if let liveRoomInfo = viewModel.liveRoomInfo, liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - viewModel.isShowLiveEndPopup = true - } else { - viewModel.isShowQuitPopup = true - } - } - - Spacer() - - Text(viewModel.isBgOn ? "배경 ON" : "배경 OFF") - .font(.custom(Font.medium.rawValue, size: 10)) - .foregroundColor(Color(hex: viewModel.isBgOn ? "9970ff" : "eeeeee")) - .padding(.horizontal, 14.3) - .padding(.vertical, 8.3) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: viewModel.isBgOn ? "9970ff" : "bbbbbb"), lineWidth: 1) - ) - .onTapGesture { - viewModel.isBgOn.toggle() - } - - HStack(spacing: 4.7) { - Image("ic_share") - .resizable() - .frame(width: 16, height: 16) - } - .padding(.horizontal, 14.3) - .padding(.vertical, 6) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: "bbbbbb"), lineWidth: 1) - ) - .onTapGesture { - viewModel.shareRoom() - } - - if let liveRoomInfo = viewModel.liveRoomInfo, - liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - HStack(spacing: 4.7) { - Image("ic_edit") - .resizable() - .frame(width: 16, height: 16) - } - .padding(.horizontal, 14.3) - .padding(.vertical, 6) - .overlay( - RoundedRectangle(cornerRadius: 13.3) - .stroke(Color(hex: "bbbbbb"), lineWidth: 1) - ) - .onTapGesture { - viewModel.isShowEditRoomInfoDialog = true - } - } - } - .padding(.horizontal, 13.3) - - if let liveRoomInfo = viewModel.liveRoomInfo { - ZStack { - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 5.3) { - if liveRoomInfo.isAdult { - Text("19") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "e33621")) - .padding(.horizontal, 5.3) - .padding(.vertical, 3.3) - .background(Color(hex: "601d14")) - .cornerRadius(2.6) - } - - Text(liveRoomInfo.title) - .font(.custom(Font.bold.rawValue, size: 15.3)) - .foregroundColor(Color(hex: "eeeeee")) - .lineLimit(1) - } - .padding(.top, 16.7) - .padding(.horizontal, 13.3) - - LiveRoomTopCreatorView( - creatorId: liveRoomInfo.creatorId, - nickname: liveRoomInfo.creatorNickname, - profileImageUrl: liveRoomInfo.creatorProfileUrl, - isFollowing: liveRoomInfo.isFollowing, - onClickProfile: { - if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) { - viewModel.getUserProfile(userId: liveRoomInfo.creatorId) - } - }, - onClickFollow: { - if $0 { - viewModel.creatorUnFollow() - } else { - viewModel.creatorFollow() - } - } - ) - .padding(.top, 16.7) - .padding(.horizontal, 13.3) - - Rectangle() - .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.3)) - .padding(.horizontal, 13.3) - .padding(.top, 8) - - NotificationView(liveRoomInfo: liveRoomInfo) - } - - if viewModel.isMute { - Image("img_noti_mute") - } - } - - if viewModel.isShowNotice { - HStack(alignment: .top, spacing: 8) { - Text("[공지]") - .font(.custom(Font.bold.rawValue, size: 11.3)) - .foregroundColor(.white) - .onTapGesture { - viewModel.isExpandNotice.toggle() - } - - if viewModel.isExpandNotice { - VStack(spacing: 10) { - TextView(text: liveRoomInfo.notice, dynamicHeight: $noticeViewHeight) - .frame(height: viewModel.isExpandNotice ? noticeViewHeight : UIFont.systemFontSize) - - Text("닫기") - .font(.custom(Font.light.rawValue, size: 11.3)) - .foregroundColor(.white) - .onTapGesture { - viewModel.isExpandNotice = false - } - } - } else { - Text(liveRoomInfo.notice) - .font(.custom(Font.light.rawValue, size: 11.3)) - .foregroundColor(.white) - .lineLimit(1) - .onTapGesture { - viewModel.isExpandNotice = true - } - } - } - .padding(.horizontal, 26.7) - .padding(.vertical, 13.3) - .frame(width: screenSize().width, alignment: .leading) - .background(Color(hex: "3d2a6c")) - .padding(.top, 10) - .contentShape(Rectangle()) - } - - if !viewModel.isSpeakerFold { - HStack(spacing: 0) { - Text("스피커") - .font(.custom(Font.bold.rawValue, size: 12)) - .foregroundColor(Color(hex: "eeeeee")) - - Spacer() - } - .padding(.top, 20) - .padding(.horizontal, 23.3) - - LazyVGrid(columns: columns) { - ForEach(liveRoomInfo.speakerList, id: \.self) { speaker in - VStack(spacing: 6.7) { - ZStack { - KFImage(URL(string: speaker.profileImage)) - .resizable() - .scaledToFill() - .frame(width: 46.7, height: 46.7, alignment: .top) - .clipShape(Circle()) - .overlay( - Circle() - .stroke( - Color(hex: "9970ff"), - lineWidth: viewModel.activeSpeakers.contains(UInt(speaker.id)) ? 3 : 0 - ) - ) - - if viewModel.muteSpeakers.contains(UInt(speaker.id)) { - Image("ic_mute") - .resizable() - .frame(width: 46.7, height: 46.7) - } - - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 0) { - Spacer() - - if liveRoomInfo.creatorId == speaker.id { - Image("ic_crown") - .resizable() - .frame(width: 16.7, height: 16.7) - } - } - - Spacer() - } - } - .frame(width: 46.7, height: 46.7) - - Text(speaker.nickname) - .font(.custom(Font.light.rawValue, size: 12)) - .foregroundColor(Color(hex: "bbbbbb")) - .lineLimit(1) - } - .onTapGesture { - viewModel.selectedProfile = speaker - viewModel.isShowProfilePopup = true - } - } - } - .padding(.top, 16.7) - .padding(.horizontal, 23.3) - } - } - } - .padding(.vertical, 16.7) - .frame(width: screenSize().width) - .background(Color(hex: "222222")) - .cornerRadius(16.7, corners: [.topLeft, .topRight]) - - ZStack(alignment: .top) { - ScrollViewReader { proxy in - ZStack(alignment: .bottomTrailing) { - GeometryReader { proxy in - if let coverImageUrl = viewModel.coverImageUrl, viewModel.isBgOn { - KFImage(URL(string: coverImageUrl)) - .resizable() - .scaledToFill() - .frame(width: proxy.size.width, height: proxy.size.height, alignment: .center) - .clipped() - - Color.black.opacity(0.4) - } - } - - VStack(alignment: .leading, spacing: 0) { - ScrollView(.vertical, showsIndicators: false) { - scrollObservableView - ChatView() - .frame(width: screenSize().width) - } - .rotationEffect(Angle(degrees: 180)) - .onTapGesture { hideKeyboard() } - .onPreferenceChange(ScrollOffsetKey.self) { - viewModel.setOffset($0) - } - - InputChatView { - isShowingNewChat = false - proxy.scrollTo(viewModel.messages.count - 1, anchor: .center) - }.padding(.bottom, 10) - } - - VStack(spacing: 13.3) { - if let liveRoomInfo = viewModel.liveRoomInfo { - 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) - .padding(.bottom, 13.3) - .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) - .padding(.bottom, 13.3) - .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) - .padding(.bottom, 13.3) - .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) - .padding(.bottom, 13.3) - .onTapGesture { - viewModel.toggleSpeakerMute() - } - - if let liveRoomInfo = viewModel.liveRoomInfo, 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, 16.7) - .padding(.bottom, 85) - - if isShowingNewChat { - NewChatView{ - isShowingNewChat = false - proxy.scrollTo(viewModel.messages.count - 1, anchor: .center) - }.padding(.bottom, 70) - } - } - .frame(width: screenSize().width) - } - - HStack(spacing: 0) { - Spacer() - - HStack(spacing: 6.7) { - Image(viewModel.isSpeakerFold ? "ic_live_detail_bottom" : "ic_live_detail_top") - .resizable() - .frame(width: 20, height: 20) - - Text(viewModel.isSpeakerFold ? "펼치기" : "접기") - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "bbbbbb")) - } - .padding(.vertical, 6.7) - .padding(.horizontal, 13.3) - .background(Color(hex: "222222")) - .cornerRadius(10, corners: [.bottomLeft, .bottomRight]) - .onTapGesture { - viewModel.isSpeakerFold.toggle() - } - } - .background( - LinearGradient( - gradient: Gradient(colors: [Color(hex: "222222").opacity(0.95), Color.black.opacity(0.005)]), - startPoint: .top, - endPoint: .bottom - ).ignoresSafeArea() - ) - } - } - .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(hex: "9970ff")) - .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(hex: "9970ff")) - .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: { 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: $isShowPhotoPicker) { - ImagePicker( - isShowing: $isShowPhotoPicker, - selectedImage: $viewModel.coverImage, - sourceType: .photoLibrary - ) - } - .sheet(isPresented: $viewModel.isShowEditRoomInfoDialog) { - if let liveRoomInfo = viewModel.liveRoomInfo { - LiveRoomInfoEditDialog( - isShowing: $viewModel.isShowEditRoomInfoDialog, - isShowPhotoPicker: $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) - } - } - - func makeAttributedString(_ text: String) -> NSAttributedString { - let attributedString = NSMutableAttributedString(string: text) - - let urlRegex = try! NSRegularExpression(pattern: "\\b(https?://\\S+\\b|www\\.\\S+\\b)") - let matches = urlRegex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text)) - - for match in matches { - let url = (text as NSString).substring(with: match.range) - if let detectedURL = URL(string: url) { - attributedString.addAttribute(.link, value: detectedURL, range: match.range) - } - } - - return attributedString - } - - - 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() - } - } - - @ViewBuilder - func NotificationView(liveRoomInfo: GetRoomInfoResponse) -> some View { - HStack(spacing: 8) { - Image( - viewModel.isShowNotice ? - "ic_notice_selected" : - "ic_notice_normal" - ) - .contentShape(Rectangle()) - .onTapGesture { - viewModel.isShowNotice.toggle() - } - .padding(.trailing, 10) - - Spacer() - - HStack(spacing: 4.7) { - Image("ic_can") - .resizable() - .frame(width: 16, height: 16) - - Text("\(viewModel.totalDonationCan)") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "bbbbbb")) - } - .padding(.horizontal, 11.5) - .padding(.vertical, 5.3) - .overlay( - RoundedRectangle(cornerRadius: 12.8) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "eeeeee")) - ) - .contentShape(Rectangle()) - .onTapGesture { - viewModel.isShowDonationRankingPopup = true - } - - if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) { - HStack(spacing: 0) { - Text("참여자") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "bbbbbb")) - - Text("\(liveRoomInfo.participantsCount)") - .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "9970ff")) - .padding(.leading, 6.7) - } - .padding(.horizontal, 11.5) - .padding(.vertical, 7.3) - .overlay( - RoundedRectangle(cornerRadius: 12.8) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "eeeeee")) - ) - .contentShape(Rectangle()) - .onTapGesture { - viewModel.isShowProfileList = true - } - } - } - .padding(.top, 13.3) - .padding(.horizontal, 13.3) - } - - @ViewBuilder - func NewChatView(scrollToBottom: @escaping () -> Void) -> 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(hex: "eeeeee")) - } - .padding(.vertical, 8) - .padding(.horizontal, 13.3) - .background(Color(hex: "555555").opacity(0.8)) - .cornerRadius(16.7) - .padding(.bottom, 13.3) - .onTapGesture { scrollToBottom() } - - Spacer() - } - } - - @ViewBuilder - func InputChatView(scrollToBottom: @escaping () -> Void) -> some View { - HStack(spacing: 0) { - TextField("채팅을 입력하세요", text: $viewModel.chatMessage) - .autocapitalization(.none) - .disableAutocorrection(true) - .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .accentColor(Color(hex: "3bb9f1")) - .keyboardType(.default) - .padding(.horizontal, 13.3) - .onTapGesture { - if viewModel.isNoChatting { - viewModel.popupContent = "\(viewModel.remainingNoChattingTime)초 동안 채팅하실 수 없습니다" - viewModel.isShowPopup = true - } - } - - Spacer() - - Image("btn_message_send") - .resizable() - .frame(width: 35, height: 35) - .padding(6.7) - .onTapGesture { - viewModel.sendMessage() - scrollToBottom() - } - } - .background(Color(hex: "232323")) - .cornerRadius(10) - .overlay( - RoundedRectangle(cornerRadius: 10) - .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "eeeeee")) - ) - .padding(13.3) - } - - @ViewBuilder - func ChatView() -> some View { - LazyVGrid(columns: chatColumns, alignment: .leading, spacing: 20) { - ForEach(0.. (56.7 * 2) { - isShowingNewChat = true - } - } - } - - private func spinRoulette() { - viewModel.spinRoulette() - } -} - -struct LiveRoomView_Previews: PreviewProvider { - static var previews: some View { - LiveRoomView() - } -} - -struct TextView: UIViewRepresentable { - var text: String - @Binding var dynamicHeight: CGFloat - - func makeUIView(context: Context) -> UITextView { - let textView = UITextView() - textView.text = text - textView.isEditable = false - textView.isScrollEnabled = true - textView.backgroundColor = .clear - 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 - DispatchQueue.main.async { - let height = uiView.sizeThatFits(uiView.frame.size).height - self.dynamicHeight = height > 500 ? 500 : height - } - } - - func makeCoordinator() -> Coordinator { - Coordinator($dynamicHeight) - } - - class Coordinator: NSObject, UITextViewDelegate { - var dynamicHeight: Binding - - init(_ dynamicHeight: Binding) { - self.dynamicHeight = dynamicHeight - } - - func textViewDidChange(_ textView: UITextView) { - DispatchQueue.main.async { - self.dynamicHeight.wrappedValue = textView.sizeThatFits(textView.frame.size).height - } - } - } -} - diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index b8fca5e..616ea28 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -24,7 +24,6 @@ final class LiveRoomViewModel: NSObject, ObservableObject { private let rouletteRepository = RouletteRepository() private var subscription = Set() - @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() }) } } diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift new file mode 100644 index 0000000..2d69078 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift @@ -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 {} + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift new file mode 100644 index 0000000..e80b2ea --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeImageButton.swift @@ -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: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift new file mode 100644 index 0000000..8e8d866 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextButton.swift @@ -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: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift new file mode 100644 index 0000000..5e6f88b --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomOverlayStrokeTextToggleButton.swift @@ -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: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift new file mode 100644 index 0000000..5d46c7b --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomRightBottomButton.swift @@ -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: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift b/SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift new file mode 100644 index 0000000..02b89de --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift @@ -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 + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift new file mode 100644 index 0000000..9b614b6 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift @@ -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.. 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: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift new file mode 100644 index 0000000..9ff4c76 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift @@ -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.. 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.. 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: {} + ) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift new file mode 100644 index 0000000..0912a55 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift @@ -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 }) + } +} diff --git a/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift new file mode 100644 index 0000000..c796d31 --- /dev/null +++ b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift @@ -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() + } +} diff --git a/SodaLive/Sources/Main/Home/HomeView.swift b/SodaLive/Sources/Main/Home/HomeView.swift index 169327e..70ed615 100644 --- a/SodaLive/Sources/Main/Home/HomeView.swift +++ b/SodaLive/Sources/Main/Home/HomeView.swift @@ -120,7 +120,7 @@ struct HomeView: View { } if appState.isShowPlayer { - LiveRoomView() + LiveRoomViewV2() } if appState.isShowNotificationSettingsDialog { diff --git a/SodaLive/Sources/UI/Theme/Color.swift b/SodaLive/Sources/UI/Theme/Color.swift new file mode 100644 index 0000000..4ff5ce8 --- /dev/null +++ b/SodaLive/Sources/UI/Theme/Color.swift @@ -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") +}