diff --git a/Poppool/Poppool.xcodeproj/project.pbxproj b/Poppool/Poppool.xcodeproj/project.pbxproj index 08aec80b..10238e46 100644 --- a/Poppool/Poppool.xcodeproj/project.pbxproj +++ b/Poppool/Poppool.xcodeproj/project.pbxproj @@ -90,6 +90,12 @@ 08B191AE2CF5BFA60057BC04 /* AgeSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191AD2CF5BFA60057BC04 /* AgeSelectedView.swift */; }; 08B191B02CF5BFAE0057BC04 /* AgeSelectedReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191AF2CF5BFAE0057BC04 /* AgeSelectedReactor.swift */; }; 08B191B22CF5C0A60057BC04 /* PPPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B12CF5C0A60057BC04 /* PPPicker.swift */; }; + 08B191B42CF609260057BC04 /* KakaoLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B32CF609260057BC04 /* KakaoLoginService.swift */; }; + 08B191B62CF6092B0057BC04 /* AppleLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B52CF6092B0057BC04 /* AppleLoginService.swift */; }; + 08B191B82CF6092F0057BC04 /* AuthServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B191B72CF6092F0057BC04 /* AuthServiceable.swift */; }; + 08B191BA2CF609AE0057BC04 /* RxKakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 08B191B92CF609AE0057BC04 /* RxKakaoSDK */; }; + 08B191BC2CF609AE0057BC04 /* RxKakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 08B191BB2CF609AE0057BC04 /* RxKakaoSDKAuth */; }; + 08B191BE2CF609AE0057BC04 /* RxKakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 08B191BD2CF609AE0057BC04 /* RxKakaoSDKUser */; }; BDCA41C12CF35AC0005EECF6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41C02CF35AC0005EECF6 /* AppDelegate.swift */; }; BDCA41C32CF35AC0005EECF6 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41C22CF35AC0005EECF6 /* SceneDelegate.swift */; }; BDCA41C52CF35AC0005EECF6 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCA41C42CF35AC0005EECF6 /* TestViewController.swift */; }; @@ -210,6 +216,9 @@ 08B191AD2CF5BFA60057BC04 /* AgeSelectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeSelectedView.swift; sourceTree = ""; }; 08B191AF2CF5BFAE0057BC04 /* AgeSelectedReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeSelectedReactor.swift; sourceTree = ""; }; 08B191B12CF5C0A60057BC04 /* PPPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PPPicker.swift; sourceTree = ""; }; + 08B191B32CF609260057BC04 /* KakaoLoginService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KakaoLoginService.swift; sourceTree = ""; }; + 08B191B52CF6092B0057BC04 /* AppleLoginService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleLoginService.swift; sourceTree = ""; }; + 08B191B72CF6092F0057BC04 /* AuthServiceable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthServiceable.swift; sourceTree = ""; }; BDCA41BD2CF35AC0005EECF6 /* Poppool.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Poppool.app; sourceTree = BUILT_PRODUCTS_DIR; }; BDCA41C02CF35AC0005EECF6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; BDCA41C22CF35AC0005EECF6 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -234,11 +243,14 @@ BDCA41F52CF35D33005EECF6 /* Kingfisher in Frameworks */, BDCA42072CF35FA6005EECF6 /* Tabman in Frameworks */, BDCA42042CF35F76005EECF6 /* PanModal in Frameworks */, + 08B191BA2CF609AE0057BC04 /* RxKakaoSDK in Frameworks */, + 08B191BC2CF609AE0057BC04 /* RxKakaoSDKAuth in Frameworks */, 083A25D02CF364B70099B58E /* Alamofire in Frameworks */, BDCA42102CF35FF5005EECF6 /* Lottie in Frameworks */, BDCA41FE2CF35EE7005EECF6 /* ReactorKit in Frameworks */, BDCA41F22CF35D0D005EECF6 /* SnapKit in Frameworks */, BDCA420A2CF35FB1005EECF6 /* Pageboy in Frameworks */, + 08B191BE2CF609AE0057BC04 /* RxKakaoSDKUser in Frameworks */, BDCA42012CF35EFE005EECF6 /* RxKeyboard in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -367,6 +379,9 @@ 083A25A02CF3623C0099B58E /* Infrastructure */ = { isa = PBXGroup; children = ( + 08B191B72CF6092F0057BC04 /* AuthServiceable.swift */, + 08B191B52CF6092B0057BC04 /* AppleLoginService.swift */, + 08B191B32CF609260057BC04 /* KakaoLoginService.swift */, 083A25BE2CF362770099B58E /* Logger */, ); path = Infrastructure; @@ -724,6 +739,9 @@ BDCA420C2CF35FD2005EECF6 /* RxGesture */, BDCA420F2CF35FF5005EECF6 /* Lottie */, 083A25CF2CF364B70099B58E /* Alamofire */, + 08B191B92CF609AE0057BC04 /* RxKakaoSDK */, + 08B191BB2CF609AE0057BC04 /* RxKakaoSDKAuth */, + 08B191BD2CF609AE0057BC04 /* RxKakaoSDKUser */, ); productName = Poppool; productReference = BDCA41BD2CF35AC0005EECF6 /* Poppool.app */; @@ -868,6 +886,7 @@ 08B1913D2CF367EB0057BC04 /* Constants.swift in Sources */, 083A25B72CF362670099B58E /* RequestEndpoint.swift in Sources */, 083A25B82CF362670099B58E /* IndicatorMaker.swift in Sources */, + 08B191B62CF6092B0057BC04 /* AppleLoginService.swift in Sources */, 083A25B22CF362670099B58E /* NetworkError.swift in Sources */, 083A25832CF361EF0099B58E /* BaseViewController.swift in Sources */, 083A25922CF361F90099B58E /* ViewConvention.swift in Sources */, @@ -883,6 +902,7 @@ 08B1919C2CF4A77C0057BC04 /* TagSection.swift in Sources */, 083A25902CF361F90099B58E /* TestDynamicCell.swift in Sources */, 08B1917A2CF452B30057BC04 /* SignUpTermsView.swift in Sources */, + 08B191B82CF6092F0057BC04 /* AuthServiceable.swift in Sources */, BDCA41C52CF35AC0005EECF6 /* TestViewController.swift in Sources */, 083A258C2CF361F90099B58E /* ControllerConvention.swift in Sources */, 08B191982CF4A1010057BC04 /* SignUpStep4Reactor.swift in Sources */, @@ -894,6 +914,7 @@ 083A25B62CF362670099B58E /* MultipartEndPoint.swift in Sources */, 08B191942CF4A0F00057BC04 /* SignUpStep4View.swift in Sources */, 08B1918D2CF49FF70057BC04 /* SignUpStep3View.swift in Sources */, + 08B191B42CF609260057BC04 /* KakaoLoginService.swift in Sources */, BDCA41C12CF35AC0005EECF6 /* AppDelegate.swift in Sources */, 08B191A42CF5A7030057BC04 /* PPSegmentedControl.swift in Sources */, 08B191AE2CF5BFA60057BC04 /* AgeSelectedView.swift in Sources */, @@ -1390,6 +1411,21 @@ package = 083A25CE2CF364B70099B58E /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + 08B191B92CF609AE0057BC04 /* RxKakaoSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */; + productName = RxKakaoSDK; + }; + 08B191BB2CF609AE0057BC04 /* RxKakaoSDKAuth */ = { + isa = XCSwiftPackageProductDependency; + package = BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */; + productName = RxKakaoSDKAuth; + }; + 08B191BD2CF609AE0057BC04 /* RxKakaoSDKUser */ = { + isa = XCSwiftPackageProductDependency; + package = BDCA41F92CF35EC0005EECF6 /* XCRemoteSwiftPackageReference "kakao-ios-sdk-rx" */; + productName = RxKakaoSDKUser; + }; BDCA41F12CF35D0D005EECF6 /* SnapKit */ = { isa = XCSwiftPackageProductDependency; package = BDCA41F02CF35D0D005EECF6 /* XCRemoteSwiftPackageReference "SnapKit" */; diff --git a/Poppool/Poppool/Application/AppDelegate.swift b/Poppool/Poppool/Application/AppDelegate.swift index 3df2aaf9..b4a39b57 100644 --- a/Poppool/Poppool/Application/AppDelegate.swift +++ b/Poppool/Poppool/Application/AppDelegate.swift @@ -7,11 +7,15 @@ import UIKit +import RxKakaoSDKAuth +import KakaoSDKAuth +import RxKakaoSDKCommon + @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + RxKakaoSDK.initSDK(appKey: "", loggingEnable: false) return true } diff --git a/Poppool/Poppool/Application/SceneDelegate.swift b/Poppool/Poppool/Application/SceneDelegate.swift index bfdef5a0..0a88e219 100644 --- a/Poppool/Poppool/Application/SceneDelegate.swift +++ b/Poppool/Poppool/Application/SceneDelegate.swift @@ -7,6 +7,10 @@ import UIKit +import RxKakaoSDKAuth +import KakaoSDKAuth +import RxSwift + class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? @@ -17,8 +21,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) - let rootViewController = SignUpMainController() - rootViewController.reactor = SignUpMainReactor() + let rootViewController = LoginController() + rootViewController.reactor = LoginReactor() let navigationController = UINavigationController(rootViewController: rootViewController) window?.rootViewController = navigationController @@ -39,5 +43,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneDidEnterBackground(_ scene: UIScene) { } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let url = URLContexts.first?.url { + if AuthApi.isKakaoTalkLoginUrl(url) { + _ = AuthController.rx.handleOpenUrl(url: url) + } + } + } } diff --git a/Poppool/Poppool/Infrastructure/AppleLoginService.swift b/Poppool/Poppool/Infrastructure/AppleLoginService.swift new file mode 100644 index 00000000..759c1192 --- /dev/null +++ b/Poppool/Poppool/Infrastructure/AppleLoginService.swift @@ -0,0 +1,124 @@ +// +// AppleLoginService.swift +// MomsVillage +// +// Created by SeoJunYoung on 8/20/24. +// + +import RxSwift +import AuthenticationServices + +final class AppleLoginService: NSObject, AuthServiceable { + + // 사용자 자격 증명 정보를 방출할 subject + private var authServiceResponse: PublishSubject = .init() + + override init() { + super.init() + Logger.log( + message: "\(self) init", + category: .info, + fileName: #file, + line: #line + ) + } + + deinit { + Logger.log( + message: "\(self) deinit", + category: .info, + fileName: #file, + line: #line + ) + } + + func fetchUserCredential() -> Observable { + performRequest() + return authServiceResponse + } + + // Apple 인증 요청을 수행하는 함수 + private func performRequest() { + let appleIDProvider = ASAuthorizationAppleIDProvider() + let request = appleIDProvider.createRequest() + request.requestedScopes = [.fullName, .email] + + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.presentationContextProvider = self + authorizationController.performRequests() + } +} + +extension AppleLoginService: ASAuthorizationControllerPresentationContextProviding, + ASAuthorizationControllerDelegate { + // 인증 컨트롤러의 프레젠테이션 앵커를 반환하는 함수 + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + let scenes = UIApplication.shared.connectedScenes + let windowSecne = scenes.first as? UIWindowScene + guard let window = windowSecne?.windows.first else { + Logger.log( + message: "\(#function) UIWindow fetch Fail", + category: .error, + fileName: #file, + line: #line + ) + return UIWindow() + } + return window + } + + // 인증 성공 시 호출되는 함수 + func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + switch authorization.credential { + case let appleIDCredential as ASAuthorizationAppleIDCredential: + guard let idToken = appleIDCredential.identityToken else { + // 토큰이 없는 경우 오류 방출 + Logger.log( + message: "AppleLogin Token is Not Found", + category: .error, + fileName: #file, + line: #line + ) + authServiceResponse.onError(AuthError.unknownError) + return + } + guard let idToken = String(data: idToken, encoding: .utf8) else { + Logger.log( + message: "AppleLogin Token Convert Fail", + category: .error, + fileName: #file, + line: #line + ) + authServiceResponse.onError(AuthError.unknownError) + return + } + guard let authorizationCode = appleIDCredential.authorizationCode else { + return + } + + guard let convertAuthorizationCode = String(data: authorizationCode, encoding: .utf8) else { + return + } + authServiceResponse.onNext(.init(idToken: idToken, authorizationCode: convertAuthorizationCode)) + default: + break + } + } + // 인증 실패 시 호출되는 함수 + func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: Error + ) { + Logger.log( + message: "AppleLogin Fail", + category: .error, + fileName: #file, + line: #line + ) + authServiceResponse.onError(error) + } +} diff --git a/Poppool/Poppool/Infrastructure/AuthServiceable.swift b/Poppool/Poppool/Infrastructure/AuthServiceable.swift new file mode 100644 index 00000000..b48cfb6b --- /dev/null +++ b/Poppool/Poppool/Infrastructure/AuthServiceable.swift @@ -0,0 +1,28 @@ +// +// AuthService.swift +// MomsVillage +// +// Created by SeoJunYoung on 8/22/24. +// + +import UIKit + +import RxSwift + +protocol AuthServiceable: AnyObject { + /// 사용자 자격 증명을 가져오는 함수 + /// - Returns: Response 형태의 사용자 자격 증명 + func fetchUserCredential() -> Observable +} + +struct AuthServiceResponse { + var idToken: String? + var authorizationCode: String? + var kakaoUserId: Int64? + var kakaoAccessToken: String? +} + +enum AuthError: Error { + case notInstalled + case unknownError +} diff --git a/Poppool/Poppool/Infrastructure/KakaoLoginService.swift b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift new file mode 100644 index 00000000..8f710ea6 --- /dev/null +++ b/Poppool/Poppool/Infrastructure/KakaoLoginService.swift @@ -0,0 +1,149 @@ +// +// KakaoLoginService.swift +// MomsVillage +// +// Created by SeoJunYoung on 8/13/24. +// + +import RxSwift +import KakaoSDKUser +import RxKakaoSDKUser +import KakaoSDKAuth + +final class KakaoLoginService: AuthServiceable { + + struct Credential: Encodable { + var id: String + var token: String + } + + var disposeBag = DisposeBag() + + init() { + Logger.log( + message: "\(self) init", + category: .info, + fileName: #file, + line: #line + ) + } + + deinit { + Logger.log( + message: "\(self) deinit", + category: .info, + fileName: #file, + line: #line + ) + } + + func unlink() -> Observable { + return Observable.create { observer in + UserApi.shared.unlink { error in + if let error = error { + observer.onNext(()) + Logger.log(message: error.localizedDescription, category: .error) + } else { + observer.onNext(()) + observer.onCompleted() + } + } + return Disposables.create() + } + } + + func fetchUserCredential() -> Observable { + return Observable.create { [weak self] observer in + guard let self = self else { + Logger.log( + message: "KakaoTalk login Error", + category: .error, + fileName: #file, + line: #line + ) + return Disposables.create() + } + // 카카오톡 설치 유무 + guard UserApi.isKakaoTalkLoginAvailable() else { + Logger.log( + message: "KakaoTalk is not install", + category: .error, + fileName: #file, + line: #line + ) + UserApi.shared.loginWithKakaoAccount { [weak self] (oauthToken, error) in + if let error = error { + observer.onError(error) + } else { + if let self = self, let accessToken = oauthToken?.accessToken { + self.fetchUserId(observer: observer, accessToken: accessToken) + } + } + } + return Disposables.create() + } + // token을 획득하기 위한 로그인 + loginWithKakaoTalk() + .withUnretained(self) + .subscribe { (owner, loginResponse) in + owner.fetchUserId(observer: observer, accessToken: loginResponse.accessToken) + } onError: { _ in + observer.onError(AuthError.unknownError) + } + .disposed(by: disposeBag) + + return Disposables.create() + } + } +} + +private extension KakaoLoginService { + + func fetchUserId(observer: AnyObserver, accessToken: String) { + UserApi.shared.rx.me() + .subscribe(onSuccess: { user in + observer.onNext(.init(kakaoUserId: user.id,kakaoAccessToken: accessToken)) + }, onFailure: { _ in + observer.onError(AuthError.unknownError) + }) + .disposed(by: self.disposeBag) + } + + func loginWithKakaoTalk() -> Observable { + return UserApi.shared.rx.loginWithKakaoTalk() + .do { token in + Logger.log( + message: "KakaoTalk Login Response - \(token)", + category: .info, + fileName: #file, + line: #line + ) + } onError: { _ in + Logger.log( + message: "KakaoTalk Login Fail", + category: .error, + fileName: #file, + line: #line + ) + } + } + + func fetchUserProfile() -> Single { + return UserApi.shared.rx.me() + .do { user in + Logger.log( + message: "KakaoTalk Profile Response - \(user)", + category: .info, + fileName: #file, + line: #line + ) + } onError: { _ in + Logger.log( + message: "KakaoTalk Profile Fetch Fail", + category: .error, + fileName: #file, + line: #line + ) + } + } +} diff --git a/Poppool/Poppool/Presentation/Components/PPButton.swift b/Poppool/Poppool/Presentation/Components/PPButton.swift index e37b1b91..87063272 100644 --- a/Poppool/Poppool/Presentation/Components/PPButton.swift +++ b/Poppool/Poppool/Presentation/Components/PPButton.swift @@ -13,6 +13,8 @@ class PPButton: UIButton { case primary case secondary case tertiary + case kakao + case apple var backgroundColor: UIColor { switch self { @@ -22,6 +24,10 @@ class PPButton: UIButton { return .g50 case .tertiary: return .blu500 + case .kakao: + return .init(hexCode: "#F8E049") + case .apple: + return .g900 } } @@ -33,6 +39,10 @@ class PPButton: UIButton { return .blu500 case .tertiary: return .blu500 + case .kakao: + return .g1000 + case .apple: + return .w100 } } @@ -42,7 +52,7 @@ class PPButton: UIButton { return .g100 case .secondary: return .g50 - case .tertiary: + case .tertiary, .apple, .kakao: return .blu500 } } @@ -53,7 +63,7 @@ class PPButton: UIButton { return .g400 case .secondary: return .g50 - case .tertiary: + case .tertiary, .apple, .kakao: return .blu500 } } diff --git a/Poppool/Poppool/Presentation/Secne/Login/LoginController.swift b/Poppool/Poppool/Presentation/Secne/Login/LoginController.swift index 4b16463e..3507f43e 100644 --- a/Poppool/Poppool/Presentation/Secne/Login/LoginController.swift +++ b/Poppool/Poppool/Presentation/Secne/Login/LoginController.swift @@ -43,5 +43,20 @@ private extension LoginController { // MARK: - Methods extension LoginController { func bind(reactor: Reactor) { + mainView.kakaoButton.rx.tap + .withUnretained(self) + .map { (owner, _) in + Reactor.Action.kakaoButtonTapped(controller: owner) + } + .bind(to: reactor.action) + .disposed(by: disposeBag) + + mainView.appleButton.rx.tap + .withUnretained(self) + .map { (owner, _) in + Reactor.Action.appleButtonTapped(controller: owner) + } + .bind(to: reactor.action) + .disposed(by: disposeBag) } } diff --git a/Poppool/Poppool/Presentation/Secne/Login/LoginReactor.swift b/Poppool/Poppool/Presentation/Secne/Login/LoginReactor.swift index feea1734..d63db796 100644 --- a/Poppool/Poppool/Presentation/Secne/Login/LoginReactor.swift +++ b/Poppool/Poppool/Presentation/Secne/Login/LoginReactor.swift @@ -13,9 +13,13 @@ final class LoginReactor: Reactor { // MARK: - Reactor enum Action { + case kakaoButtonTapped(controller: BaseViewController) + case appleButtonTapped(controller: BaseViewController) } enum Mutation { + case moveToSignUpScene(controller: BaseViewController) + case loadView } struct State { @@ -26,6 +30,9 @@ final class LoginReactor: Reactor { var initialState: State var disposeBag = DisposeBag() + private let kakaoLoginService = KakaoLoginService() + private let appleLoginService = AppleLoginService() + // MARK: - init init() { self.initialState = State() @@ -34,13 +41,39 @@ final class LoginReactor: Reactor { // MARK: - Reactor Methods func mutate(action: Action) -> Observable { switch action { + case .kakaoButtonTapped(let controller): + return loginWithKakao() + case .appleButtonTapped(let controller): + return loginWithApple() } } func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { + case .moveToSignUpScene(let controller): + let signUpController = SignUpMainController() + signUpController.reactor = SignUpMainReactor() + controller.navigationController?.pushViewController(signUpController, animated: true) + case .loadView: + print(#function) } return newState } + + func loginWithKakao() -> Observable { + kakaoLoginService.fetchUserCredential() + .map { authResponse in + print(authResponse) + return .loadView + } + } + + func loginWithApple() -> Observable { + appleLoginService.fetchUserCredential() + .map { authResponse in + print(authResponse) + return .loadView + } + } } diff --git a/Poppool/Poppool/Presentation/Secne/Login/LoginView.swift b/Poppool/Poppool/Presentation/Secne/Login/LoginView.swift index 43666152..ed3830da 100644 --- a/Poppool/Poppool/Presentation/Secne/Login/LoginView.swift +++ b/Poppool/Poppool/Presentation/Secne/Login/LoginView.swift @@ -12,12 +12,62 @@ import SnapKit final class LoginView: UIView { // MARK: - Components + let guestButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("둘러보기", for: .normal) + button.titleLabel?.font = .KorFont(style: .regular, size: 14) + button.setTitleColor(.g1000, for: .normal) + return button + }() + + private let logoImageView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "image_login_logo") + view.contentMode = .scaleAspectFit + return view + }() + + private let titleLabel: PPLabel = { + let label = PPLabel(style: .bold, fontSize: 16, text: "간편하게 SNS 로그인하고\n팝풀 서비스를 이용해보세요") + label.numberOfLines = 0 + label.textAlignment = .center + return label + }() + + let kakaoButton: PPButton = { + let button = PPButton(style: .kakao, text: "카카오톡으로 로그인") + return button + }() + + private let kakaoImageView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "icon_login_kakao") + return view + }() + + let appleButton: PPButton = { + let button = PPButton(style: .apple, text: "Apple로 로그인") + return button + }() + + private let appleImageView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "icon_login_apple") + return view + }() + + let inquiryButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("로그인이 어려우신가요?", for: .normal) + button.titleLabel?.font = .KorFont(style: .regular, size: 12) + button.setTitleColor(.g1000, for: .normal) + return button + }() // MARK: - init init() { super.init(frame: .zero) setUpConstraints() - self.backgroundColor = .red } required init?(coder: NSCoder) { @@ -29,5 +79,58 @@ final class LoginView: UIView { private extension LoginView { func setUpConstraints() { + self.addSubview(guestButton) + guestButton.snp.makeConstraints { make in + make.top.equalToSuperview().inset(11) + make.trailing.equalToSuperview().inset(20) + } + + self.addSubview(logoImageView) + logoImageView.snp.makeConstraints { make in + make.height.equalTo(90) + make.width.equalTo(70) + make.top.equalTo(guestButton.snp.bottom).offset(75) + make.centerX.equalToSuperview() + } + + self.addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(logoImageView.snp.bottom).offset(28) + } + + self.addSubview(kakaoButton) + kakaoButton.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(156) + make.leading.trailing.equalToSuperview().inset(20) + make.height.equalTo(50) + } + + kakaoButton.addSubview(kakaoImageView) + kakaoImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().inset(20) + make.size.equalTo(22) + } + + self.addSubview(appleButton) + appleButton.snp.makeConstraints { make in + make.top.equalTo(kakaoButton.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview().inset(20) + make.height.equalTo(50) + } + + appleButton.addSubview(appleImageView) + appleImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().inset(20) + make.size.equalTo(22) + } + + self.addSubview(inquiryButton) + inquiryButton.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(56) + make.centerX.equalToSuperview() + } } } diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/Contents.json b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_apple.imageset/Contents.json b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_apple.imageset/Contents.json new file mode 100644 index 00000000..5f670ca8 --- /dev/null +++ b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_apple.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_apple.imageset/logo.png b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_apple.imageset/logo.png new file mode 100644 index 00000000..98392d4f Binary files /dev/null and b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_apple.imageset/logo.png differ diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_kakao.imageset/Contents.json b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_kakao.imageset/Contents.json new file mode 100644 index 00000000..5f670ca8 --- /dev/null +++ b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_kakao.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_kakao.imageset/logo.png b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_kakao.imageset/logo.png new file mode 100644 index 00000000..89becee5 Binary files /dev/null and b/Poppool/Poppool/Resource/Assets.xcassets/Icon/icon_login/icon_login_kakao.imageset/logo.png differ diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Image/Contents.json b/Poppool/Poppool/Resource/Assets.xcassets/Image/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Poppool/Poppool/Resource/Assets.xcassets/Image/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Image/image_login_logo.imageset/Contents.json b/Poppool/Poppool/Resource/Assets.xcassets/Image/image_login_logo.imageset/Contents.json new file mode 100644 index 00000000..68658dcb --- /dev/null +++ b/Poppool/Poppool/Resource/Assets.xcassets/Image/image_login_logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image_login_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Poppool/Poppool/Resource/Assets.xcassets/Image/image_login_logo.imageset/image_login_logo.png b/Poppool/Poppool/Resource/Assets.xcassets/Image/image_login_logo.imageset/image_login_logo.png new file mode 100644 index 00000000..00933500 Binary files /dev/null and b/Poppool/Poppool/Resource/Assets.xcassets/Image/image_login_logo.imageset/image_login_logo.png differ diff --git a/Poppool/Poppool/Resource/Info.plist b/Poppool/Poppool/Resource/Info.plist index 06e3b834..7b4ad126 100644 --- a/Poppool/Poppool/Resource/Info.plist +++ b/Poppool/Poppool/Resource/Info.plist @@ -2,6 +2,17 @@ + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + kakao2ab6cefb16ac200822530a956ecdf014 + + + LSApplicationQueriesSchemes kakaokompassauth @@ -9,6 +20,17 @@ kakaoplus kakaotalk + UIAppFonts + + GothicA1-Bold.ttf + GothicA1-Light.ttf + GothicA1-Medium.ttf + GothicA1-Regular.ttf + Poppins-Bold.ttf + Poppins-Light.ttf + Poppins-Medium.ttf + Poppins-Regular.ttf + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -26,16 +48,5 @@ - UIAppFonts - - GothicA1-Bold.ttf - GothicA1-Light.ttf - GothicA1-Medium.ttf - GothicA1-Regular.ttf - Poppins-Bold.ttf - Poppins-Light.ttf - Poppins-Medium.ttf - Poppins-Regular.ttf -