diff --git a/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved index 410e712..48985a2 100644 --- a/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "1.2022062300.0" } }, + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "bc268c28fb170f494de9e9927c371b8342979ece", + "version" : "5.7.1" + } + }, { "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", @@ -63,6 +72,15 @@ "version" : "3.1.1" } }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "c75584ac759cbb16b204d0a7de3ebf53ea6b304d", + "version" : "7.9.0" + } + }, { "identity" : "leveldb", "kind" : "remoteSourceControl", @@ -72,6 +90,15 @@ "version" : "1.22.2" } }, + { + "identity" : "moya", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Moya/Moya.git", + "state" : { + "revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26", + "version" : "15.0.3" + } + }, { "identity" : "nanopb", "kind" : "remoteSourceControl", @@ -81,6 +108,15 @@ "version" : "2.30909.0" } }, + { + "identity" : "popupview", + "kind" : "remoteSourceControl", + "location" : "https://github.com/exyte/PopupView.git", + "state" : { + "revision" : "68349a0ae704b9a7041f756f3f4f460ddbf7ba8d", + "version" : "2.6.0" + } + }, { "identity" : "promises", "kind" : "remoteSourceControl", @@ -90,6 +126,33 @@ "version" : "2.3.1" } }, + { + "identity" : "reactiveswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", + "state" : { + "revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c", + "version" : "6.7.0" + } + }, + { + "identity" : "refreshablescrollview", + "kind" : "remoteSourceControl", + "location" : "https://github.com/phuhuynh2411/RefreshableScrollView", + "state" : { + "revision" : "e06edf5dc4facc7fbf71179e8a94f0d1c7035ce3", + "version" : "1.1.1" + } + }, + { + "identity" : "rxswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveX/RxSwift.git", + "state" : { + "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", + "version" : "6.6.0" + } + }, { "identity" : "swift-protobuf", "kind" : "remoteSourceControl", diff --git a/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/Contents.json new file mode 100644 index 0000000..11e184a --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_select_checked.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png new file mode 100644 index 0000000..ee6d977 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png differ diff --git a/SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/Contents.json new file mode 100644 index 0000000..0db70c0 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_select_normal.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/btn_select_normal.png b/SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/btn_select_normal.png new file mode 100644 index 0000000..b13a12d Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/btn_select_normal.png differ diff --git a/SodaLive/Resources/Assets.xcassets/loading/Contents.json b/SodaLive/Resources/Assets.xcassets/loading/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/loading/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/Contents.json new file mode 100644 index 0000000..16ca8fc --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "loading_1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/loading_1.png b/SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/loading_1.png new file mode 100644 index 0000000..d916f59 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/loading_1.png differ diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/Contents.json new file mode 100644 index 0000000..1340f9a --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "loading_2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/loading_2.png b/SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/loading_2.png new file mode 100644 index 0000000..ee58be3 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/loading_2.png differ diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/Contents.json new file mode 100644 index 0000000..9c5c108 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "loading_3.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/loading_3.png b/SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/loading_3.png new file mode 100644 index 0000000..e1e50ef Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/loading_3.png differ diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/Contents.json new file mode 100644 index 0000000..08349c1 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "loading_4.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/loading_4.png b/SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/loading_4.png new file mode 100644 index 0000000..c4fc517 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/loading_4.png differ diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/Contents.json new file mode 100644 index 0000000..0f1ea75 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "loading_5.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/loading_5.png b/SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/loading_5.png new file mode 100644 index 0000000..e08ba00 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/loading_5.png differ diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index 14aa8b4..75d819e 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -11,4 +11,8 @@ enum AppStep { case splash case main + + case signUp + + case findPassword } diff --git a/SodaLive/Sources/Common/ApiResponse.swift b/SodaLive/Sources/Common/ApiResponse.swift new file mode 100644 index 0000000..de293a6 --- /dev/null +++ b/SodaLive/Sources/Common/ApiResponse.swift @@ -0,0 +1,15 @@ +// +// ApiResponse.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import Foundation + +struct ApiResponse: Decodable { + let success: Bool + let data: T? + let message: String? + let errorProperty: String? +} diff --git a/SodaLive/Sources/Common/LoadingView.swift b/SodaLive/Sources/Common/LoadingView.swift new file mode 100644 index 0000000..7e7d159 --- /dev/null +++ b/SodaLive/Sources/Common/LoadingView.swift @@ -0,0 +1,69 @@ +// +// LoadingView.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import SwiftUI + +struct LoadingView: View { + @State var index = 0 + + @State var timer = Timer.publish(every: 0.35, on: .current, in: .common).autoconnect() + + var body: some View { + ZStack { + Color.primary.opacity(0.2) + .ignoresSafeArea() + + ZStack { + Image("loading_1") + .resizable() + .frame(width: 100, height: 100) + .opacity(index == 0 ? 1.0 : 0.0) + + Image("loading_2") + .resizable() + .frame(width: 100, height: 100) + .opacity(index == 1 ? 1.0 : 0.0) + + Image("loading_3") + .resizable() + .frame(width: 100, height: 100) + .opacity(index == 2 ? 1.0 : 0.0) + + Image("loading_4") + .resizable() + .frame(width: 100, height: 100) + .opacity(index == 3 ? 1.0 : 0.0) + + Image("loading_5") + .resizable() + .frame(width: 100, height: 100) + .opacity(index == 4 ? 1.0 : 0.0) + } + .frame(width: 150, height: 150) + .background(Color.white) + .cornerRadius(14) + .shadow(color: Color.primary.opacity(0.07), radius: 5, x: 5, y: 5) + .shadow(color: Color.primary.opacity(0.07), radius: 5, x: -5, y: -5) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .onReceive(timer) { _ in + DispatchQueue.main.async { + if index == 4 { + index = 0 + } else { + index += 1 + } + } + } + } +} + +struct LoadingView_Previews: PreviewProvider { + static var previews: some View { + LoadingView() + } +} diff --git a/SodaLive/Sources/NavigationBar/HomeNavigationBar.swift b/SodaLive/Sources/NavigationBar/HomeNavigationBar.swift new file mode 100644 index 0000000..8c80782 --- /dev/null +++ b/SodaLive/Sources/NavigationBar/HomeNavigationBar.swift @@ -0,0 +1,42 @@ +// +// HomeNavigationBar.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import SwiftUI + +struct HomeNavigationBar: View { + + let title: String + let content: Content + + init( + title: String, + @ViewBuilder content: () -> Content + ) { + self.title = title + self.content = content() + } + + var body: some View { + HStack { + Text(title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color(hex: "eeeeee")) + + Spacer() + + content + } + .padding(.horizontal, 13.3) + .frame(width: screenSize().width, height: 50, alignment: .center) + } +} + +struct HomeNavigationBar_Previews: PreviewProvider { + static var previews: some View { + HomeNavigationBar(title: "홈") {} + } +} diff --git a/SodaLive/Sources/User/Login/LoginRequest.swift b/SodaLive/Sources/User/Login/LoginRequest.swift new file mode 100644 index 0000000..4394fd6 --- /dev/null +++ b/SodaLive/Sources/User/Login/LoginRequest.swift @@ -0,0 +1,13 @@ +// +// LoginRequest.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import Foundation + +struct LoginRequest: Encodable { + let email: String + let password: String +} diff --git a/SodaLive/Sources/User/Login/LoginResponse.swift b/SodaLive/Sources/User/Login/LoginResponse.swift new file mode 100644 index 0000000..179cfb6 --- /dev/null +++ b/SodaLive/Sources/User/Login/LoginResponse.swift @@ -0,0 +1,16 @@ +// +// LoginResponse.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import Foundation + +struct LoginResponse: Decodable { + let userId: Int + let token: String + let nickname: String + let email: String + let profileImage: String +} diff --git a/SodaLive/Sources/User/Login/LoginView.swift b/SodaLive/Sources/User/Login/LoginView.swift new file mode 100644 index 0000000..211efce --- /dev/null +++ b/SodaLive/Sources/User/Login/LoginView.swift @@ -0,0 +1,84 @@ +// +// LoginView.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import SwiftUI +import PopupView + +struct LoginView: View { + + @ObservedObject var viewModel = LoginViewModel() + + var body: some View { + ZStack { + Color.black.ignoresSafeArea() + + VStack(spacing: 0) { + HomeNavigationBar(title: "로그인") {} + + Spacer() + + UserTextField( + title: "이메일", + hint: "이메일 주소를 입력해 주세요", + isSecure: false, + variable: $viewModel.email, + keyboardType: .emailAddress + ) + .padding(.horizontal, 26.7) + + UserTextField( + title: "비밀번호", + hint: "비밀번호를 입력해 주세요", + isSecure: true, + variable: $viewModel.password, + isPasswordVisibleButton: true + ) + .padding(.top, 33.3) + .padding(.horizontal, 26.7) + + Button(action: { viewModel.login() }) { + Text("로그인") + .font(.custom(Font.bold.rawValue, size: 15)) + .frame(width: screenSize().width - 26.6, height: 46.7) + .foregroundColor(.white) + .background(Color(hex: "9970ff")) + .cornerRadius(6.7) + } + .padding(.top, 40) + + HStack(spacing: 10) { + Text("비밀번호 재설정") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "bbbbbb")) + .onTapGesture { AppState.shared.setAppStep(step: .findPassword) } + + Text("|") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "bbbbbb")) + + Text("회원가입") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "bbbbbb")) + .onTapGesture { AppState.shared.setAppStep(step: .signUp) } + } + .padding(.top, 40) + + Spacer() + } + + if viewModel.isLoading { + LoadingView() + } + } + } +} + +struct LoginView_Previews: PreviewProvider { + static var previews: some View { + LoginView() + } +} diff --git a/SodaLive/Sources/User/Login/LoginViewModel.swift b/SodaLive/Sources/User/Login/LoginViewModel.swift new file mode 100644 index 0000000..e5b5ac8 --- /dev/null +++ b/SodaLive/Sources/User/Login/LoginViewModel.swift @@ -0,0 +1,74 @@ +// +// LoginViewModel.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import Foundation +import Moya +import Combine + +final class LoginViewModel: ObservableObject { + private let repository = UserRepository() + private var subscription = Set() + + @Published var email = "" + @Published var password = "" + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + + func login() { + if email.isEmpty { + self.errorMessage = "이메일을 입력해 주세요." + self.isShowPopup = true + return + } + + if password.isEmpty { + self.errorMessage = "비밀번호를 입력해 주세요." + self.isShowPopup = true + return + } + + isLoading = true + repository.login(request: LoginRequest(email: email, password: password)) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { response in + self.isLoading = false + let responseData = response.data + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if let data = decoded.data, decoded.success { + UserDefaults.set(data.profileImage, forKey: .profileImage) + UserDefaults.set(data.nickname, forKey: .nickname) + UserDefaults.set(data.userId, forKey: .userId) + UserDefaults.set(data.email, forKey: .email) + UserDefaults.set(data.token, forKey: .token) + } else { + if let message = decoded.message { + self.errorMessage = message + } else { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + } + + self.isShowPopup = true + } + } catch { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.isShowPopup = true + } + } + .store(in: &subscription) + } +} diff --git a/SodaLive/Sources/User/LoginView.swift b/SodaLive/Sources/User/LoginView.swift deleted file mode 100644 index 1568291..0000000 --- a/SodaLive/Sources/User/LoginView.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// LoginView.swift -// SodaLive -// -// Created by klaus on 2023/08/09. -// - -import SwiftUI - -struct LoginView: View { - var body: some View { - Text("Login View") - } -} - -struct LoginView_Previews: PreviewProvider { - static var previews: some View { - LoginView() - } -} diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift new file mode 100644 index 0000000..a55cfa0 --- /dev/null +++ b/SodaLive/Sources/User/UserApi.swift @@ -0,0 +1,57 @@ +// +// UserApi.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import Foundation +import Moya + +enum UserApi { + case login(request: LoginRequest) + case signUp(parameters: [MultipartFormData]) +} + +extension UserApi: TargetType { + var baseURL: URL { + return URL(string: BASE_URL)! + } + + var path: String { + switch self { + case .login: + return "/member/login" + + case .signUp: + return "/member/signup" + } + } + + var method: Moya.Method { + switch self { + case .login, .signUp: + return .post + } + } + + var task: Task { + switch self { + case .login(let request): + return .requestJSONEncodable(request) + + case .signUp(let parameters): + return .uploadMultipart(parameters) + } + } + + var headers: [String : String]? { + switch self { + case .login, .signUp: + return nil + + default: + return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"] + } + } +} diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift new file mode 100644 index 0000000..fe73663 --- /dev/null +++ b/SodaLive/Sources/User/UserRepository.swift @@ -0,0 +1,23 @@ +// +// UserRepository.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +final class UserRepository { + private let api = MoyaProvider() + + func login(request: LoginRequest) -> AnyPublisher { + return api.requestPublisher(.login(request: request)) + } + + func signUp(parameters: [MultipartFormData]) -> AnyPublisher { + return api.requestPublisher(.signUp(parameters: parameters)) + } +} diff --git a/SodaLive/Sources/User/UserTextField.swift b/SodaLive/Sources/User/UserTextField.swift new file mode 100644 index 0000000..3659d34 --- /dev/null +++ b/SodaLive/Sources/User/UserTextField.swift @@ -0,0 +1,84 @@ +// +// UserTextField.swift +// SodaLive +// +// Created by klaus on 2023/08/09. +// + +import SwiftUI + +struct UserTextField: View { + + let title: String + let hint: String + let isSecure: Bool + @Binding var variable: String + + var isPasswordVisibleButton: Bool = false + var keyboardType: UIKeyboardType = .default + + @State private var visiblePassword = false + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(title) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color(hex: "eeeeee")) + .padding(.leading, 6.7) + + if isSecure && !visiblePassword{ + SecureField(hint, text: $variable) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .padding(.top, 12) + .padding(.leading, 6.7) + } else { + TextField(hint, text: $variable) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .keyboardType(keyboardType) + .padding(.top, 12) + .padding(.leading, 6.7) + } + + Divider() + .frame(height: 0.3) + .foregroundColor(Color(hex: "909090")) + .padding(.top, 8.3) + + if isSecure && isPasswordVisibleButton { + Button(action: { visiblePassword.toggle() }) { + HStack(spacing: 13.3) { + if visiblePassword { + Image("btn_select_checked") + } else { + Image("btn_select_normal") + } + + Text("비밀번호 표시") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + } + } + .padding(.top, 20) + .padding(.leading, 6.7) + } + } + } +} + +struct UserTextField_Previews: PreviewProvider { + static var previews: some View { + UserTextField( + title: "이메일", + hint: "user_id@email.com", + isSecure: true, + variable: .constant("test"), + isPasswordVisibleButton: true + ) + } +}