콘텐츠 메인
- 채널 검색 추가
This commit is contained in:
		| @@ -139,4 +139,6 @@ enum AppStep { | ||||
|     case auditionDetail(auditionId: Int) | ||||
|      | ||||
|     case auditionRoleDetail(roleId: Int) | ||||
|      | ||||
|     case searchChannel | ||||
| } | ||||
|   | ||||
| @@ -37,6 +37,33 @@ struct ContentMainView: View { | ||||
|                             .padding(.bottom, 26.7) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|                          | ||||
|                         HStack(spacing: 0) { | ||||
|                             Image("ic_title_search_black") | ||||
|                              | ||||
|                             Text("채널명을 입력해 보세요") | ||||
|                                 .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                                 .foregroundColor(Color.gray55) | ||||
|                                 .keyboardType(.default) | ||||
|                                 .padding(.horizontal, 13.3) | ||||
|                              | ||||
|                             Spacer() | ||||
|                         } | ||||
|                         .padding(.horizontal, 21.3) | ||||
|                         .frame(height: 50) | ||||
|                         .frame(maxWidth: .infinity) | ||||
|                         .background(Color.gray22) | ||||
|                         .overlay( | ||||
|                             RoundedRectangle(cornerRadius: 6.7) | ||||
|                                 .strokeBorder(lineWidth: 1) | ||||
|                                 .foregroundColor(Color.graybb) | ||||
|                         ) | ||||
|                         .padding(.bottom, 26.7) | ||||
|                         .padding(.horizontal, 13.3) | ||||
|                         .onTapGesture { | ||||
|                             UserDefaults.set("", forKey: .searchChannel) | ||||
|                             AppState.shared.setAppStep(step: .searchChannel) | ||||
|                         } | ||||
|                          | ||||
|                         ContentMainCreatorRankingView() | ||||
|                             .padding(.bottom, 26.7) | ||||
|                             .padding(.horizontal, 13.3) | ||||
|   | ||||
| @@ -209,6 +209,9 @@ struct ContentView: View { | ||||
|             case .auditionRoleDetail(let roleId): | ||||
|                 AuditionRoleDetailView(roleId: roleId) | ||||
|                  | ||||
|             case .searchChannel: | ||||
|                 SearchChannelView() | ||||
|                  | ||||
|             default: | ||||
|                 EmptyView() | ||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||
|   | ||||
							
								
								
									
										56
									
								
								SodaLive/Sources/CustomView/FocusedTextField.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								SodaLive/Sources/CustomView/FocusedTextField.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| // | ||||
| //  FocusedTextField.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 1/9/25. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct FocusedTextField: UIViewRepresentable { | ||||
|     @Binding var text: String | ||||
|     var isFirstResponder: Bool | ||||
|      | ||||
|     class Coordinator: NSObject, UITextFieldDelegate { | ||||
|         var parent: FocusedTextField | ||||
|          | ||||
|         init(_ parent: FocusedTextField) { | ||||
|             self.parent = parent | ||||
|         } | ||||
|          | ||||
|         func textFieldDidChangeSelection(_ textField: UITextField) { | ||||
|             parent.text = textField.text ?? "" | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func makeCoordinator() -> Coordinator { | ||||
|         Coordinator(self) | ||||
|     } | ||||
|      | ||||
|     func makeUIView(context: Context) -> UITextField { | ||||
|         let textField = UITextField() | ||||
|         textField.delegate = context.coordinator | ||||
|         textField.font = UIFont(name: Font.medium.rawValue, size: 13.3) | ||||
|         textField.textColor = UIColor(hex: "eeeeee", alpha: 1.0) | ||||
|         textField.tintColor = UIColor(hex: "3bb9f1", alpha: 1.0) | ||||
|         textField.keyboardType = .default | ||||
|         textField.autocapitalizationType = .none | ||||
|         textField.autocorrectionType = .no | ||||
|          | ||||
|         let placeholder = "채널명을 입력해 보세요" | ||||
|         textField.placeholder = placeholder | ||||
|         textField.attributedPlaceholder = NSAttributedString( | ||||
|             string: placeholder, | ||||
|             attributes: [.foregroundColor: UIColor(hex: "555555", alpha: 1.0)] | ||||
|         ) | ||||
|          | ||||
|         return textField | ||||
|     } | ||||
|      | ||||
|     func updateUIView(_ uiView: UITextField, context: Context) { | ||||
|         uiView.text = text | ||||
|         if isFirstResponder { | ||||
|             uiView.becomeFirstResponder() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -29,6 +29,7 @@ final class ExplorerViewModel: ObservableObject { | ||||
|             .sink { [unowned self] value in | ||||
|                 DEBUG_LOG("value: \(value)") | ||||
|                 if value.count > 1 { | ||||
|                     UserDefaults.set(value, forKey: .searchChannel) | ||||
|                     searchChannel() | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ enum UserDefaultsKey: String, CaseIterable { | ||||
|     case isAdultContentVisible | ||||
|     case contentPreference | ||||
|     case isAuditionNotification | ||||
|     case searchChannel | ||||
| } | ||||
|  | ||||
| extension UserDefaults { | ||||
|   | ||||
							
								
								
									
										130
									
								
								SodaLive/Sources/SearchChannel/SearchChannelView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								SodaLive/Sources/SearchChannel/SearchChannelView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| // | ||||
| //  SearchChannelView.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 1/9/25. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import Kingfisher | ||||
|  | ||||
| struct SearchChannelView: View { | ||||
|      | ||||
|     @StateObject var viewModel = ExplorerViewModel() | ||||
|     @State private var isFocused: Bool = false | ||||
|      | ||||
|     var body: some View { | ||||
|         BaseView(isLoading: $viewModel.isLoading) { | ||||
|             VStack(spacing: 0) { | ||||
|                 HStack(spacing: 0) { | ||||
|                     Image("ic_back") | ||||
|                         .resizable() | ||||
|                         .frame(width: 20, height: 20) | ||||
|                         .padding(13.3) | ||||
|                         .onTapGesture { | ||||
|                             AppState.shared.back() | ||||
|                         } | ||||
|                      | ||||
|                     HStack(spacing: 0) { | ||||
|                         Image("ic_title_search_black") | ||||
|                          | ||||
|                         FocusedTextField( | ||||
|                             text: $viewModel.channel, | ||||
|                             isFirstResponder: isFocused | ||||
|                         ) | ||||
|                         .padding(.horizontal, 13.3) | ||||
|                         .onAppear { | ||||
|                             DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { | ||||
|                                 isFocused = true | ||||
|                             } | ||||
|                         } | ||||
|                          | ||||
|                         if !viewModel.channel.isEmpty { | ||||
|                             Image("ic_close_white") | ||||
|                                 .resizable() | ||||
|                                 .frame(width: 20, height: 20) | ||||
|                                 .onTapGesture { | ||||
|                                     viewModel.channel = "" | ||||
|                                 } | ||||
|                         } | ||||
|                     } | ||||
|                     .padding(.horizontal, 21.3) | ||||
|                     .frame(height: 50) | ||||
|                     .frame(maxWidth: .infinity) | ||||
|                     .background(Color.gray22) | ||||
|                     .overlay( | ||||
|                         RoundedRectangle(cornerRadius: 6.7) | ||||
|                             .strokeBorder(lineWidth: 1) | ||||
|                             .foregroundColor(Color.graybb) | ||||
|                     ) | ||||
|                 } | ||||
|                 .padding(.horizontal, 13.3) | ||||
|                 .frame(height: 50) | ||||
|                 .background(Color.black) | ||||
|                 .padding(.top, 13.3) | ||||
|                  | ||||
|                 ScrollView(.vertical, showsIndicators: false) { | ||||
|                     if viewModel.channelResponses.count > 0 { | ||||
|                         VStack(spacing: 26.7) { | ||||
|                             ForEach(viewModel.channelResponses, id: \.self) { channel in | ||||
|                                 HStack(spacing: 13.3) { | ||||
|                                     KFImage(URL(string: channel.profileImageUrl)) | ||||
|                                         .cancelOnDisappear(true) | ||||
|                                         .downsampling(size: CGSize(width: 46.7, height: 46.7)) | ||||
|                                         .resizable() | ||||
|                                         .frame(width: 46.7, height: 46.7) | ||||
|                                         .clipShape(Circle()) | ||||
|                                      | ||||
|                                     Text(channel.nickname) | ||||
|                                         .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||
|                                         .foregroundColor(Color.grayee) | ||||
|                                     Spacer() | ||||
|                                 } | ||||
|                                 .frame(maxWidth: .infinity) | ||||
|                                 .contentShape(Rectangle()) | ||||
|                                 .onTapGesture { | ||||
|                                     AppState.shared.setAppStep(step: .creatorDetail(userId: channel.id)) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|                         Text("검색 결과가 없습니다.") | ||||
|                             .font(.custom(Font.medium.rawValue, size: 18.3)) | ||||
|                             .foregroundColor(.white) | ||||
|                             .padding(.top, 40) | ||||
|                     } | ||||
|                 } | ||||
|                 .padding(.vertical, 40) | ||||
|                 .padding(.horizontal, 26.7) | ||||
|             } | ||||
|             .padding(.horizontal, 13.3) | ||||
|             .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.button) | ||||
|                             .foregroundColor(Color.white) | ||||
|                             .multilineTextAlignment(.center) | ||||
|                             .cornerRadius(20) | ||||
|                             .padding(.top, 66.7) | ||||
|                         Spacer() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             .onAppear { | ||||
|                 viewModel.channel = UserDefaults.string(forKey: .searchChannel) | ||||
|             } | ||||
|             .onTapGesture { | ||||
|                 hideKeyboard() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     SearchChannelView() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung