// // ProfileUpdateView.swift // SodaLive // // Created by klaus on 2023/08/19. // import SwiftUI import Kingfisher struct ProfileUpdateView: View { @StateObject var viewModel = ProfileUpdateViewModel() @StateObject var keyboardHandler = KeyboardHandler() @State private var isShowPhotoPicker = false @State private var isShowSelectTagView = false let refresh: () -> Void @ViewBuilder func EmailAndPasswordView() -> some View { VStack(spacing: 26.7) { VStack(alignment: .leading, spacing: 0) { Text("이메일") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "eeeeee")) .padding(.leading, 6.7) Text(viewModel.email) .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "777777")) .padding(.top, 12) .padding(.leading, 6.7) Divider() .frame(height: 0.3) .foregroundColor(Color(hex: "909090")) .padding(.top, 8.3) } HStack(alignment: .bottom, spacing: 13.3) { VStack(alignment: .leading, spacing: 0) { Text("비밀번호") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "eeeeee")) .padding(.leading, 6.7) Text("********") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "777777")) .padding(.top, 12) .padding(.leading, 6.7) Divider() .frame(height: 0.3) .foregroundColor(Color(hex: "909090")) .padding(.top, 8.3) } Button(action: { AppState.shared.setAppStep(step: .modifyPassword) }) { Text("비밀번호 변경") .font(.custom(Font.bold.rawValue, size: 13.3)) .foregroundColor(Color.white) .padding(.vertical, 13.3) .padding(.horizontal, 22.7) .background(Color(hex: "9970ff")) .cornerRadius(8) } } } .padding(.vertical, 20) .padding(.horizontal, 13.3) .frame(width: screenSize().width - 26.7) .background(Color(hex: "222222")) .cornerRadius(6.7) } @ViewBuilder func NicknameAndGenderView() -> some View { VStack(spacing: 16.7) { HStack(alignment: .bottom, spacing: 13.3) { VStack(alignment: .leading, spacing: 0) { Text("닉네임") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "eeeeee")) .padding(.leading, 6.7) Text(viewModel.nickname) .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) .padding(.top, 12) .padding(.leading, 6.7) Divider() .frame(height: 0.3) .foregroundColor(Color(hex: "909090")) .padding(.top, 8.3) } Button(action: { AppState.shared.setAppStep(step: .changeNickname) }) { Text("닉네임 변경") .font(.custom(Font.bold.rawValue, size: 13.3)) .foregroundColor(Color.white) .padding(.vertical, 13.3) .padding(.horizontal, 22.7) .background(Color(hex: "9970ff")) .cornerRadius(8) } } VStack(alignment: .leading, spacing: 13.3) { Text("성별") .font(.custom(Font.bold.rawValue, size: 12)) .foregroundColor(Color(hex: "eeeeee")) .padding(.leading, 6.7) HStack(spacing: 0) { Button(action: { viewModel.gender = .FEMALE }) { HStack(spacing: 13.3) { Image(viewModel.gender == .FEMALE ? "btn_radio_select_selected" : "btn_radio_select_normal") .resizable() .frame(width: 20, height: 20) Text("여자") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) } } Spacer() Button(action: { viewModel.gender = .MALE }) { HStack(spacing: 13.3) { Image(viewModel.gender == .MALE ? "btn_radio_select_selected" : "btn_radio_select_normal") .resizable() .frame(width: 20, height: 20) Text("남자") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) } } Spacer() Button(action: { viewModel.gender = .NONE }) { HStack(spacing: 13.3) { Image(viewModel.gender == .NONE ? "btn_radio_select_selected" : "btn_radio_select_normal") .resizable() .frame(width: 20, height: 20) Text("공개 안 함") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) } } Spacer() } .padding(.horizontal, 6.7) } } .padding(.vertical, 20) .padding(.horizontal, 13.3) .frame(width: screenSize().width - 26.7) .background(Color(hex: "222222")) .cornerRadius(6.7) } @ViewBuilder func InstagramAndYoutubeAccountView() -> some View { VStack(spacing: 16.7) { UserTextField( title: "인스타그램", hint: "인스타그램 URL", isSecure: false, variable: $viewModel.instagramUrl ) UserTextField( title: "유튜브", hint: "유튜브 URL", isSecure: false, variable: $viewModel.youtubeUrl ) UserTextField( title: "웹사이트", hint: "웹사이트 URL", isSecure: false, variable: $viewModel.websiteUrl ) UserTextField( title: "블로그", hint: "블로그 URL", isSecure: false, variable: $viewModel.blogUrl ) } .padding(.vertical, 20) .padding(.horizontal, 13.3) .frame(width: screenSize().width - 26.7) .background(Color(hex: "222222")) .cornerRadius(6.7) } @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 - 53.4) .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) } .padding(.vertical, 20) .padding(.horizontal, 13.3) .frame(width: screenSize().width - 26.7) .background(Color(hex: "222222")) .cornerRadius(6.7) } @ViewBuilder func ContentInputView() -> some View { VStack(alignment: .leading, spacing: 13.3) { Text("소개글") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) TextViewWrapper( text: $viewModel.introduce, placeholder: viewModel.placeholder, textColorHex: "eeeeee", backgroundColorHex: "222222" ) .frame(width: screenSize().width - 26.7, height: 133.3) .padding(.top, 13.3) } .frame(width: screenSize().width - 26.7) } var body: some View { BaseView(isLoading: $viewModel.isLoading) { GeometryReader { proxy in ZStack { VStack(spacing: 0) { DetailNavigationBar(title: "프로필 수정") ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { if let profileResponse = viewModel.profileResponse { ZStack { if profileResponse.profileUrl.trimmingCharacters(in: .whitespaces).count > 0 { KFImage(URL(string: profileResponse.profileUrl)) .resizable() .scaledToFill() .frame(width: 80, height: 80, alignment: .top) .clipShape(Circle()) } else { Image("ic_logo") .resizable() .scaledToFill() .frame(width: 80, height: 80, alignment: .top) .background(Color(hex: "3e3358")) .clipShape(Circle()) } Image("ic_camera") .padding(10) .background(Color(hex: "9970ff")) .cornerRadius(30) .offset(x: 25, y: 25) } .frame(alignment: .bottomTrailing) .padding(.top, 13.3) .onTapGesture { isShowPhotoPicker = true } EmailAndPasswordView() .padding(.top, 26.7) NicknameAndGenderView() .padding(.top, 13.3) if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { InstagramAndYoutubeAccountView() .padding(.top, 13.3) } if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { TagSelectView() .padding(.top, 33.3) } if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { ContentInputView() .padding(.top, 33.3) } if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { 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.7) .padding(.horizontal, 13.3) .frame(width: screenSize().width) .background(Color(hex: "222222")) .cornerRadius(16.7, corners: [.topLeft, .topRight]) .padding(.top, 26.7) .onTapGesture { viewModel.updateProfile() } if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: proxy.size.width, height: 15.3) } } } } } .padding(.top, 13.3) if UserDefaults.string(forKey: .role) != MemberRole.CREATOR.rawValue { Spacer() 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.7) .padding(.horizontal, 13.3) .frame(width: screenSize().width) .background(Color(hex: "222222")) .cornerRadius(16.7, corners: [.topLeft, .topRight]) .padding(.top, 13.3) .onTapGesture { viewModel.updateProfile() } if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: proxy.size.width, height: 15.3) } } } if isShowPhotoPicker { ImagePicker( isShowing: $isShowPhotoPicker, selectedImage: $viewModel.profileImage, sourceType: .photoLibrary ) } if isShowSelectTagView { GeometryReader { proxy in VStack { Spacer() MemberTagView( isShowing: $isShowSelectTagView, selectedTags: $viewModel.tags, onItemClick: { tag, isChecked in if isChecked { viewModel.addTag(tag: tag) } else { viewModel.removeTag(tag: tag) } } ) .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) } } .offset(y: keyboardHandler.keyboardHeight > 0 ? -keyboardHandler.keyboardHeight + 15.3 : 0) .edgesIgnoringSafeArea(.bottom) .onTapGesture { hideKeyboard() } } .onAppear { viewModel.refresh = refresh viewModel.getMyProfile() } .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { GeometryReader { geo in HStack { Spacer() Text(viewModel.errorMessage) .padding(.vertical, 13.3) .padding(.horizontal, 6.7) .frame(width: geo.size.width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) .background(Color(hex: "9970ff")) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .cornerRadius(20) .padding(.top, 66.7) Spacer() } } } } } } struct ProfileUpdateView_Previews: PreviewProvider { static var previews: some View { ProfileUpdateView(refresh: {}) } }