// // LiveRoomCreateView.swift // SodaLive // // Created by klaus on 2023/08/14. // import SwiftUI import Kingfisher struct LiveRoomCreateView: View { @StateObject var keyboardHandler = KeyboardHandler() @StateObject var viewModel = LiveRoomCreateViewModel() @State private var isShowPhotoPicker = false @State private var isShowSelectTagView = false @State private var isShowSelectDateView = false @State private var isShowSelectTimeView = false let timeSettingMode: LiveRoomCreateViewModel.TimeSettingMode let onSuccess: (CreateLiveRoomResponse) -> Void init(timeSettingMode: LiveRoomCreateViewModel.TimeSettingMode, onSuccess: @escaping (CreateLiveRoomResponse) -> Void) { UITextView.appearance().backgroundColor = .clear UIScrollView.appearance().bounces = false self.onSuccess = onSuccess self.timeSettingMode = timeSettingMode } var body: some View { BaseView(isLoading: $viewModel.isLoading) { GeometryReader { proxy in ZStack { VStack(spacing: 0) { HStack(spacing: 0) { Button { AppState.shared.back() } label: { Image("ic_back") .resizable() .frame(width: 20, height: 20) Text("라이브 만들기") .font(.custom(Font.bold.rawValue, size: 18.3)) .foregroundColor(Color(hex: "eeeeee")) } Spacer() if viewModel.isShowGetRecentInfoButton { Text("최근 데이터 가져오기") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "9970ff")) .padding(.vertical, 8) .padding(.horizontal, 10.7) .overlay( RoundedRectangle(cornerRadius: 8) .stroke() .foregroundColor(Color(hex: "9970ff")) ) .onTapGesture { viewModel.getRecentInfo() } } } .padding(.horizontal, 13.3) .frame(height: 50) .background(Color.black) ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { VStack(spacing: 0) { Text("썸네일") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .padding(.horizontal, 13.3) .padding(.top, 13.3) .frame(width: screenSize().width, alignment: .leading) ZStack { if let selectedImage = viewModel.coverImage { Image(uiImage: selectedImage) .resizable() .scaledToFill() .frame(width: 80, height: 116.8, alignment: .top) .cornerRadius(10) } else if let coverImageUrl = viewModel.coverImageUrl { KFImage(URL(string: coverImageUrl)) .resizable() .scaledToFill() .frame(width: 80, height: 116.8, alignment: .top) .clipped() .cornerRadius(10) } else { Image("ic_logo") .resizable() .scaledToFit() .frame(width: 80, height: 116.8) .background(Color(hex: "3e3358")) .cornerRadius(10) } Image("ic_camera") .padding(10) .background(Color(hex: "9970ff")) .cornerRadius(30) .offset(x: 40, y: 40) } .frame(alignment: .bottomTrailing) .padding(.top, 13.3) .onTapGesture { isShowPhotoPicker = true } TitleInputView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) TagSelectView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) } VStack(spacing: 0) { ContentInputView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) if viewModel.roomType != .SECRET { TimeSettingView() .padding(.top, 33.3) } NumberOfPeopleLimitView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) } VStack(spacing: 0) { RoomTypeSettingView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) if UserDefaults.bool(forKey: .auth) { AdultSettingView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) } if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { PriceSettingView() .frame(width: screenSize().width - 26.7) .padding(.top, 33.3) } } HStack(alignment: .top, spacing: 0) { Button(action: { viewModel.createRoom { response in AppState.shared.back() DispatchQueue.main.async { onSuccess(response) } } }) { Text("라이브 오픈하기") .font(.custom(Font.bold.rawValue, size: 18.3)) .foregroundColor(Color.white) .frame(width: screenSize().width - 26.7, height: 50) .background(Color(hex: "9970ff")) .cornerRadius(10) .padding(.vertical, 13.3) } } .frame(width: screenSize().width) .background(Color(hex: "222222")) .cornerRadius(16.7, corners: [.topLeft, .topRight]) .padding(.top, 30) Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: screenSize().width, height: keyboardHandler.keyboardHeight) if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: screenSize().width, height: 15.3) } } } } if isShowSelectDateView { SelectDateView() } if isShowSelectTimeView { SelectTimeView() } if isShowPhotoPicker { ImagePicker( isShowing: $isShowPhotoPicker, selectedImage: $viewModel.coverImage, sourceType: .photoLibrary ) } GeometryReader { proxy in VStack { Spacer() LiveRoomCreateTagView( isShowing: $isShowSelectTagView, selectedTags: $viewModel.tags ) .frame(width: proxy.size.width, height: proxy.size.height * 0.9) .offset(y: isShowSelectTagView ? 0 : proxy.size.height * 0.9) .animation(.easeInOut(duration: 0.49), value: self.isShowSelectTagView) } } .edgesIgnoringSafeArea(.bottom) } .onTapGesture { hideKeyboard() } .edgesIgnoringSafeArea(.bottom) } } .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { GeometryReader { geo in HStack { Spacer() Text(viewModel.errorMessage) .padding(.vertical, 13.3) .frame(width: geo.size.width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) .background(Color(hex: "9970ff")) .foregroundColor(Color.white) .multilineTextAlignment(.center) .cornerRadius(20) .padding(.top, 66.7) Spacer() } } } .onAppear { viewModel.timeSettingMode = timeSettingMode } } @ViewBuilder func TitleInputView() -> some View { VStack(spacing: 0) { Text("제목") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .padding(.horizontal, 13.3) .frame(width: screenSize().width, alignment: .leading) TextField("라이브 제목을 입력하세요", text: $viewModel.title) .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) .accentColor(Color(hex: "9970ff")) .keyboardType(.default) .padding(.top, 12) .padding(.horizontal, 6.7) Rectangle() .frame(height: 1) .foregroundColor(Color(hex: "909090").opacity(0.7)) .padding(.top, 8.3) } } @ViewBuilder func TagSelectView() -> some View { VStack(alignment: .leading, spacing: 13.3) { Text("관심사") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) Button(action: { hideKeyboard() isShowSelectTagView = true }) { Text("관심사 선택") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "9970ff")) .padding(.vertical, 13.7) .frame(width: screenSize().width - 26.7) .background(Color(hex: "9970ff").opacity(0.2)) .cornerRadius(24.3) .overlay( RoundedRectangle(cornerRadius: 24.3) .stroke() .foregroundColor(Color(hex: "9970ff")) ) } ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 10) { ForEach(viewModel.tags, id: \.self) { tag in HStack(spacing: 6.7) { Text(tag) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(.white) Image("ic_circle_x") .onTapGesture { if let index = viewModel.tags.firstIndex(of: tag) { viewModel.tags.remove(at: index) } } } .padding(10) .background(Color(hex: "9970ff")) .cornerRadius(24.3) } } } .padding(.top, 13.3) } } @ViewBuilder func ContentInputView() -> some View { VStack(spacing: 13.3) { HStack(spacing: 0) { Text("공지") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) Spacer() Text("\(viewModel.content.count)자") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "ff5c49")) + Text(" / 1000자") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "777777")) } TextViewWrapper( text: $viewModel.content, placeholder: viewModel.placeholder, textColorHex: "eeeeee", backgroundColorHex: "222222" ) .frame(width: screenSize().width - 26.7, height: 133.3) .cornerRadius(6.7) .padding(.top, 13.3) } } @ViewBuilder func TimeSettingView() -> some View { VStack(spacing: 0) { Text("시간설정") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) HStack(spacing: 13.3) { TimeSettingSelectButton( title: "지금 즉시", timeSettingMode: .NOW, buttonWidth: (screenSize().width - 40) / 2 ) TimeSettingSelectButton( title: "예약 설정", timeSettingMode: .RESERVATION, buttonWidth: (screenSize().width - 40) / 2 ) } .padding(.top, 13.3) if viewModel.timeSettingMode == .RESERVATION { ReservationDateTimeView(buttonWidth: (screenSize().width - 40) / 2) .padding(.top, 22.7) } } } @ViewBuilder func TimeSettingSelectButton( title: String, timeSettingMode: LiveRoomCreateViewModel.TimeSettingMode, buttonWidth: CGFloat ) -> some View { HStack(spacing: 6.7) { if viewModel.timeSettingMode == timeSettingMode { Image("ic_select_check") } Text(title) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor( viewModel.timeSettingMode == timeSettingMode ? .white : Color(hex: "9970ff") ) } .frame(width: buttonWidth, height: 48.7) .background( viewModel.timeSettingMode == timeSettingMode ? Color(hex: "9970ff") : Color(hex: "1f1734") ) .cornerRadius(6.7) .onTapGesture { hideKeyboard() if viewModel.timeSettingMode != timeSettingMode { viewModel.timeSettingMode = timeSettingMode } } } @ViewBuilder func ReservationDateTimeView(buttonWidth: CGFloat) -> some View { HStack(spacing: 13.3) { VStack(alignment: .leading, spacing: 6.7) { Text("예약 날짜") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) Button(action: { hideKeyboard() self.isShowSelectDateView = true }) { Text(viewModel.reservationDateString) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: buttonWidth, height: 48.7) .overlay( RoundedRectangle(cornerRadius: 6.7) .stroke(Color(hex: "9970ff"), lineWidth: 1.3) ) } } VStack(alignment: .leading, spacing: 6.7) { Text("예약 시간") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) Button(action: { hideKeyboard() self.isShowSelectTimeView = true }) { Text(viewModel.reservationTimeString) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: buttonWidth, height: 48.7) .overlay( RoundedRectangle(cornerRadius: 6.7) .stroke(Color(hex: "9970ff"), lineWidth: 1.3) ) } } } .frame(width: screenSize().width) .padding(.vertical, 13.3) .background(Color(hex: "222222")) } @ViewBuilder func NumberOfPeopleLimitView() -> some View { VStack(spacing: 13.3) { Text("참여인원 설정") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) TextField("최대 인원 999명", text: $viewModel.numberOfPeople) .autocapitalization(.none) .disableAutocorrection(true) .multilineTextAlignment(.center) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color(hex: "eeeeee")) .accentColor(Color(hex: "9970ff")) .keyboardType(.numberPad) .padding(.vertical, 15.7) .frame(width: screenSize().width - 26.7, alignment: .center) .background(Color(hex: "222222")) .cornerRadius(6.7) } } @ViewBuilder func SelectDateView() -> some View { GeometryReader { proxy in ZStack { Color .black .opacity(0.5) .edgesIgnoringSafeArea(.all) VStack(spacing: 0) { DatePicker("", selection: $viewModel.reservationDate, in: Date()..., displayedComponents: .date) .datePickerStyle(WheelDatePickerStyle()) .labelsHidden() .environment(\.locale, Locale.init(identifier: "ko")) .frame(width: proxy.size.width) Button(action: { self.isShowSelectDateView = false }) { Text("확인") .font(.system(size: 16)) .foregroundColor(Color(hex: "eeeeee")) .padding(.vertical, 10) .frame(width: proxy.size.width - 53.4) } } .background(Color(hex: "222222")) .cornerRadius(6.7) } .frame(width: proxy.size.width) } } @ViewBuilder func SelectTimeView() -> some View { GeometryReader { proxy in ZStack { Color .black .opacity(0.5) .edgesIgnoringSafeArea(.all) VStack(spacing: 0) { DatePicker("", selection: $viewModel.reservationTime, displayedComponents: .hourAndMinute) .datePickerStyle(WheelDatePickerStyle()) .labelsHidden() .environment(\.locale, Locale.init(identifier: "ko")) .frame(width: proxy.size.width - 53.4) Button(action: { self.isShowSelectTimeView = false }) { Text("확인") .font(.system(size: 16)) .foregroundColor(Color(hex: "eeeeee")) .padding(.vertical, 10) .frame(width: proxy.size.width) } } .background(Color(hex: "222222")) .cornerRadius(6.7) } .frame(width: proxy.size.width) } } @ViewBuilder func RoomTypeSettingView() -> some View { VStack(spacing: 0) { Text("공개 설정") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) HStack(spacing: 13.3) { RoomTypeSelectButton( title: "공개", type: .OPEN, buttonWidth: (screenSize().width - 40) / 2 ) RoomTypeSelectButton( title: "비공개", type: .PRIVATE, buttonWidth: (screenSize().width - 40) / 2 ) } .padding(.top, 13.3) if viewModel.roomType == .PRIVATE { RoomPasswordView() .padding(.top, 33.3) } } } @ViewBuilder func RoomTypeSelectButton(title: String, type: LiveRoomCreateViewModel.LiveRoomType, buttonWidth: CGFloat) -> some View { HStack(spacing: 6.7) { if viewModel.roomType == type { Image("ic_select_check") } Text(title) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor( viewModel.roomType == type ? .white : Color(hex: "9970ff") ) } .frame(width: buttonWidth, height: 48.7) .background( viewModel.roomType == type ? Color(hex: "9970ff") : Color(hex: "1f1734") ) .cornerRadius(6.7) .onTapGesture { hideKeyboard() if viewModel.roomType != type { viewModel.roomType = type } } } @ViewBuilder func RoomPasswordView() -> some View { VStack(spacing: 13.3) { Text("방 비밀번호 입력") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) TextField("방 입장 비밀번호 6자리를 입력해 주세요.", text: $viewModel.password) .autocapitalization(.none) .disableAutocorrection(true) .multilineTextAlignment(.center) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color(hex: "eeeeee")) .accentColor(Color(hex: "9970ff")) .keyboardType(.numberPad) .padding(.vertical, 15.7) .frame(width: screenSize().width - 26.7, alignment: .center) .background(Color(hex: "222222")) .cornerRadius(6.7) } } @ViewBuilder func AdultSettingView() -> some View { VStack(spacing: 13.3) { Text("연령 제한") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) HStack(spacing: 13.3) { AdultSettingSelectButton( title: "전체 연령", isAdult: false, buttonWidth: (screenSize().width - 40) / 2 ) AdultSettingSelectButton( title: "19세 이상", isAdult: true, buttonWidth: (screenSize().width - 40) / 2 ) } } } @ViewBuilder func AdultSettingSelectButton(title: String, isAdult: Bool, buttonWidth: CGFloat) -> some View { HStack(spacing: 6.7) { if viewModel.isAdult == isAdult { Image("ic_select_check") } Text(title) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor( viewModel.isAdult == isAdult ? .white : Color(hex: "9970ff") ) } .frame(width: buttonWidth, height: 48.7) .background( viewModel.isAdult == isAdult ? Color(hex: "9970ff") : Color(hex: "1f1734") ) .cornerRadius(6.7) .onTapGesture { hideKeyboard() if viewModel.isAdult != isAdult { viewModel.isAdult = isAdult } } } @ViewBuilder func PriceSettingView() -> some View { VStack(spacing: 13.3) { Text("티켓 가격") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) HStack(spacing: 13.3) { PriceButtonView(price: 0, buttonWidth: (screenSize().width - 53) / 3) PriceButtonView(price: 100, buttonWidth: (screenSize().width - 53) / 3) PriceButtonView(price: 300, buttonWidth: (screenSize().width - 53) / 3) } HStack(spacing: 13.3) { PriceButtonView(price: 500, buttonWidth: (screenSize().width - 53) / 3) PriceButtonView(price: 1000, buttonWidth: (screenSize().width - 53) / 3) PriceButtonView(price: 2000, buttonWidth: (screenSize().width - 53) / 3) } HStack(spacing: 0) { TextField("", text: $viewModel.priceString) .autocapitalization(.none) .disableAutocorrection(true) .multilineTextAlignment(.center) .font(.custom(Font.bold.rawValue, size: 13.3)) .foregroundColor(Color(hex: "9970ff")) .accentColor(Color(hex: "9970ff")) .keyboardType(.numberPad) Spacer() Text("코인") .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor( Color(hex: "9970ff") ) } .padding(.horizontal, 13.3) .frame(width: screenSize().width - 26.7, height: 48.7) .overlay( RoundedRectangle(cornerRadius: 6.7) .stroke( Color(hex: !viewModel.prices.contains(viewModel.price) ? "9970ff" : "777777" ), lineWidth: 1 ) ) .background( !viewModel.prices.contains(viewModel.price) ? Color(hex:"9970ff").opacity(0.3): Color(hex: "232323") ) } } @ViewBuilder func PriceButtonView(price: Int, buttonWidth: CGFloat) -> some View { HStack(spacing: 6.7) { Text(price == 0 ? "무료" : "\(price) 코인") .font(.custom( viewModel.price == price ? Font.bold.rawValue : Font.medium.rawValue, size: 14.7 )) .foregroundColor( Color(hex: viewModel.price == price ? "9970ff" : "777777") ) } .frame(width: buttonWidth, height: 48.7) .overlay( RoundedRectangle(cornerRadius: 6.7) .stroke( Color(hex: viewModel.price == price ? "9970ff" : "777777"), lineWidth: 1 ) ) .background( viewModel.price == price ? Color(hex:"9970ff").opacity(0.3): Color(hex: "232323") ) .cornerRadius(6.7) .onTapGesture { hideKeyboard() viewModel.priceString = "\(price)" } } }