- 룰렛 돌리기 API 연동

- 룰렛 돌린 결과 전송
This commit is contained in:
Yu Sung 2023-12-07 04:29:03 +09:00
parent 5682adf967
commit 0af16ac000
8 changed files with 333 additions and 3 deletions

View File

@ -8,7 +8,7 @@
import Foundation
enum LiveRoomChatType: String {
case CHAT, DONATION, JOIN
case CHAT, DONATION, JOIN, ROULETTE_DONATION
}
protocol LiveRoomChat {
@ -35,6 +35,14 @@ struct LiveRoomDonationChat: LiveRoomChat {
var type: LiveRoomChatType = .DONATION
}
struct LiveRoomRouletteDonationChat: LiveRoomChat {
let profileUrl: String
let nickname: String
let rouletteResult: String
var type: LiveRoomChatType = .ROULETTE_DONATION
}
struct LiveRoomJoinChat: LiveRoomChat {
let nickname: String

View File

@ -9,7 +9,7 @@ import Foundation
struct LiveRoomChatRawMessage: Codable {
enum LiveRoomChatRawMessageType: String, Codable {
case DONATION, EDIT_ROOM_INFO, SET_MANAGER, TOGGLE_ROULETTE
case DONATION, EDIT_ROOM_INFO, SET_MANAGER, TOGGLE_ROULETTE, ROULETTE_DONATION
}
let type: LiveRoomChatRawMessageType

View File

@ -0,0 +1,64 @@
//
// LiveRoomRouletteDonationChatItemView.swift
// SodaLive
//
// Created by klaus on 2023/12/07.
//
import SwiftUI
import Kingfisher
struct LiveRoomRouletteDonationChatItemView: View {
let chatMessage: LiveRoomRouletteDonationChat
var body: some View {
HStack(spacing: 13.3) {
ZStack(alignment: .bottomTrailing) {
KFImage(URL(string: chatMessage.profileUrl))
.resizable()
.scaledToFill()
.frame(width: 33.3, height: 33.3, alignment: .top)
.clipped()
.cornerRadius(23.3)
Image("ic_roulette")
.resizable()
.frame(width: 20, height: 20)
}
VStack(alignment: .leading, spacing: 6.7) {
HStack(spacing: 0) {
Text(chatMessage.nickname)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(.white)
Text("님의 룰렛 결과?")
.font(.custom(Font.light.rawValue, size: 12))
.foregroundColor(.white)
}
HStack(spacing: 0) {
Text("[\(chatMessage.rouletteResult)]")
.font(.custom(Font.medium.rawValue, size: 13))
.foregroundColor(Color(hex: "ffe500"))
Text(" 당첨!")
.font(.custom(Font.medium.rawValue, size: 13))
.foregroundColor(.white)
}
}
}
.padding(13)
.frame(width: screenSize().width - 86, alignment: .leading)
.background(Color(hex: "c25264"))
.cornerRadius(10)
.padding(.leading, 20)
}
}
struct LiveRoomRouletteDonationChatItemView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomRouletteDonationChatItemView(chatMessage: LiveRoomRouletteDonationChat(profileUrl: "", nickname: "유저일", rouletteResult: "옵션1"))
}
}

View File

@ -327,6 +327,9 @@ struct LiveRoomView: View {
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.padding(.bottom, 13.3)
.onTapGesture {
viewModel.showRoulette()
}
}
}
@ -691,6 +694,19 @@ struct LiveRoomView: View {
}
}
if let preview = viewModel.roulettePreview, viewModel.isShowRoulettePreview {
RoulettePreviewDialog(
isShowing: $viewModel.isShowRoulettePreview,
title: nil,
onClickSpin: { spinRoulette() },
preview: preview
)
}
if viewModel.isShowRoulette {
}
if viewModel.isLoading && viewModel.liveRoomInfo == nil {
LoadingView()
}
@ -927,6 +943,10 @@ struct LiveRoomView: View {
LazyVGrid(columns: chatColumns, alignment: .leading, spacing: 20) {
ForEach(0..<viewModel.messages.count, id: \.self) { index in
switch (viewModel.messages[index].type) {
case LiveRoomChatType.ROULETTE_DONATION:
let chatMessage = viewModel.messages[index] as! LiveRoomRouletteDonationChat
LiveRoomRouletteDonationChatItemView(chatMessage: chatMessage)
case LiveRoomChatType.DONATION:
let chatMessage = viewModel.messages[index] as! LiveRoomDonationChat
LiveRoomDonationChatItemView(chatMessage: chatMessage)
@ -955,6 +975,10 @@ struct LiveRoomView: View {
}
}
}
private func spinRoulette() {
viewModel.spinRoulette()
}
}
struct LiveRoomView_Previews: PreviewProvider {

View File

@ -21,6 +21,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
private let repository = LiveRepository()
private let userRepository = UserRepository()
private let reportRepository = ReportRepository()
private let rouletteRepository = RouletteRepository()
private var subscription = Set<AnyCancellable>()
@Published var chatMessage = ""
@ -136,6 +137,14 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
@Published var isShowRouletteSettings = false
@Published var isShowRoulettePreview = false
@Published var roulettePreview: RoulettePreview? = nil
@Published var isShowRoulette = false
@Published var rouletteItems = [RouletteItem]()
@Published var rouletteSelectedItem = ""
var rouletteCan = 0
var timer: DispatchSourceTimer?
func setOriginOffset(_ offset: CGFloat) {
@ -1342,6 +1351,196 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
)
)
}
func showRoulette() {
if let liveRoomInfo = liveRoomInfo, !isLoading {
isLoading = true
rouletteRepository.getRoulette(creatorId: liveRoomInfo.creatorId)
.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(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))
self.isShowRoulettePreview = true
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
}
self.isShowErrorPopup = true
}
} catch {
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
self.isShowErrorPopup = true
}
}
.store(in: &subscription)
}
}
func spinRoulette() {
if !isLoading {
isLoading = true
rouletteRepository.spinRoulette(request: SpinRouletteRequest(roomId: AppState.shared.roomId))
.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(ApiResponse<GetRouletteResponse>.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)
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
}
self.isShowErrorPopup = true
}
} catch {
self.errorMessage = "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
self.isShowErrorPopup = true
}
}
.store(in: &subscription)
}
}
func sendRouletteDonation() {
let rawMessage = rouletteSelectedItem
let rouletteRawMessage = LiveRoomChatRawMessage(
type: .ROULETTE_DONATION,
message: rawMessage,
can: rouletteCan,
donationMessage: ""
)
self.agora.sendRawMessageToGroup(
rawMessage: rouletteRawMessage,
completion: { [unowned self] errorCode in
if errorCode == .errorOk {
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
self.messages.append(
LiveRoomRouletteDonationChat(
profileUrl: profileUrl,
nickname: nickname,
rouletteResult: rawMessage
)
)
totalDonationCan += rouletteCan
self.rouletteItems.removeAll()
self.rouletteSelectedItem = ""
self.rouletteCan = 0
self.messageChangeFlag.toggle()
if self.messages.count > 100 {
self.messages.remove(at: 0)
}
} else {
self.refundRouletteDonation()
}
},
fail: { [unowned self] in
self.refundRouletteDonation()
}
)
}
private func calculatePercentages(options: [RouletteItem]) -> [RoulettePreviewItem] {
let totalWeight = options.reduce(0) { $0 + $1.weight }
let updatedOptions = options.map { option in
RoulettePreviewItem(title: option.title, percent: "\(Int(Float(option.weight) / Float(totalWeight) * Float(100)))%")
}
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)
self.rouletteSelectedItem = rouletteItems[Int(arc4random_uniform(UInt32(rouletteItems.count)))]
self.rouletteCan = can
sendRouletteDonation()
}
private func refundRouletteDonation() {
isLoading = true
rouletteRepository.refundRouletteDonation(roomId: AppState.shared.roomId)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] response in
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
self.isLoading = false
if decoded.success {
self.popupContent = "후원에 실패했습니다.\n다시 후원해주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.popupContent = "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
}
self.isShowPopup = true
}
} catch {
self.isLoading = false
self.popupContent = "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
}
extension LiveRoomViewModel: AgoraRtcEngineDelegate {

View File

@ -11,6 +11,8 @@ import Moya
enum RouletteApi {
case getRoulette(creatorId: Int)
case createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest)
case spinRoulette(request: SpinRouletteRequest)
case refundRouletteDonation(roomId: Int)
}
extension RouletteApi: TargetType {
@ -22,6 +24,12 @@ extension RouletteApi: TargetType {
switch self {
case .getRoulette, .createOrUpdateRoulette:
return "/roulette"
case .spinRoulette:
return "/roulette/spin"
case .refundRouletteDonation(let roomId):
return "/roulette/refund/\(roomId)"
}
}
@ -30,7 +38,7 @@ extension RouletteApi: TargetType {
case .getRoulette:
return .get
case .createOrUpdateRoulette:
case .createOrUpdateRoulette, .spinRoulette, .refundRouletteDonation:
return .post
}
}
@ -49,6 +57,12 @@ extension RouletteApi: TargetType {
case .createOrUpdateRoulette(let request):
return .requestJSONEncodable(request)
case .spinRoulette(let request):
return .requestJSONEncodable(request)
case .refundRouletteDonation:
return .requestPlain
}
}

View File

@ -20,5 +20,13 @@ final class RouletteRepository {
func createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.createOrUpdateRoulette(request: request))
}
func spinRoulette(request: SpinRouletteRequest) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.spinRoulette(request: request))
}
func refundRouletteDonation(roomId: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.refundRouletteDonation(roomId: roomId))
}
}

View File

@ -0,0 +1,13 @@
//
// SpinRouletteRequest.swift
// SodaLive
//
// Created by klaus on 2023/12/07.
//
import Foundation
struct SpinRouletteRequest: Encodable {
let roomId: Int
let container: String = "ios"
}