룰렛 프리셋 적용
This commit is contained in:
		| @@ -7,7 +7,14 @@ | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| struct CreateOrUpdateRouletteRequest: Encodable { | ||||
| struct CreateRouletteRequest: Encodable { | ||||
|     let can: Int | ||||
|     let isActive: Bool | ||||
|     let items: [RouletteItem] | ||||
| } | ||||
|  | ||||
| struct UpdateRouletteRequest: Encodable { | ||||
|     let id: Int | ||||
|     let can: Int | ||||
|     let isActive: Bool | ||||
|     let items: [RouletteItem] | ||||
|   | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  GetNewRouletteResponse.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/23/24. | ||||
| // | ||||
|  | ||||
| struct GetNewRouletteResponse: Decodable { | ||||
|     let id: Int | ||||
|     let can: Int | ||||
|     let isActive: Bool | ||||
|     let items: [RouletteItem] | ||||
| } | ||||
| @@ -25,6 +25,33 @@ struct RouletteSettingsView: View { | ||||
|                      | ||||
|                     ScrollView(.vertical, showsIndicators: false) { | ||||
|                         VStack(spacing: 0) { | ||||
|                             HStack(spacing: 13.3) { | ||||
|                                 SelectedButtonView( | ||||
|                                     title: "룰렛 1", | ||||
|                                     isSelected: viewModel.selectedRoulette == .ROULETTE_1 | ||||
|                                 ) | ||||
|                                 .onTapGesture { | ||||
|                                     viewModel.selectRoulette(selectedRoulette: .ROULETTE_1) | ||||
|                                 } | ||||
|                                  | ||||
|                                 SelectedButtonView( | ||||
|                                     title: "룰렛 2", | ||||
|                                     isSelected: viewModel.selectedRoulette == .ROULETTE_2 | ||||
|                                 ) | ||||
|                                 .onTapGesture { | ||||
|                                     viewModel.selectRoulette(selectedRoulette: .ROULETTE_2) | ||||
|                                 } | ||||
|                                  | ||||
|                                 SelectedButtonView( | ||||
|                                     title: "룰렛 3", | ||||
|                                     isSelected: viewModel.selectedRoulette == .ROULETTE_3 | ||||
|                                 ) | ||||
|                                 .onTapGesture { | ||||
|                                     viewModel.selectRoulette(selectedRoulette: .ROULETTE_3) | ||||
|                                 } | ||||
|                             } | ||||
|                             .padding(.top, 26.7) | ||||
|                              | ||||
|                             HStack(spacing: 0) { | ||||
|                                 Text("룰렛을 활성화 하시겠습니까?") | ||||
|                                     .font(.custom(Font.bold.rawValue, size: 16)) | ||||
| @@ -39,6 +66,7 @@ struct RouletteSettingsView: View { | ||||
|                                         viewModel.isActive.toggle() | ||||
|                                     } | ||||
|                             } | ||||
|                             .padding(.top, 26.7) | ||||
|                              | ||||
|                             VStack(alignment: .leading, spacing: 13.3) { | ||||
|                                 Text("룰렛 금액 설정") | ||||
| @@ -181,7 +209,7 @@ struct RouletteSettingsView: View { | ||||
|                 } | ||||
|             } | ||||
|             .onAppear { | ||||
|                 viewModel.getRoulette(creatorId: UserDefaults.int(forKey: .userId)) | ||||
|                 viewModel.getAllRoulette(creatorId: UserDefaults.int(forKey: .userId)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -8,6 +8,12 @@ | ||||
| import Foundation | ||||
| import Combine | ||||
|  | ||||
| enum SelectedRoulette: Int { | ||||
|     case ROULETTE_1 = 0 | ||||
|     case ROULETTE_2 = 1 | ||||
|     case ROULETTE_3 = 2 | ||||
| } | ||||
|  | ||||
| final class RouletteSettingsViewModel: ObservableObject { | ||||
|     private let repository = RouletteRepository() | ||||
|     private var subscription = Set<AnyCancellable>() | ||||
| @@ -34,7 +40,11 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|     } | ||||
|     @Published var previewData: RoulettePreview? = nil | ||||
|      | ||||
|     @Published var selectedRoulette: SelectedRoulette? = nil | ||||
|      | ||||
|     var can = 0 | ||||
|     private var rouletteId = 0 | ||||
|     private var rouletteList = [GetNewRouletteResponse]() | ||||
|      | ||||
|     func plusWeight(index: Int) { | ||||
|         options[index].weight += 1 | ||||
| @@ -84,9 +94,9 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|         self.options.append(contentsOf: options) | ||||
|     } | ||||
|      | ||||
|     func getRoulette(creatorId: Int) { | ||||
|     func getAllRoulette(creatorId: Int) { | ||||
|         self.isLoading = true | ||||
|         repository.getRoulette(creatorId: creatorId) | ||||
|         repository.getAllRoulette(creatorId: creatorId) | ||||
|             .sink { result in | ||||
|                 switch result { | ||||
|                 case .finished: | ||||
| @@ -100,32 +110,15 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|                  | ||||
|                 do { | ||||
|                     let jsonDecoder = JSONDecoder() | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponse<GetRouletteResponse>.self, from: responseData) | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponse<[GetNewRouletteResponse]>.self, from: responseData) | ||||
|                      | ||||
|                     if let data = decoded.data, decoded.success { | ||||
|                         self.isActive = data.isActive | ||||
|                          | ||||
|                         if data.can > 0 { | ||||
|                             self.canText = String(data.can) | ||||
|                         } else { | ||||
|                             self.canText = "" | ||||
|                         } | ||||
|                          | ||||
|                         if !data.items.isEmpty { | ||||
|                             let options = data.items.map { | ||||
|                                 RouletteOption(title: $0.title, weight: $0.weight) | ||||
|                             } | ||||
|                             removeAllAndAddOptions(options: options) | ||||
|                             recalculatePercentages() | ||||
|                         } else { | ||||
|                             self.addOption() | ||||
|                             self.addOption() | ||||
|                         } | ||||
|                         rouletteList.removeAll() | ||||
|                         rouletteList.append(contentsOf: data) | ||||
|                         selectRoulette(selectedRoulette: .ROULETTE_1) | ||||
|                     } else { | ||||
|                         self.isActive = false | ||||
|                         self.canText = "" | ||||
|                         self.addOption() | ||||
|                         self.addOption() | ||||
|                         self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         self.isShowErrorPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
| @@ -159,47 +152,172 @@ final class RouletteSettingsViewModel: ObservableObject { | ||||
|         if !isLoading { | ||||
|             isLoading = true | ||||
|              | ||||
|             var items = [RouletteItem]() | ||||
|             for option in options { | ||||
|                 if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { | ||||
|                     isLoading = false | ||||
|                     errorMessage = "옵션은 빈칸일 수 없습니다." | ||||
|                     isShowErrorPopup = true | ||||
|                     return | ||||
|                 } | ||||
|                  | ||||
|                 items.append(RouletteItem(title: option.title, weight: option.weight)) | ||||
|             if rouletteId > 0 { | ||||
|                 updateRoulette(onSuccess: onSuccess) | ||||
|             } else { | ||||
|                 createRoulette(onSuccess: onSuccess) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func createRoulette(onSuccess: @escaping (Bool) -> Void) { | ||||
|         var items = [RouletteItem]() | ||||
|         for option in options { | ||||
|             if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { | ||||
|                 isLoading = false | ||||
|                 errorMessage = "옵션은 빈칸일 수 없습니다." | ||||
|                 isShowErrorPopup = true | ||||
|                 return | ||||
|             } | ||||
|              | ||||
|             let request = CreateOrUpdateRouletteRequest(can: can, isActive: isActive, items: items) | ||||
|             repository.createOrUpdateRoulette(request: request) | ||||
|                 .sink { result in | ||||
|                     switch result { | ||||
|                     case .finished: | ||||
|                         DEBUG_LOG("finish") | ||||
|                     case .failure(let error): | ||||
|                         ERROR_LOG(error.localizedDescription) | ||||
|                     } | ||||
|                 } receiveValue: { [unowned self] response in | ||||
|                     self.isLoading = false | ||||
|                     let responseData = response.data | ||||
|             items.append(RouletteItem(title: option.title, weight: option.weight)) | ||||
|         } | ||||
|          | ||||
|         let request = CreateRouletteRequest(can: can, isActive: isActive, items: items) | ||||
|         repository.createRoulette(request: request) | ||||
|             .sink { result in | ||||
|                 switch result { | ||||
|                 case .finished: | ||||
|                     DEBUG_LOG("finish") | ||||
|                 case .failure(let error): | ||||
|                     ERROR_LOG(error.localizedDescription) | ||||
|                 } | ||||
|             } receiveValue: { [unowned self] response in | ||||
|                 self.isLoading = false | ||||
|                 let responseData = response.data | ||||
|                  | ||||
|                 do { | ||||
|                     let jsonDecoder = JSONDecoder() | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) | ||||
|                      | ||||
|                     do { | ||||
|                         let jsonDecoder = JSONDecoder() | ||||
|                         let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) | ||||
|                          | ||||
|                         if decoded.success { | ||||
|                             onSuccess(isActive) | ||||
|                         } else { | ||||
|                             self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                             self.isShowErrorPopup = true | ||||
|                         } | ||||
|                     } catch { | ||||
|                     if decoded.success { | ||||
|                         onSuccess(isActive) | ||||
|                     } else { | ||||
|                         self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         self.isShowErrorPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowErrorPopup = true | ||||
|                 } | ||||
|                 .store(in: &subscription) | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
|      | ||||
|     private func updateRoulette(onSuccess: @escaping (Bool) -> Void) { | ||||
|         var items = [RouletteItem]() | ||||
|         for option in options { | ||||
|             if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { | ||||
|                 isLoading = false | ||||
|                 errorMessage = "옵션은 빈칸일 수 없습니다." | ||||
|                 isShowErrorPopup = true | ||||
|                 return | ||||
|             } | ||||
|              | ||||
|             items.append(RouletteItem(title: option.title, weight: option.weight)) | ||||
|         } | ||||
|          | ||||
|         let selectedRoulette = rouletteList[selectedRoulette!.rawValue] | ||||
|         if selectedRoulette.isActive == isActive && selectedRoulette.can == can && selectedRoulette.items == items { | ||||
|             self.errorMessage = "변동사항이 없습니다." | ||||
|             self.isShowErrorPopup = true | ||||
|             self.isLoading = false | ||||
|             return | ||||
|         } | ||||
|          | ||||
|         let selectedRouletteTitle: String | ||||
|         let successMessage: String | ||||
|          | ||||
|         switch (self.selectedRoulette) { | ||||
|         case .ROULETTE_2: | ||||
|             selectedRouletteTitle = "룰렛 2" | ||||
|              | ||||
|         case .ROULETTE_3: | ||||
|             selectedRouletteTitle = "룰렛 3" | ||||
|          | ||||
|         default: | ||||
|             selectedRouletteTitle = "룰렛 1" | ||||
|         } | ||||
|          | ||||
|         if isActive { | ||||
|             successMessage = "\(selectedRouletteTitle)을 활성화 했습니다." | ||||
|         } else { | ||||
|             successMessage = "\(selectedRouletteTitle)을 비활성화 했습니다." | ||||
|         } | ||||
|          | ||||
|         let request = UpdateRouletteRequest(id: rouletteId, can: can, isActive: isActive, items: items) | ||||
|         repository.updateRoulette(request: request) | ||||
|             .sink { result in | ||||
|                 switch result { | ||||
|                 case .finished: | ||||
|                     DEBUG_LOG("finish") | ||||
|                 case .failure(let error): | ||||
|                     ERROR_LOG(error.localizedDescription) | ||||
|                 } | ||||
|             } receiveValue: { [unowned self] response in | ||||
|                 self.isLoading = false | ||||
|                 let responseData = response.data | ||||
|                  | ||||
|                 do { | ||||
|                     let jsonDecoder = JSONDecoder() | ||||
|                     let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) | ||||
|                      | ||||
|                     if decoded.success { | ||||
|                         self.errorMessage = successMessage | ||||
|                         self.isShowErrorPopup = true | ||||
|                         onSuccess(isActive) | ||||
|                     } else { | ||||
|                         self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                         self.isShowErrorPopup = true | ||||
|                     } | ||||
|                 } catch { | ||||
|                     self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." | ||||
|                     self.isShowErrorPopup = true | ||||
|                 } | ||||
|             } | ||||
|             .store(in: &subscription) | ||||
|     } | ||||
|      | ||||
|     func selectRoulette(selectedRoulette: SelectedRoulette) { | ||||
|         if rouletteList.isEmpty && (selectedRoulette == .ROULETTE_2 || selectedRoulette == .ROULETTE_3) { | ||||
|             errorMessage = "룰렛 1만 선택 가능" | ||||
|             isShowErrorPopup = true | ||||
|             return | ||||
|         } | ||||
|          | ||||
|         if rouletteList.count == 1 && selectedRoulette == .ROULETTE_3 { | ||||
|             errorMessage = "룰렛 1, 룰렛2만 선택 가능" | ||||
|             isShowErrorPopup = true | ||||
|             return | ||||
|         } | ||||
|          | ||||
|         if self.selectedRoulette != selectedRoulette { | ||||
|             self.selectedRoulette = selectedRoulette | ||||
|              | ||||
|             if rouletteList.count > selectedRoulette.rawValue { | ||||
|                 let roulette = rouletteList[selectedRoulette.rawValue] | ||||
|                 if roulette.can > 0 { | ||||
|                     self.canText = String(roulette.can) | ||||
|                 } else { | ||||
|                     self.canText = "" | ||||
|                 } | ||||
|                  | ||||
|                 self.rouletteId = roulette.id | ||||
|                 self.isActive = roulette.isActive | ||||
|                 let options = roulette.items.map { | ||||
|                     RouletteOption(title: $0.title, weight: $0.weight) | ||||
|                 } | ||||
|                 removeAllAndAddOptions(options: options) | ||||
|                 recalculatePercentages() | ||||
|             } else { | ||||
|                 self.canText = "" | ||||
|                 self.isActive = false | ||||
|                 self.rouletteId = 0 | ||||
|                  | ||||
|                 options.removeAll() | ||||
|                 self.addOption() | ||||
|                 self.addOption() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ struct GetRouletteResponse: Decodable { | ||||
|     let items: [RouletteItem] | ||||
| } | ||||
|  | ||||
| struct RouletteItem: Codable { | ||||
| struct RouletteItem: Codable, Equatable { | ||||
|     let title: String | ||||
|     let weight: Int | ||||
| } | ||||
|   | ||||
| @@ -10,7 +10,9 @@ import Moya | ||||
|  | ||||
| enum RouletteApi { | ||||
|     case getRoulette(creatorId: Int) | ||||
|     case createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest) | ||||
|     case getAllRoulette(creatorId: Int) | ||||
|     case createRoulette(request: CreateRouletteRequest) | ||||
|     case updateRoulette(request: UpdateRouletteRequest) | ||||
|     case spinRoulette(request: SpinRouletteRequest) | ||||
|     case refundRouletteDonation(roomId: Int) | ||||
| } | ||||
| @@ -22,24 +24,30 @@ extension RouletteApi: TargetType { | ||||
|      | ||||
|     var path: String { | ||||
|         switch self { | ||||
|         case .getRoulette, .createOrUpdateRoulette: | ||||
|             return "/roulette" | ||||
|         case .getRoulette, .createRoulette, .updateRoulette: | ||||
|             return "/new-roulette" | ||||
|              | ||||
|         case .getAllRoulette: | ||||
|             return "/new-roulette/creator" | ||||
|              | ||||
|         case .spinRoulette: | ||||
|             return "/roulette/spin" | ||||
|             return "/new-roulette/spin" | ||||
|              | ||||
|         case .refundRouletteDonation(let roomId): | ||||
|             return "/roulette/refund/\(roomId)" | ||||
|             return "/new-roulette/refund/\(roomId)" | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var method: Moya.Method { | ||||
|         switch self { | ||||
|         case .getRoulette: | ||||
|         case .getRoulette, .getAllRoulette: | ||||
|             return .get | ||||
|              | ||||
|         case .createOrUpdateRoulette, .spinRoulette, .refundRouletteDonation: | ||||
|         case .createRoulette, .spinRoulette, .refundRouletteDonation: | ||||
|             return .post | ||||
|              | ||||
|         case .updateRoulette: | ||||
|             return .put | ||||
|         } | ||||
|     } | ||||
|      | ||||
| @@ -55,7 +63,20 @@ extension RouletteApi: TargetType { | ||||
|                 encoding: URLEncoding.queryString | ||||
|             ) | ||||
|              | ||||
|         case .createOrUpdateRoulette(let request): | ||||
|         case .getAllRoulette(let creatorId): | ||||
|             let parameters = [ | ||||
|                 "creatorId": creatorId | ||||
|             ] as [String : Any] | ||||
|              | ||||
|             return .requestParameters( | ||||
|                 parameters: parameters, | ||||
|                 encoding: URLEncoding.queryString | ||||
|             ) | ||||
|              | ||||
|         case .createRoulette(let request): | ||||
|             return .requestJSONEncodable(request) | ||||
|              | ||||
|         case .updateRoulette(let request): | ||||
|             return .requestJSONEncodable(request) | ||||
|              | ||||
|         case .spinRoulette(let request): | ||||
|   | ||||
| @@ -17,8 +17,16 @@ final class RouletteRepository { | ||||
|         return api.requestPublisher(.getRoulette(creatorId: creatorId)) | ||||
|     } | ||||
|      | ||||
|     func createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.createOrUpdateRoulette(request: request)) | ||||
|     func getAllRoulette(creatorId: Int) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.getAllRoulette(creatorId: creatorId)) | ||||
|     } | ||||
|      | ||||
|     func createRoulette(request: CreateRouletteRequest) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.createRoulette(request: request)) | ||||
|     } | ||||
|      | ||||
|     func updateRoulette(request: UpdateRouletteRequest) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.updateRoulette(request: request)) | ||||
|     } | ||||
|      | ||||
|     func spinRoulette(request: SpinRouletteRequest) -> AnyPublisher<Response, MoyaError> { | ||||
|   | ||||
							
								
								
									
										34
									
								
								SodaLive/Sources/UI/Component/SelectedButtonView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								SodaLive/Sources/UI/Component/SelectedButtonView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| // | ||||
| //  SelectedButtonView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2/23/24. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct SelectedButtonView: View { | ||||
|      | ||||
|     let title: String | ||||
|     let isSelected: Bool | ||||
|      | ||||
|     var body: some View { | ||||
|         HStack(spacing: 6.7) { | ||||
|             if isSelected { | ||||
|                 Image("ic_select_check") | ||||
|             } | ||||
|              | ||||
|             Text(title) | ||||
|                 .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||
|                 .foregroundColor(isSelected ? .white : Color.button) | ||||
|         } | ||||
|         .padding(.vertical, 14.3) | ||||
|         .frame(maxWidth: .infinity) | ||||
|         .background(isSelected ? Color.button : Color.bg) | ||||
|         .cornerRadius(6.7) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     SelectedButtonView(title: "테스트", isSelected: true) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung