// // RouletteSettingsView.swift // SodaLive // // Created by klaus on 2023/12/05. // import SwiftUI enum RouletteSettingsFocusField: Hashable { case canAmount case optionTitle(UUID) case optionPercentage(UUID) } struct RouletteSettingsView: View { @StateObject var keyboardHandler = KeyboardHandler() @StateObject var viewModel = RouletteSettingsViewModel() @FocusState private var focusedField: RouletteSettingsFocusField? @Binding var isShowing: Bool let availableActive: Bool let onComplete: (Bool, String) -> Void var body: some View { GeometryReader { proxy in BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { DetailNavigationBar(title: String(localized: "룰렛 설정")) { isShowing = false } ScrollViewReader { scrollProxy in ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { HStack(spacing: 13.3) { SelectedButtonView( title: I18n.Common.roulette1, isActive: true, isSelected: viewModel.selectedRoulette == .ROULETTE_1 ) .onTapGesture { viewModel.selectRoulette(selectedRoulette: .ROULETTE_1) } SelectedButtonView( title: I18n.Common.roulette2, isActive: viewModel.rouletteList.count > 0, isSelected: viewModel.selectedRoulette == .ROULETTE_2, checkImage: "ic_select_check_black", bgSelectedColor: Color(hex: "ffcb14"), textSelectedColor: Color.black, textDefaultColor: Color(hex: "ffcb14") ) .onTapGesture { viewModel.selectRoulette(selectedRoulette: .ROULETTE_2) } SelectedButtonView( title: I18n.Common.roulette3, isActive: viewModel.rouletteList.count > 1, isSelected: viewModel.selectedRoulette == .ROULETTE_3, bgSelectedColor: Color(hex: "ff14d9"), textDefaultColor: Color(hex: "ff14d9") ) .onTapGesture { viewModel.selectRoulette(selectedRoulette: .ROULETTE_3) } } .padding(.top, 26.7) if availableActive { HStack(spacing: 0) { Text("룰렛을 활성화 하시겠습니까?") .appFont(size: 16, weight: .bold) .foregroundColor(Color.grayee) Spacer() Image(viewModel.isActive ? "btn_toggle_on_big" : "btn_toggle_off_big") .resizable() .frame(width: 44, height: 27) .onTapGesture { viewModel.isActive.toggle() } } .padding(.top, 26.7) } VStack(alignment: .leading, spacing: 13.3) { Text("룰렛 금액 설정") .appFont(size: 16, weight: .bold) .foregroundColor(Color.grayee) HStack(spacing: 8) { TextField("룰렛 금액을 입력해 주세요 (최소 5캔)", text: Binding( get: { self.viewModel.canText }, set: { newValue in self.viewModel.canText = newValue.filter { "0123456789".contains($0) } } )) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) .keyboardType(.numberPad) .focused($focusedField, equals: .canAmount) .padding(.horizontal, 13.3) .padding(.vertical, 16.7) .frame(maxWidth: .infinity) .background(Color.gray22) .cornerRadius(6.7) Spacer() Text("캔") .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) } } .padding(.top, 26.7) .id("roulette_can_input") VStack(alignment: .leading, spacing: 21.3) { Text("룰렛 옵션 설정") .appFont(size: 16, weight: .bold) .foregroundColor(Color.grayee) HStack(spacing: 0) { Text("※ 룰렛 옵션은 최소 2개,\n최대 10개까지 설정할 수 있습니다.") .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.mainRed) Spacer() Image("btn_add") .onTapGesture { viewModel.addOption() } } } .padding(.top, 26.7) HStack(spacing: 0) { Text("옵션 확률 합계") .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.grayee) Spacer() Text("( \(String(format: "%.2f", viewModel.totalPercentage))% )") .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.grayee) } .padding(.top, 21.3) VStack(spacing: 21.3) { ForEach(Array(viewModel.options.enumerated()), id: \.element.id) { index, option in RouletteSettingsOptionView( option: option, index: index, focusedField: $focusedField, onClickDelete: { viewModel.deleteOption(index: index) }, calculateTotalPercentage: { viewModel.calculateTotalPercentage() } ) .id(option.id) } } .padding(.top, 21.3) } .padding(.horizontal, 13.3) .padding(.bottom, keyboardHandler.keyboardHeight) .onChange(of: focusedField) { _ in scrollToFocusedField(scrollProxy: scrollProxy) } .onChange(of: keyboardHandler.keyboardHeight) { height in if height > 0, focusedField != nil { scrollToFocusedField(scrollProxy: scrollProxy) } } } } Spacer() HStack(spacing: 13.3) { Text("미리보기") .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.button) .padding(.vertical, 16) .frame(maxWidth: .infinity) .overlay( RoundedRectangle(cornerRadius: 10) .strokeBorder(lineWidth: 1) .foregroundColor(Color.button) ) .onTapGesture { viewModel.onClickPreview() } Text("설정완료") .appFont(size: 18.3, weight: .bold) .foregroundColor(.white) .padding(.vertical, 16) .frame(maxWidth: .infinity) .background(Color.button) .cornerRadius(10) .onTapGesture { viewModel.createOrUpdateRoulette { onComplete($0, $1) isShowing = false } } } .padding(13.3) .background(Color.gray22) .cornerRadius(16.7, corners: [.topLeft, .topRight]) if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color.gray22) .frame(width: screenSize().width, height: 15.3) } } .onTapGesture { focusedField = nil hideKeyboard() } if let preview = viewModel.previewData, viewModel.isShowPreview { RoulettePreviewDialog( isShowing: $viewModel.isShowPreview, title: "룰렛 미리보기", onClickSpin: nil, previewList: [preview] ) } } .ignoresSafeArea(edges: .bottom) .popup(isPresented: $viewModel.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1.3) { GeometryReader { geo in HStack { Spacer() Text(viewModel.errorMessage) .padding(.vertical, 13.3) .frame(width: geo.size.width - 66.7, alignment: .center) .appFont(size: 12, weight: .medium) .background(Color.button) .foregroundColor(Color.white) .multilineTextAlignment(.center) .cornerRadius(20) .padding(.top, 66.7) Spacer() } } } .onAppear { viewModel.availableActive = availableActive viewModel.getAllRoulette(creatorId: UserDefaults.int(forKey: .userId)) } } } private func scrollToFocusedField(scrollProxy: ScrollViewProxy) { guard let focusedField else { return } withAnimation(.easeOut(duration: 0.2)) { switch focusedField { case .canAmount: scrollProxy.scrollTo("roulette_can_input", anchor: .center) case .optionTitle(let id), .optionPercentage(let id): scrollProxy.scrollTo(id, anchor: .center) } } } } struct RouletteSettingsView_Previews: PreviewProvider { static var previews: some View { RouletteSettingsView(isShowing: .constant(true), availableActive: true) { _, _ in } } }