룰렛 변경
- 확률 수동 설정 - 여러개의 룰렛이 켜져있을 때 선택하여 돌리기 - 후원 히스토리에 룰렛 히스토리
This commit is contained in:
@@ -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"))
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.vertical, 16.7)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(6.7)
|
||||
|
||||
Image("btn_minus_round_rect")
|
||||
.onTapGesture { onClickSubstract() }
|
||||
|
||||
Image("btn_plus_round_rect")
|
||||
.onTapGesture { onClickPlus() }
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,25 +120,48 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||
if !isLoading {
|
||||
isLoading = true
|
||||
|
||||
if rouletteId > 0 {
|
||||
updateRoulette(onSuccess: onSuccess)
|
||||
} else {
|
||||
createRoulette(onSuccess: onSuccess)
|
||||
if validationOptions() {
|
||||
if rouletteId > 0 {
|
||||
updateRoulette(onSuccess: onSuccess)
|
||||
} else {
|
||||
createRoulette(onSuccess: onSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createRoulette(onSuccess: @escaping (Bool, String) -> Void) {
|
||||
var items = [RouletteItem]()
|
||||
private func validationOptions() -> Bool {
|
||||
var totalPercentage = Float(0)
|
||||
|
||||
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(
|
||||
can: 100,
|
||||
items: [
|
||||
RoulettePreviewItem(title: "옵션1", percent: "10%"),
|
||||
RoulettePreviewItem(title: "옵션2", percent: "90%"),
|
||||
]
|
||||
)
|
||||
previewList: [
|
||||
RoulettePreview(
|
||||
id: 0,
|
||||
can: 100,
|
||||
items: [
|
||||
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]
|
||||
}
|
Reference in New Issue
Block a user