라이브 UI 변경
| Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.7 KiB | 
| Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 1.5 KiB | 
| Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 2.2 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal 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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_notice_triangle.imageset/ic_notice_triangle.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 419 B | 
| Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 6.1 KiB | 
| Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB | 
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|                         } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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() | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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 {} | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										31
									
								
								SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift
									
									
									
									
									
										Normal 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 | ||||
|     } | ||||
| } | ||||
| @@ -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 } | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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 }) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										668
									
								
								SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift
									
									
									
									
									
										Normal 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() | ||||
|     } | ||||
| } | ||||
| @@ -120,7 +120,7 @@ struct HomeView: View { | ||||
|                 } | ||||
|                  | ||||
|                 if appState.isShowPlayer { | ||||
|                     LiveRoomView() | ||||
|                     LiveRoomViewV2() | ||||
|                 } | ||||
|                  | ||||
|                 if appState.isShowNotificationSettingsDialog { | ||||
|   | ||||
							
								
								
									
										31
									
								
								SodaLive/Sources/UI/Theme/Color.swift
									
									
									
									
									
										Normal 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") | ||||
| } | ||||
 Yu Sung
					Yu Sung