diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/CreateOrUpdateRouletteRequest.swift b/SodaLive/Sources/Live/Room/Routlette/Config/CreateOrUpdateRouletteRequest.swift index 50b2f4c..d22500c 100644 --- a/SodaLive/Sources/Live/Room/Routlette/Config/CreateOrUpdateRouletteRequest.swift +++ b/SodaLive/Sources/Live/Room/Routlette/Config/CreateOrUpdateRouletteRequest.swift @@ -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] diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/GetNewRouletteResponse.swift b/SodaLive/Sources/Live/Room/Routlette/Config/GetNewRouletteResponse.swift new file mode 100644 index 0000000..933e85f --- /dev/null +++ b/SodaLive/Sources/Live/Room/Routlette/Config/GetNewRouletteResponse.swift @@ -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] +} diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift index ece0466..f785ccf 100644 --- a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift @@ -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)) } } } diff --git a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift index be20f16..2cf63fb 100644 --- a/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift +++ b/SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift @@ -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() @@ -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.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() + } } } } diff --git a/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift b/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift index ac4395d..02a61ec 100644 --- a/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift +++ b/SodaLive/Sources/Live/Room/Routlette/GetRouletteResponse.swift @@ -11,7 +11,7 @@ struct GetRouletteResponse: Decodable { let items: [RouletteItem] } -struct RouletteItem: Codable { +struct RouletteItem: Codable, Equatable { let title: String let weight: Int } diff --git a/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift b/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift index 299f0a4..79399fa 100644 --- a/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift +++ b/SodaLive/Sources/Live/Room/Routlette/RouletteApi.swift @@ -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): diff --git a/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift b/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift index 4ae7f61..c6313e1 100644 --- a/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift +++ b/SodaLive/Sources/Live/Room/Routlette/RouletteRepository.swift @@ -17,8 +17,16 @@ final class RouletteRepository { return api.requestPublisher(.getRoulette(creatorId: creatorId)) } - func createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest) -> AnyPublisher { - return api.requestPublisher(.createOrUpdateRoulette(request: request)) + func getAllRoulette(creatorId: Int) -> AnyPublisher { + return api.requestPublisher(.getAllRoulette(creatorId: creatorId)) + } + + func createRoulette(request: CreateRouletteRequest) -> AnyPublisher { + return api.requestPublisher(.createRoulette(request: request)) + } + + func updateRoulette(request: UpdateRouletteRequest) -> AnyPublisher { + return api.requestPublisher(.updateRoulette(request: request)) } func spinRoulette(request: SpinRouletteRequest) -> AnyPublisher { diff --git a/SodaLive/Sources/UI/Component/SelectedButtonView.swift b/SodaLive/Sources/UI/Component/SelectedButtonView.swift new file mode 100644 index 0000000..bb11357 --- /dev/null +++ b/SodaLive/Sources/UI/Component/SelectedButtonView.swift @@ -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) +}