로그인 페이지
| @@ -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", | ||||
|   | ||||
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/btn_select_checked.imageset/btn_select_checked.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 544 B | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/btn_select_normal.imageset/btn_select_normal.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 680 B | 
							
								
								
									
										6
									
								
								SodaLive/Resources/Assets.xcassets/loading/Contents.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| { | ||||
|   "info" : { | ||||
|     "author" : "xcode", | ||||
|     "version" : 1 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_1.imageset/loading_1.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.3 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_2.imageset/loading_2.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_3.imageset/loading_3.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.2 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_4.imageset/loading_4.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.1 KiB | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/loading/loading_5.imageset/loading_5.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.6 KiB | 
| @@ -11,4 +11,8 @@ enum AppStep { | ||||
|     case splash | ||||
|      | ||||
|     case main | ||||
|      | ||||
|     case signUp | ||||
|      | ||||
|     case findPassword | ||||
| } | ||||
|   | ||||
							
								
								
									
										15
									
								
								SodaLive/Sources/Common/ApiResponse.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| // | ||||
| //  ApiResponse.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import Foundation | ||||
|  | ||||
| struct ApiResponse<T: Decodable>: Decodable { | ||||
|     let success: Bool | ||||
|     let data: T? | ||||
|     let message: String? | ||||
|     let errorProperty: String? | ||||
| } | ||||
							
								
								
									
										69
									
								
								SodaLive/Sources/Common/LoadingView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										42
									
								
								SodaLive/Sources/NavigationBar/HomeNavigationBar.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | ||||
| // | ||||
| //  HomeNavigationBar.swift | ||||
| //  SodaLive | ||||
| // | ||||
| //  Created by klaus on 2023/08/09. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
|  | ||||
| struct HomeNavigationBar<Content: View>: 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: "홈") {} | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								SodaLive/Sources/User/Login/LoginRequest.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
| } | ||||
							
								
								
									
										16
									
								
								SodaLive/Sources/User/Login/LoginResponse.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
| } | ||||
							
								
								
									
										84
									
								
								SodaLive/Sources/User/Login/LoginView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										74
									
								
								SodaLive/Sources/User/Login/LoginViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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<AnyCancellable>() | ||||
|      | ||||
|     @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<LoginResponse>.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) | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										57
									
								
								SodaLive/Sources/User/UserApi.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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))"] | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								SodaLive/Sources/User/UserRepository.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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<UserApi>() | ||||
|      | ||||
|     func login(request: LoginRequest) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.login(request: request)) | ||||
|     } | ||||
|      | ||||
|     func signUp(parameters: [MultipartFormData]) -> AnyPublisher<Response, MoyaError> { | ||||
|         return api.requestPublisher(.signUp(parameters: parameters)) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										84
									
								
								SodaLive/Sources/User/UserTextField.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -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 | ||||
|         ) | ||||
|     } | ||||
| } | ||||
 Yu Sung
					Yu Sung