룰렛 변경
- 확률 수동 설정 - 여러개의 룰렛이 켜져있을 때 선택하여 돌리기 - 후원 히스토리에 룰렛 히스토리
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 2.4 KiB | 
| @@ -1,21 +0,0 @@ | ||||
| { | ||||
|   "images" : [ | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "1x" | ||||
|     }, | ||||
|     { | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "btn_plus_round_rect.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
|   ], | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 2.8 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 996 B | 
| @@ -9,7 +9,7 @@ | ||||
|       "scale" : "2x" | ||||
|     }, | ||||
|     { | ||||
|       "filename" : "btn_minus_round_rect.png", | ||||
|       "filename" : "ic_select_check_black.png", | ||||
|       "idiom" : "universal", | ||||
|       "scale" : "3x" | ||||
|     } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_select_check_black.imageset/ic_select_check_black.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_select_check_black.imageset/ic_select_check_black.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 418 B | 
| @@ -23,7 +23,7 @@ struct LiveRoomDonationMessageDialog: View { | ||||
|              | ||||
|             VStack(spacing: 0) { | ||||
|                 HStack(spacing: 0) { | ||||
|                     Text("후원메시지") | ||||
|                     Text("후원 히스토리") | ||||
|                         .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                         .foregroundColor(Color(hex: "eeeeee")) | ||||
|                      | ||||
| @@ -45,43 +45,19 @@ struct LiveRoomDonationMessageDialog: View { | ||||
|                             ForEach(0..<viewModel.donationMessageList.count, id: \.self) { index in | ||||
|                                 let donationMessage = viewModel.donationMessageList[index] | ||||
|                                  | ||||
|                                 HStack(alignment: .top, spacing: 0) { | ||||
|                                     VStack(alignment: .leading, spacing: 8) { | ||||
|                                         Text("\(donationMessage.nickname)님이") | ||||
|                                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                                             .foregroundColor(.white) | ||||
|                                          | ||||
|                                         Text("\(donationMessage.canMessage)") | ||||
|                                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                                             .foregroundColor(.white) | ||||
|                                          | ||||
|                                         Text("'\(donationMessage.donationMessage)'") | ||||
|                                             .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                                             .foregroundColor(.white) | ||||
|                                 LiveRoomDonationMessageItemView(message: donationMessage) { | ||||
|                                     viewModel.deleteDonationMessage(uuid: $0) | ||||
|                                 } | ||||
|                                      | ||||
|                                     Spacer() | ||||
|                                      | ||||
|                                     Image("ic_close_white") | ||||
|                                         .resizable() | ||||
|                                         .frame(width: 13.3, height: 13.3) | ||||
|                                         .onTapGesture { | ||||
|                                             viewModel.deleteDonationMessage(uuid: donationMessage.uuid) | ||||
|                                         } | ||||
|                                 } | ||||
|                                 .padding(13.3) | ||||
|                                 .background(Color(hex: "333333")) | ||||
|                                 .cornerRadius(5.3) | ||||
|                                 .onTapGesture { | ||||
|                                     UIPasteboard.general.string = donationMessage.donationMessage | ||||
|                                     self.viewModel.errorMessage = "후원 메시지가 복사되었습니다." | ||||
|                                     self.viewModel.errorMessage = "후원 히스토리가 복사되었습니다." | ||||
|                                     self.viewModel.isShowPopup = true | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         .padding(.top, 18.7) | ||||
|                     } else { | ||||
|                         Text("후원메시지가 없습니다.") | ||||
|                         Text("후원 히스토리가 없습니다.") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 14.7)) | ||||
|                             .foregroundColor(Color(hex: "eeeeee")) | ||||
|                             .padding(.top, 30) | ||||
|   | ||||
| @@ -0,0 +1,68 @@ | ||||
| // | ||||
| //  LiveRoomDonationMessageItemView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 5/11/24. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct LiveRoomDonationMessageItemView: View { | ||||
|      | ||||
|     let message: LiveRoomDonationMessage | ||||
|     let deleteDonationMessage: (String) -> Void | ||||
|      | ||||
|     var body: some View { | ||||
|         HStack(alignment: .top, spacing: 0) { | ||||
|             VStack(alignment: .leading, spacing: 8) { | ||||
|                 Text("\(message.nickname)님이") | ||||
|                     .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                     .foregroundColor(.white) | ||||
|                  | ||||
|                 if !message.canMessage.trimmingCharacters(in: .whitespaces).isEmpty { | ||||
|                     Text("\(message.canMessage)") | ||||
|                         .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                         .foregroundColor(.white) | ||||
|                 } | ||||
|                  | ||||
|                 Text("'\(message.donationMessage)'") | ||||
|                     .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                     .foregroundColor(.white) | ||||
|             } | ||||
|              | ||||
|             Spacer() | ||||
|              | ||||
|             Image("ic_close_white") | ||||
|                 .resizable() | ||||
|                 .frame(width: 13.3, height: 13.3) | ||||
|                 .onTapGesture { deleteDonationMessage(message.uuid) } | ||||
|         } | ||||
|         .padding(13.3) | ||||
|         .background(message.canMessage.trimmingCharacters(in: .whitespaces).isEmpty ? Color(hex: "c25264").opacity(0.8) : Color.gray33) | ||||
|         .cornerRadius(5.3) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview("일반후원 메시지") { | ||||
|     LiveRoomDonationMessageItemView( | ||||
|         message: LiveRoomDonationMessage( | ||||
|             uuid: "", | ||||
|             nickname: "유저2님이", | ||||
|             canMessage: "10캔을 후원하셨습니다", | ||||
|             donationMessage: "ㅅㅅㅅ" | ||||
|         ), | ||||
|         deleteDonationMessage: { _ in } | ||||
|     ) | ||||
| } | ||||
|  | ||||
| #Preview("룰렛후원 메시지") { | ||||
|     LiveRoomDonationMessageItemView( | ||||
|         message: LiveRoomDonationMessage( | ||||
|             uuid: "", | ||||
|             nickname: "유저2님의 룰렛 결과?", | ||||
|             canMessage: "", | ||||
|             donationMessage: "[테스트] 당첨!" | ||||
|         ), | ||||
|         deleteDonationMessage: { _ in } | ||||
|     ) | ||||
| } | ||||
| @@ -151,7 +151,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { | ||||
|     @Published var isShowRouletteSettings = false | ||||
|      | ||||
|     @Published var isShowRoulettePreview = false | ||||
|     @Published var roulettePreview: RoulettePreview? = nil | ||||
|     @Published var roulettePreviewList = [RoulettePreview]() | ||||
|      | ||||
|     @Published var isShowRoulette = false | ||||
|     @Published var rouletteItems = [String]() | ||||
| @@ -1456,6 +1456,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject { | ||||
|      | ||||
|     func showRoulette() { | ||||
|         if let liveRoomInfo = liveRoomInfo, !isLoading { | ||||
|             self.roulettePreviewList.removeAll() | ||||
|             isLoading = true | ||||
|              | ||||
|             rouletteRepository.getRoulette(creatorId: liveRoomInfo.creatorId) | ||||
| @@ -1472,10 +1473,14 @@ final class LiveRoomViewModel: NSObject, ObservableObject { | ||||
|                      | ||||
|                     do { | ||||
|                         let jsonDecoder = JSONDecoder() | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<GetRouletteResponse>.self, from: responseData) | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<[GetRouletteResponse]>.self, from: responseData) | ||||
|                          | ||||
|                         if let data = decoded.data, decoded.success, !data.items.isEmpty { | ||||
|                             self.roulettePreview = RoulettePreview(can: data.can, items: calculatePercentages(options: data.items)) | ||||
|                         if let data = decoded.data, decoded.success, !data.isEmpty { | ||||
|                             let roulettePreviewList = data | ||||
|                                 .filter { $0.isActive } | ||||
|                                 .filter { !$0.items.isEmpty} | ||||
|                                 .map { RoulettePreview(id: $0.id, can: $0.can, items: calculatePercentages(options: $0.items)) } | ||||
|                             self.roulettePreviewList.append(contentsOf: roulettePreviewList) | ||||
|                             self.isShowRoulettePreview = true | ||||
|                         } else { | ||||
|                             if let message = decoded.message { | ||||
| @@ -1494,10 +1499,10 @@ final class LiveRoomViewModel: NSObject, ObservableObject { | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func spinRoulette() { | ||||
|     func spinRoulette(rouletteId: Int) { | ||||
|         if !isLoading { | ||||
|             isLoading = true | ||||
|             rouletteRepository.spinRoulette(request: SpinRouletteRequest(roomId: AppState.shared.roomId)) | ||||
|             rouletteRepository.spinRoulette(request: SpinRouletteRequest(roomId: AppState.shared.roomId, rouletteId: rouletteId)) | ||||
|                 .sink { result in | ||||
|                     switch result { | ||||
|                     case .finished: | ||||
| @@ -1511,11 +1516,15 @@ final class LiveRoomViewModel: NSObject, ObservableObject { | ||||
|                      | ||||
|                     do { | ||||
|                         let jsonDecoder = JSONDecoder() | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<GetRouletteResponse>.self, from: responseData) | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponse<SpinRouletteResponse>.self, from: responseData) | ||||
|                          | ||||
|                         if let data = decoded.data, decoded.success, !data.items.isEmpty { | ||||
|                             UserDefaults.set(UserDefaults.int(forKey: .can) - data.can, forKey: .can) | ||||
|                             randomSelectRouletteItem(can: data.can, items: data.items) | ||||
|                             self.rouletteItems.removeAll() | ||||
|                             self.rouletteItems.append(contentsOf: data.items.map { $0.title }) | ||||
|                             self.rouletteSelectedItem = data.result | ||||
|                             self.rouletteCan = data.can | ||||
|                             self.isShowRoulette = true | ||||
|                         } else { | ||||
|                             if let message = decoded.message { | ||||
|                                 self.errorMessage = message | ||||
| @@ -1574,35 +1583,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject { | ||||
|     } | ||||
|      | ||||
|     private func calculatePercentages(options: [RouletteItem]) -> [RoulettePreviewItem] { | ||||
|         let totalWeight = options.reduce(0) { $0 + $1.weight } | ||||
|         let updatedOptions = options.map { option in | ||||
|             let percent = floor(Double(option.weight) / Double(totalWeight) * 10000) / 100 | ||||
|             return RoulettePreviewItem(title: option.title, percent: "\(String(format: "%.2f", percent))%") | ||||
|             return RoulettePreviewItem(title: option.title, percent: "\(String(format: "%.2f", option.percentage))%") | ||||
|         } | ||||
|          | ||||
|         return updatedOptions | ||||
|     } | ||||
|      | ||||
|     private func randomSelectRouletteItem(can: Int, items: [RouletteItem]) { | ||||
|         isLoading = true | ||||
|          | ||||
|         var rouletteItems = [String]() | ||||
|         items.forEach { | ||||
|             var i = 1 | ||||
|             while (i < $0.weight * 10) { | ||||
|                 rouletteItems.append($0.title) | ||||
|                 i += 1 | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         isLoading = false | ||||
|         self.rouletteItems.removeAll() | ||||
|         self.rouletteItems.append(contentsOf: items.map { $0.title }) | ||||
|         self.rouletteSelectedItem = rouletteItems.randomElement()! | ||||
|         self.rouletteCan = can | ||||
|         self.isShowRoulette = true | ||||
|     } | ||||
|      | ||||
|     private func refundRouletteDonation() { | ||||
|         isLoading = true | ||||
|          | ||||
|   | ||||
| @@ -9,11 +9,10 @@ import SwiftUI | ||||
|  | ||||
| class RouletteOption: ObservableObject { | ||||
|     var title: String | ||||
|     var weight: Int | ||||
|     var percentage: String = "50.00" | ||||
|     var percentage: String = "" | ||||
|      | ||||
|     init(title: String, weight: Int) { | ||||
|     init(title: String, percentage: String) { | ||||
|         self.title = title | ||||
|         self.weight = weight | ||||
|         self.percentage = percentage | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,10 +12,7 @@ struct RouletteSettingsOptionView: View { | ||||
|     @ObservedObject var option: RouletteOption | ||||
|      | ||||
|     let index: Int | ||||
|      | ||||
|     let onClickPlus: () -> Void | ||||
|     let onClickDelete: () -> Void | ||||
|     let onClickSubstract: () -> Void | ||||
|      | ||||
|     var body: some View { | ||||
|         VStack(spacing: 6.7) { | ||||
| @@ -47,19 +44,28 @@ struct RouletteSettingsOptionView: View { | ||||
|                     .background(Color(hex: "222222")) | ||||
|                     .cornerRadius(6.7) | ||||
|                  | ||||
|                 Text("\(option.percentage)%") | ||||
|                 HStack(spacing: 0) { | ||||
|                     TextField("0.00", text: $option.percentage) | ||||
|                     .autocapitalization(.none) | ||||
|                     .disableAutocorrection(true) | ||||
|                     .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                     .foregroundColor(Color(hex: "eeeeee")) | ||||
|                     .keyboardType(.decimalPad) | ||||
|                     .onChange(of: option.percentage) { newValue in | ||||
|                         if newValue.count > 5 { | ||||
|                             option.percentage = String(newValue.prefix(5)) | ||||
|                         } | ||||
|                     } | ||||
|                      | ||||
|                     Text("%") | ||||
|                         .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                         .foregroundColor(Color(hex: "eeeeee")) | ||||
|                 } | ||||
|                 .padding(.horizontal, 13.3) | ||||
|                 .padding(.vertical, 16.7) | ||||
|                 .frame(maxWidth: 85) | ||||
|                 .background(Color(hex: "222222")) | ||||
|                 .cornerRadius(6.7) | ||||
|                  | ||||
|                 Image("btn_minus_round_rect") | ||||
|                     .onTapGesture { onClickSubstract() } | ||||
|                  | ||||
|                 Image("btn_plus_round_rect") | ||||
|                     .onTapGesture { onClickPlus() } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -68,11 +74,9 @@ struct RouletteSettingsOptionView: View { | ||||
| struct RouletteSettingsOptionView_Previews: PreviewProvider { | ||||
|     static var previews: some View { | ||||
|         RouletteSettingsOptionView( | ||||
|             option: RouletteOption(title: "옵션1", weight: 1), | ||||
|             option: RouletteOption(title: "옵션1", percentage: ""), | ||||
|             index: 2, | ||||
|             onClickPlus: {}, | ||||
|             onClickDelete: {}, | ||||
|             onClickSubstract: {} | ||||
|             onClickDelete: {} | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -128,9 +128,7 @@ struct RouletteSettingsView: View { | ||||
|                                     RouletteSettingsOptionView( | ||||
|                                         option: viewModel.options[index], | ||||
|                                         index: index, | ||||
|                                         onClickPlus: { viewModel.plusWeight(index: index) }, | ||||
|                                         onClickDelete: { viewModel.deleteOption(index: index) }, | ||||
|                                         onClickSubstract: { viewModel.subtractWeight(index: index) } | ||||
|                                         onClickDelete: { viewModel.deleteOption(index: index) } | ||||
|                                     ) | ||||
|                                 } | ||||
|                             } | ||||
| @@ -190,7 +188,7 @@ struct RouletteSettingsView: View { | ||||
|                         isShowing: $viewModel.isShowPreview, | ||||
|                         title: "룰렛 미리보기", | ||||
|                         onClickSpin: nil, | ||||
|                         preview: preview | ||||
|                         previewList: [preview] | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -46,47 +46,15 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|     private var rouletteId = 0 | ||||
|     @Published var rouletteList = [GetNewRouletteResponse]() | ||||
|      | ||||
|     func plusWeight(index: Int) { | ||||
|         options[index].weight += 1 | ||||
|         recalculatePercentages() | ||||
|     } | ||||
|      | ||||
|     func subtractWeight(index: Int) { | ||||
|         if options[index].weight > 1 { | ||||
|             options[index].weight -= 1 | ||||
|             recalculatePercentages() | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func addOption() { | ||||
|         if (options.count >= 10) { | ||||
|             return | ||||
|         } | ||||
|         options.append(RouletteOption(title: "", weight: 1)) | ||||
|         recalculatePercentages() | ||||
|         options.append(RouletteOption(title: "", percentage: "")) | ||||
|     } | ||||
|      | ||||
|     func deleteOption(index: Int) { | ||||
|         options.remove(at: index) | ||||
|         recalculatePercentages() | ||||
|     } | ||||
|      | ||||
|     private func recalculatePercentages() { | ||||
|         let options = options | ||||
|          | ||||
|         var totalWeight = 0 | ||||
|         for option in options { | ||||
|             totalWeight += option.weight | ||||
|         } | ||||
|          | ||||
|         guard totalWeight > 0 else { return } | ||||
|          | ||||
|         for i in 0..<options.count { | ||||
|             let percent = floor(Double(options[i].weight) / Double(totalWeight) * 10000) / 100 | ||||
|             options[i].percentage = String(format: "%.2f", percent) | ||||
|         } | ||||
|          | ||||
|         removeAllAndAddOptions(options: options) | ||||
|     } | ||||
|      | ||||
|     private func removeAllAndAddOptions(options: [RouletteOption]) { | ||||
| @@ -143,7 +111,7 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|             items.append(RoulettePreviewItem(title: option.title, percent: "\(option.percentage)%")) | ||||
|         } | ||||
|          | ||||
|         previewData = RoulettePreview(can: self.can, items: items) | ||||
|         previewData = RoulettePreview(id: 0, can: self.can, items: items) | ||||
|         isLoading = false | ||||
|         isShowPreview = true | ||||
|     } | ||||
| @@ -152,6 +120,7 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|         if !isLoading { | ||||
|             isLoading = true | ||||
|              | ||||
|             if validationOptions() { | ||||
|                 if rouletteId > 0 { | ||||
|                     updateRoulette(onSuccess: onSuccess) | ||||
|                 } else { | ||||
| @@ -159,18 +128,40 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func validationOptions() -> Bool { | ||||
|         var totalPercentage = Float(0) | ||||
|          | ||||
|     private func createRoulette(onSuccess: @escaping (Bool, String) -> Void) { | ||||
|         var items = [RouletteItem]() | ||||
|         for option in options { | ||||
|             if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { | ||||
|                 isLoading = false | ||||
|                 errorMessage = "옵션은 빈칸일 수 없습니다." | ||||
|                 isShowErrorPopup = true | ||||
|                 return | ||||
|                 return false | ||||
|             } | ||||
|              | ||||
|             items.append(RouletteItem(title: option.title, weight: option.weight)) | ||||
|             if let percentage = Float(option.percentage) { | ||||
|                 totalPercentage += percentage | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if totalPercentage != Float(100) { | ||||
|             isLoading = false | ||||
|             errorMessage = "확률이 100%가 아닙니다" | ||||
|             isShowErrorPopup = true | ||||
|             return false | ||||
|         } | ||||
|          | ||||
|         return true | ||||
|     } | ||||
|      | ||||
|     private func createRoulette(onSuccess: @escaping (Bool, String) -> Void) { | ||||
|         var items = [RouletteItem]() | ||||
|         for option in options { | ||||
|             if let percentage = Float(option.percentage) { | ||||
|                 items.append(RouletteItem(title: option.title, percentage: percentage)) | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         let selectedRouletteTitle: String | ||||
| @@ -227,14 +218,9 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|     private func updateRoulette(onSuccess: @escaping (Bool, String) -> Void) { | ||||
|         var items = [RouletteItem]() | ||||
|         for option in options { | ||||
|             if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { | ||||
|                 isLoading = false | ||||
|                 errorMessage = "옵션은 빈칸일 수 없습니다." | ||||
|                 isShowErrorPopup = true | ||||
|                 return | ||||
|             if let percentage = Float(option.percentage) { | ||||
|                 items.append(RouletteItem(title: option.title, percentage: percentage)) | ||||
|             } | ||||
|              | ||||
|             items.append(RouletteItem(title: option.title, weight: option.weight)) | ||||
|         } | ||||
|          | ||||
|         let selectedRoulette = rouletteList[selectedRoulette!.rawValue] | ||||
| @@ -259,24 +245,10 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|             selectedRouletteTitle = "룰렛 1" | ||||
|         } | ||||
|          | ||||
|         var isAllActive = false | ||||
|          | ||||
|         rouletteList | ||||
|             .filter { | ||||
|                 $0.id != selectedRoulette.id | ||||
|             } | ||||
|             .forEach { | ||||
|                 if $0.isActive { | ||||
|                     isAllActive = true | ||||
|                 } | ||||
|             } | ||||
|          | ||||
|         if isActive { | ||||
|             successMessage = "\(selectedRouletteTitle)로 설정하였습니다." | ||||
|         } else if !isAllActive { | ||||
|             successMessage = "\(selectedRouletteTitle)이 비활성화 되었습니다." | ||||
|             successMessage = "\(selectedRouletteTitle)을 활성화 했습니다." | ||||
|         } else { | ||||
|             successMessage = "\(selectedRouletteTitle)을 설정했습니다." | ||||
|             successMessage = "\(selectedRouletteTitle)을 비활성화 했습니다." | ||||
|         } | ||||
|          | ||||
|         let request = UpdateRouletteRequest(id: rouletteId, can: can, isActive: isActive, items: items) | ||||
| @@ -337,10 +309,9 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|                 self.rouletteId = roulette.id | ||||
|                 self.isActive = roulette.isActive | ||||
|                 let options = roulette.items.map { | ||||
|                     RouletteOption(title: $0.title, weight: $0.weight) | ||||
|                     RouletteOption(title: $0.title, percentage: String($0.percentage)) | ||||
|                 } | ||||
|                 removeAllAndAddOptions(options: options) | ||||
|                 recalculatePercentages() | ||||
|             } else { | ||||
|                 self.canText = "" | ||||
|                 self.isActive = false | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| // | ||||
|  | ||||
| struct GetRouletteResponse: Decodable { | ||||
|     let id: Int | ||||
|     let can: Int | ||||
|     let isActive: Bool | ||||
|     let items: [RouletteItem] | ||||
| @@ -13,5 +14,5 @@ struct GetRouletteResponse: Decodable { | ||||
|  | ||||
| struct RouletteItem: Codable, Equatable { | ||||
|     let title: String | ||||
|     let weight: Int | ||||
|     let percentage: Float | ||||
| } | ||||
|   | ||||
| @@ -25,16 +25,16 @@ extension RouletteApi: TargetType { | ||||
|     var path: String { | ||||
|         switch self { | ||||
|         case .getRoulette, .createRoulette, .updateRoulette: | ||||
|             return "/new-roulette" | ||||
|             return "/v2/roulette" | ||||
|              | ||||
|         case .getAllRoulette: | ||||
|             return "/new-roulette/creator" | ||||
|             return "/v2/roulette/creator" | ||||
|              | ||||
|         case .spinRoulette: | ||||
|             return "/new-roulette/spin" | ||||
|             return "/v2/roulette/spin" | ||||
|              | ||||
|         case .refundRouletteDonation(let roomId): | ||||
|             return "/new-roulette/refund/\(roomId)" | ||||
|             return "/v2/roulette/refund/\(roomId)" | ||||
|         } | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| import Foundation | ||||
|  | ||||
| struct RoulettePreview { | ||||
|     let id: Int | ||||
|     let can: Int | ||||
|     let items: [RoulettePreviewItem] | ||||
| } | ||||
|   | ||||
| @@ -12,13 +12,61 @@ struct RoulettePreviewDialog: View { | ||||
|     @Binding var isShowing: Bool | ||||
|      | ||||
|     let title: String? | ||||
|     let onClickSpin: (() -> Void)? | ||||
|     let preview: RoulettePreview | ||||
|     let onClickSpin: ((Int) -> Void)? | ||||
|     let previewList: [RoulettePreview] | ||||
|      | ||||
|     @State var selectedRoulette = SelectedRoulette.ROULETTE_1 | ||||
|      | ||||
|     var body: some View { | ||||
|         GeometryReader { geo in | ||||
|             ZStack { | ||||
|                 VStack(spacing: 0) { | ||||
|                 VStack(spacing: 16.7) { | ||||
|                     if previewList.count > 1 { | ||||
|                         HStack(spacing: 13.3) { | ||||
|                             SelectedButtonView( | ||||
|                                 title: "룰렛 1", | ||||
|                                 isActive: true, | ||||
|                                 isSelected: selectedRoulette == .ROULETTE_1 | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 if selectedRoulette != .ROULETTE_1 { | ||||
|                                     selectedRoulette = .ROULETTE_1 | ||||
|                                 } | ||||
|                             } | ||||
|                              | ||||
|                             SelectedButtonView( | ||||
|                                 title: "룰렛 2", | ||||
|                                 isActive: true, | ||||
|                                 isSelected: selectedRoulette == .ROULETTE_2, | ||||
|                                 checkImage: "ic_select_check_black", | ||||
|                                 bgSelectedColor: Color(hex: "ffcb14"), | ||||
|                                 textSelectedColor: Color.black, | ||||
|                                 textDefaultColor: Color(hex: "ffcb14") | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 if selectedRoulette != .ROULETTE_2 { | ||||
|                                     selectedRoulette = .ROULETTE_2 | ||||
|                                 } | ||||
|                             } | ||||
|                              | ||||
|                             if previewList.count > 2 { | ||||
|                                 SelectedButtonView( | ||||
|                                     title: "룰렛 3", | ||||
|                                     isActive: true, | ||||
|                                     isSelected: selectedRoulette == .ROULETTE_3, | ||||
|                                     bgSelectedColor: Color(hex: "ff14d9"), | ||||
|                                     textDefaultColor: Color(hex: "ff14d9") | ||||
|                                 ) | ||||
|                                 .onTapGesture { | ||||
|                                     if selectedRoulette != .ROULETTE_3 { | ||||
|                                         selectedRoulette = .ROULETTE_3 | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         .padding(.top, 16.7) | ||||
|                     } | ||||
|                      | ||||
|                     HStack(spacing: 0) { | ||||
|                         Text(title ?? "룰렛") | ||||
|                             .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||
| @@ -42,54 +90,66 @@ struct RoulettePreviewDialog: View { | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     .padding(.top, 16.7) | ||||
|                     .padding(.top, previewList.count > 1 ? 0 : 16.7) | ||||
|                     .padding(.horizontal, 13.3) | ||||
|                      | ||||
|                     LazyVStack(alignment: .leading, spacing: 13.3) { | ||||
|                         ForEach(preview.items.indices, id: \.self) { index in | ||||
|                         ForEach(previewList[selectedRoulette.rawValue].items.indices, id: \.self) { index in | ||||
|                             HStack(spacing:13.3) { | ||||
|                                 Text("\(index + 1)") | ||||
|                                     .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                                     .foregroundColor(Color(hex: "e2e2e2")) | ||||
|                                  | ||||
|                                 Text("\(preview.items[index].title) (\(preview.items[index].percent))") | ||||
|                                 Text("\(previewList[selectedRoulette.rawValue].items[index].title) (\(previewList[selectedRoulette.rawValue].items[index].percent))") | ||||
|                                     .font(.custom(Font.medium.rawValue, size: 14.7)) | ||||
|                                     .foregroundColor(Color(hex: "e2e2e2")) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     .padding(.top, 13.3) | ||||
|                     .padding(.horizontal, 13.3) | ||||
|                      | ||||
|                     HStack(spacing: 13.3) { | ||||
|                         Text("취소") | ||||
|                             .font(.custom(Font.bold.rawValue, size: 16)) | ||||
|                             .foregroundColor(Color(hex: "3bb9f1")) | ||||
|                             .foregroundColor( | ||||
|                                 selectedRoulette == .ROULETTE_2 ? Color(hex: "ffcb14") : | ||||
|                                     selectedRoulette == .ROULETTE_3 ? Color(hex: "ff14d9") : | ||||
|                                     Color.button | ||||
|                             ) | ||||
|                             .padding(.horizontal, 18) | ||||
|                             .padding(.vertical, 16) | ||||
|                             .overlay( | ||||
|                                 RoundedRectangle(cornerRadius: 10) | ||||
|                                     .stroke(Color(hex: "3bb9f1"), lineWidth: 1) | ||||
|                                     .stroke( | ||||
|                                         selectedRoulette == .ROULETTE_2 ? Color(hex: "ffcb14") : | ||||
|                                             selectedRoulette == .ROULETTE_3 ? Color(hex: "ff14d9") : | ||||
|                                             Color.button, | ||||
|                                         lineWidth: 1 | ||||
|                                     ) | ||||
|                             ) | ||||
|                             .onTapGesture { | ||||
|                                 isShowing = false | ||||
|                             } | ||||
|                          | ||||
|                         Text("\(preview.can)캔으로 룰렛 돌리기") | ||||
|                         Text("\(previewList[selectedRoulette.rawValue].can)캔으로 룰렛 돌리기") | ||||
|                             .font(.custom(Font.bold.rawValue, size: 16)) | ||||
|                             .foregroundColor(.white) | ||||
|                             .foregroundColor(selectedRoulette == .ROULETTE_2 ? .black : .white) | ||||
|                             .padding(.vertical, 16) | ||||
|                             .frame(maxWidth: .infinity) | ||||
|                             .background(Color(hex: "3bb9f1")) | ||||
|                             .background( | ||||
|                                 selectedRoulette == .ROULETTE_2 ? Color(hex: "ffcb14") : | ||||
|                                     selectedRoulette == .ROULETTE_3 ? Color(hex: "ff14d9") : | ||||
|                                     Color.button | ||||
|                             ) | ||||
|                             .cornerRadius(10) | ||||
|                             .onTapGesture { | ||||
|                                 if let onClickSpin = onClickSpin { | ||||
|                                     onClickSpin() | ||||
|                                     onClickSpin(previewList[selectedRoulette.rawValue].id) | ||||
|                                 } | ||||
|                                 isShowing = false | ||||
|                             } | ||||
|                     } | ||||
|                     .padding(.top, 26.7) | ||||
|                     .padding(.top, 6.7) | ||||
|                 } | ||||
|                 .padding(13.3) | ||||
|                 .background(Color(hex: "222222")) | ||||
| @@ -108,13 +168,32 @@ struct RoulettePreviewDialog_Previews: PreviewProvider { | ||||
|             isShowing: .constant(true), | ||||
|             title: nil, | ||||
|             onClickSpin: nil, | ||||
|             preview: RoulettePreview( | ||||
|             previewList: [ | ||||
|                 RoulettePreview( | ||||
|                     id: 0, | ||||
|                     can: 100, | ||||
|                     items: [ | ||||
|                     RoulettePreviewItem(title: "옵션1", percent: "10%"), | ||||
|                     RoulettePreviewItem(title: "옵션2", percent: "90%"), | ||||
|                         RoulettePreviewItem(title: "옵션1", percent: "33.40%"), | ||||
|                         RoulettePreviewItem(title: "옵션2", percent: "66.60%"), | ||||
|                     ] | ||||
|                 ), | ||||
|                 RoulettePreview( | ||||
|                     id: 1, | ||||
|                     can: 10, | ||||
|                     items: [ | ||||
|                         RoulettePreviewItem(title: "옵션3", percent: "10.8%"), | ||||
|                         RoulettePreviewItem(title: "옵션4", percent: "89.2%"), | ||||
|                     ] | ||||
|                 ), | ||||
|                 RoulettePreview( | ||||
|                     id: 2, | ||||
|                     can: 1000, | ||||
|                     items: [ | ||||
|                         RoulettePreviewItem(title: "옵션5", percent: "10%"), | ||||
|                         RoulettePreviewItem(title: "옵션6", percent: "90%"), | ||||
|                     ] | ||||
|                 ) | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,5 +9,6 @@ import Foundation | ||||
|  | ||||
| struct SpinRouletteRequest: Encodable { | ||||
|     let roomId: Int | ||||
|     let rouletteId: Int | ||||
|     let container: String = "ios" | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,12 @@ | ||||
| // | ||||
| //  SpinRouletteResponse.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 5/11/24. | ||||
| // | ||||
|  | ||||
| struct SpinRouletteResponse: Decodable { | ||||
|     let can: Int | ||||
|     let result: String | ||||
|     let items: [RouletteItem] | ||||
| } | ||||
| @@ -168,15 +168,10 @@ struct LiveRoomViewV2: View { | ||||
|                                 .padding(.top, 16) | ||||
|                                  | ||||
|                                 VStack(alignment: .trailing, spacing: 0) { | ||||
|                                     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() | ||||
|                                         } | ||||
|                                     LiveRoomRightBottomButton( | ||||
|                                         imageName: viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on", | ||||
|                                         onClick: { viewModel.toggleSpeakerMute() } | ||||
|                                     ) | ||||
|                                     .padding(.top, 26.7) | ||||
|                                     .padding(.trailing, 13.3) | ||||
|                                      | ||||
| @@ -184,60 +179,35 @@ struct LiveRoomViewV2: View { | ||||
|                                      | ||||
|                                     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 | ||||
|                                                 } | ||||
|                                             LiveRoomRightBottomButton( | ||||
|                                                 imageName: "ic_roulette_settings", | ||||
|                                                 onClick: { 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() | ||||
|                                                 } | ||||
|                                             LiveRoomRightBottomButton( | ||||
|                                                 imageName: "ic_roulette", | ||||
|                                                 onClick: { 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() | ||||
|                                                 } | ||||
|                                             LiveRoomRightBottomButton( | ||||
|                                                 imageName: viewModel.isMute ? "ic_mic_off" : "ic_mic_on", | ||||
|                                                 onClick: { viewModel.toggleMute() } | ||||
|                                             ) | ||||
|                                         } | ||||
|                                          | ||||
|                                         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 | ||||
|                                                 } | ||||
|                                             LiveRoomRightBottomButton( | ||||
|                                                 imageName: "ic_donation_message_list", | ||||
|                                                 onClick: { 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 | ||||
|                                                 } | ||||
|                                             LiveRoomRightBottomButton( | ||||
|                                                 imageName: "ic_donation", | ||||
|                                                 onClick: { viewModel.isShowDonationPopup = true } | ||||
|                                             ) | ||||
|                                         } | ||||
|                                     } | ||||
|                                     .padding(.trailing, 13.3) | ||||
| @@ -629,12 +599,12 @@ struct LiveRoomViewV2: View { | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             if let preview = viewModel.roulettePreview, viewModel.isShowRoulettePreview { | ||||
|             if !viewModel.roulettePreviewList.isEmpty && viewModel.isShowRoulettePreview { | ||||
|                 RoulettePreviewDialog( | ||||
|                     isShowing: $viewModel.isShowRoulettePreview, | ||||
|                     title: nil, | ||||
|                     onClickSpin: { viewModel.spinRoulette() }, | ||||
|                     preview: preview | ||||
|                     onClickSpin: { viewModel.spinRoulette(rouletteId: $0) }, | ||||
|                     previewList: viewModel.roulettePreviewList | ||||
|                 ) | ||||
|             } | ||||
|              | ||||
|   | ||||
| @@ -13,23 +13,51 @@ struct SelectedButtonView: View { | ||||
|     let isActive: Bool | ||||
|     let isSelected: Bool | ||||
|      | ||||
|     var checkImage = "ic_select_check" | ||||
|     var bgDisabledColor = Color.gray55 | ||||
|     var bgSelectedColor = Color.button | ||||
|     var bgDefaultColor = Color.bg | ||||
|     var textDisabledColor = Color.gray77 | ||||
|     var textSelectedColor = Color.white | ||||
|     var textDefaultColor = Color.button | ||||
|      | ||||
|     var body: some View { | ||||
|         HStack(spacing: 6.7) { | ||||
|             if isSelected { | ||||
|                 Image("ic_select_check") | ||||
|                 Image(checkImage) | ||||
|             } | ||||
|              | ||||
|             Text(title) | ||||
|                 .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                 .foregroundColor(!isActive ? Color.gray77 : isSelected ? .white : Color.button) | ||||
|                 .foregroundColor(!isActive ? textDisabledColor : isSelected ? textSelectedColor : textDefaultColor) | ||||
|         } | ||||
|         .padding(.vertical, 14.3) | ||||
|         .padding(.vertical, isSelected ? 14.3 : 17) | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .background(!isActive ? Color.gray55 : isSelected ? Color.button : Color.bg) | ||||
|         .background(!isActive ? bgDisabledColor : isSelected ? bgSelectedColor : bgDefaultColor) | ||||
|         .cornerRadius(6.7) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
| #Preview("기본") { | ||||
|     SelectedButtonView(title: "테스트", isActive: true, isSelected: true) | ||||
| } | ||||
|  | ||||
| #Preview("이미지와 컬러 수정 - selected") { | ||||
|     SelectedButtonView( | ||||
|         title: "테스트", | ||||
|         isActive: true, | ||||
|         isSelected: true, | ||||
|         bgSelectedColor: Color(hex: "ff14d9"), | ||||
|         textDefaultColor: Color(hex: "ff14d9") | ||||
|     ) | ||||
| } | ||||
|  | ||||
| #Preview("이미지와 컬러 수정 - unselected") { | ||||
|     SelectedButtonView( | ||||
|         title: "테스트", | ||||
|         isActive: true, | ||||
|         isSelected: false, | ||||
|         textDefaultColor: Color(hex: "ff14d9") | ||||
|     ) | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung