diff --git a/HMH_Tuist_iOS/Projects/App/Derived/InfoPlists/HMH-iOS-Info.plist b/HMH_Tuist_iOS/Projects/App/Derived/InfoPlists/HMH-iOS-Info.plist index a3d5de43..a58e1a02 100644 --- a/HMH_Tuist_iOS/Projects/App/Derived/InfoPlists/HMH-iOS-Info.plist +++ b/HMH_Tuist_iOS/Projects/App/Derived/InfoPlists/HMH-iOS-Info.plist @@ -2,75 +2,87 @@ - BASE_URL - $(BASE_URL) - BGTaskSchedulerPermittedIdentifiers - - com.HMH.dailyTask - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - kakao$(KAKAO_API_KEY) - - - - CFBundleVersion - 1 - KAKAO_API_KEY - $(KAKAO_API_KEY) - LSApplicationQueriesSchemes - - kakaokompassauth - kakaolink - - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UIAppFonts - - Pretendard-Regular.otf - Pretendard-SemiBold.otf - Pretendard-Medium.otf - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - + BASE_URL + $(BASE_URL) + BGTaskSchedulerPermittedIdentifiers + + com.HMH.dailyTask + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + kakao$(KAKAO_API_KEY) + + + + CFBundleVersion + 1 + KAKAO_API_KEY + $(KAKAO_API_KEY) + LSApplicationQueriesSchemes + + kakaokompassauth + kakaolink + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIAppFonts + + Pretendard-Regular.otf + Pretendard-SemiBold.otf + Pretendard-Medium.otf + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + diff --git a/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift b/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift index 8acf9880..f2ae4aaa 100644 --- a/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift +++ b/HMH_Tuist_iOS/Projects/App/Sources/Common/Custom/SplashView.swift @@ -30,7 +30,7 @@ struct SplashView: View { .background(DSKitAsset.blackground.swiftUIColor, ignoresSafeAreaEdges: .all) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 3.5, execute: { - viewModel.handleSplashScreen() +// viewModel.handleSplashScreen() }) } } diff --git a/HMH_Tuist_iOS/Projects/Domain/Sources/Entity/Auth/OAuthProviderType.swift b/HMH_Tuist_iOS/Projects/Domain/Sources/Entity/Auth/OAuthProviderType.swift index 243724c5..449b6e43 100644 --- a/HMH_Tuist_iOS/Projects/Domain/Sources/Entity/Auth/OAuthProviderType.swift +++ b/HMH_Tuist_iOS/Projects/Domain/Sources/Entity/Auth/OAuthProviderType.swift @@ -8,7 +8,7 @@ import Foundation -public enum OAuthProviderType { - case kakao - case apple +public enum OAuthProviderType: String { + case kakao = "KAKAO" + case apple = "APPLE" } diff --git a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginUseCase.swift b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginUseCase.swift new file mode 100644 index 00000000..272c858e --- /dev/null +++ b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginUseCase.swift @@ -0,0 +1,62 @@ +// +// LoginUseCase.swift +// LoginFeature +// +// Created by Seonwoo Kim on 11/8/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// + +import Foundation +import Combine + +import Domain +import Core + +public enum LoginResponseType { + case loginSuccess + case loginFailure + case onboardingNeeded +} + +public protocol LoginUseCaseType { + func login(provider: OAuthProviderType) -> AnyPublisher +} + +public final class LoginUseCase: LoginUseCaseType { + + private let repository: AuthRepositoryType + + public init(repository: AuthRepositoryType) { + self.repository = repository + } + + public func login(provider: OAuthProviderType) -> AnyPublisher { + repository.authorize(provider) + .handleEvents(receiveOutput: { socialToken in + UserManager.shared.socialToken = socialToken + }) + .flatMap { [weak self] _ -> AnyPublisher in + guard let self = self else { + return Fail(error: AuthError.appleAuthrizeError).eraseToAnyPublisher() + } + + return self.repository.socialLogin(socialPlatform: provider.rawValue) + .map { _ in LoginResponseType.loginSuccess } + .catch { error -> AnyPublisher in + switch error { + case .unregisteredUser: + return Just(.onboardingNeeded) + .setFailureType(to: AuthError.self) + .eraseToAnyPublisher() + default: + return Just(.loginFailure) + .setFailureType(to: AuthError.self) + .eraseToAnyPublisher() + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} + diff --git a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginView.swift b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginView.swift index ea6245b4..00e7f9b2 100644 --- a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginView.swift +++ b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/LoginView.swift @@ -6,9 +6,9 @@ // import SwiftUI -import AuthenticationServices import DSKit +import Domain public struct LoginView: View { @ObservedObject var viewModel: LoginViewModel @@ -23,15 +23,13 @@ public struct LoginView: View { .ignoresSafeArea() VStack(spacing: 10) { //TODO: 이미지 타입 문제거 같은데 지금 해결하기엔 싱싱미역 -// SwipeView(imageNames: [.onboardingFirst, .onboardingSecond, .onboardingThird]) -// .padding(.bottom, 75) - LoginButton(loginProvider: .kakao, viewModel: viewModel) - LoginButton(loginProvider: .apple, viewModel: viewModel) + SwipeView(swipeImages: [DSKitAsset.onboardingFirst.swiftUIImage, DSKitAsset.onboardingSecond.swiftUIImage, DSKitAsset.onboardingThird.swiftUIImage], viewModel: viewModel) + .padding(.bottom, 75) + LoginButton(loginProvider: OAuthProviderType.kakao, viewModel: viewModel) + LoginButton(loginProvider: OAuthProviderType.apple, viewModel: viewModel) } } .frame(maxHeight: .infinity) .padding(.vertical, 22) } } - - diff --git a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/ViewModels/LoginViewModel.swift b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/ViewModels/LoginViewModel.swift index 69304827..43f44a40 100644 --- a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/ViewModels/LoginViewModel.swift +++ b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/ViewModels/LoginViewModel.swift @@ -1,106 +1,65 @@ -import SwiftUI -import AuthenticationServices +// +// LoginViewModel.swift +// LoginFeature +// +// Created by Seonwoo Kim on 11/8/24. +// Copyright © 2024 HMH-iOS. All rights reserved. +// -import KakaoSDKUser +import Foundation +import Combine import Core +import Domain import DSKit -public class LoginViewModel: NSObject, ObservableObject { +public final class LoginViewModel: ObservableObject { - @Published public var isLoading: Bool = true - @Published var isPresented: Bool = false - @Published var alertType: CustomAlertType = .unlock + private let loginUseCase: LoginUseCaseType + private var cancelBag = CancelBag() - public func handleSplashScreen() { - self.isLoading = false - } + // 화면 이동 로직과 스와이프 인덱스 포함 + @Published private(set) var state = State(loginStatus: .loginFailure, swipeImageIndex: 0) - func handleAppleLogin() { - let request = ASAuthorizationAppleIDProvider().createRequest() - request.requestedScopes = [.fullName, .email] - - let authorizationController = ASAuthorizationController(authorizationRequests: [request]) - authorizationController.delegate = self - authorizationController.performRequests() + public init(loginUseCase: LoginUseCaseType) { + self.loginUseCase = loginUseCase + startImageTimer() } - func handleKakaoLogin() { - if (UserApi.isKakaoTalkLoginAvailable()) { - UserApi.shared.loginWithKakaoTalk {(oauthToken, error) in - if let error = error { - print(error) - } - if let oauthToken = oauthToken{ - let idToken = oauthToken.accessToken - UserManager.shared.socialPlatform = "KAKAO" - UserManager.shared.socialToken = "Bearer " + idToken - self.postSocialLoginData() - } - } - } else { - UserApi.shared.loginWithKakaoAccount {(oauthToken, error) in - if let error = error { - print("🍀",error) - } - if let oauthToken = oauthToken{ - print("kakao success") - UserManager.shared.socialPlatform = "KAKAO" - let idToken = oauthToken.accessToken - UserManager.shared.socialToken = "Bearer " + idToken - self.postSocialLoginData() - } - } - } + // MARK: Action + + enum Action { + case loginButtonDidTap(provider: OAuthProviderType) + case swipeButtonDidTap(index: Int) } - //TODO: 네트워크 부분은 의존성 정리한 뒤에 다시 연결해봅시다 - func postSocialLoginData() { -// let provider = Providers.AuthProvider -// let request = SocialLoginRequestDTO(socialPlatform: UserManager.shared.socialPlatform ?? "") -// -// provider.request(target: .socialLogin(data: request), instance: BaseResponse.self) { data in -// if data.status == 403 { -// UserManager.shared.appStateString = "onboarding" -// } else if data.status == 200 { -// guard let data = data.data else { return } -// UserManager.shared.refreshToken = data.token.refreshToken -// UserManager.shared.accessToken = data.token.accessToken -// UserManager.shared.appStateString = "home" -// } -// } + // MARK: State + + struct State { + var loginStatus: LoginResponseType + var swipeImageIndex: Int } -} - -extension LoginViewModel: ASAuthorizationControllerDelegate { - public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { - switch authorization.credential { - case let appleIDCredential as ASAuthorizationAppleIDCredential: - let userIdentifier = appleIDCredential.user - let fullName = appleIDCredential.fullName - - if let identityToken = appleIDCredential.identityToken, - let identifyTokenString = String(data: identityToken, encoding: .utf8) { - UserManager.shared.socialToken = identifyTokenString - UserManager.shared.socialPlatform = "APPLE" - self.postSocialLoginData() - } else { - print("Identity token is nil or failed to convert to string.") - } - default: - break + func send(action: Action) { + switch action { + case .loginButtonDidTap(let provider): + loginUseCase.login(provider: provider) + .sink(receiveCompletion: { _ in }) { [weak self] response in + self?.state.loginStatus = response + } + .store(in: cancelBag) + case .swipeButtonDidTap(let index): + self.state.swipeImageIndex = index } } - func handleAppleIDCredential(_ credential: ASAuthorizationAppleIDCredential) { - let fullName = credential.fullName - let name = (fullName?.familyName ?? "") + (fullName?.givenName ?? "") - UserManager.shared.userName = name - guard let idToken = String(data: credential.identityToken ?? Data(), encoding: .utf8) else { return print("no idToken!!") } + private func startImageTimer() { + Timer.publish(every: 3.0, on: .main, in: .common) + .autoconnect() + .sink { [weak self] _ in + self?.state.swipeImageIndex = ((self?.state.swipeImageIndex ?? 0) + 1) % 3 + } + .store(in: cancelBag) } - public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { - print(error.localizedDescription) - } } diff --git a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/LoginButton.swift b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/LoginButton.swift index 1765749d..c2d65a55 100644 --- a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/LoginButton.swift +++ b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/LoginButton.swift @@ -6,42 +6,25 @@ // import SwiftUI -import AuthenticationServices import DSKit - -enum SignInProvider { - case apple - case kakao - - var signInLogoImage: String { - switch self { - case .apple: - return "appleLogo" - case .kakao: - return "kakaoLogo" - } - } -} +import Domain struct LoginButton: View { - let loginProvider: SignInProvider + var loginProvider: OAuthProviderType = .apple @ObservedObject var viewModel: LoginViewModel + var signInLogoImage = DSKitAsset.appleLogo.swiftUIImage var body: some View { Button(action: { - if loginProvider == .apple { - viewModel.handleAppleLogin() - } else if loginProvider == .kakao { - viewModel.handleKakaoLogin() - } + viewModel.send(action: .loginButtonDidTap(provider: loginProvider)) }) { RoundedRectangle(cornerRadius: 6.3) .frame(width:336, height: 51) .foregroundColor(loginProvider == .apple ? DSKitAsset.whiteBtn.swiftUIColor : DSKitAsset.yelloBtn.swiftUIColor) .overlay( HStack { - Image(loginProvider.signInLogoImage) + Image(uiImage: loginProvider == .apple ? DSKitAsset.appleLogo.image : DSKitAsset.kakaoLogo.image) .resizable() .frame(width: 24, height: 24) .padding(.leading, 14) diff --git a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/SwipeView.swift b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/SwipeView.swift index 8e4f56c6..918125a9 100644 --- a/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/SwipeView.swift +++ b/HMH_Tuist_iOS/Projects/Features/LoginFeature/Sources/Views/SwipeView.swift @@ -6,20 +6,22 @@ // import SwiftUI - import DSKit struct SwipeView: View { - var imageNames: [ImageResource] - private let timer = Timer.publish(every: 3.0, on: .main, in: .common).autoconnect() - - @State private var selectedImageIndex: Int = 0 + var swipeImages: [Image] + @ObservedObject var viewModel: LoginViewModel var body: some View { VStack { - TabView(selection: $selectedImageIndex) { - ForEach(0..