Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions Wable-iOS/App/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

private let loginRepository = LoginRepositoryImpl()
private let profileRepository = ProfileRepositoryImpl()
private let userSessionRepository = UserSessionRepositoryImpl(
userDefaults: UserDefaultsStorage(
userDefaults: UserDefaults.standard,
jsonEncoder: JSONEncoder(),
jsonDecoder: JSONDecoder()
)
)
private let userSessionRepository = UserSessionRepositoryImpl(userDefaults: UserDefaultsStorage())
private let checkAppUpdateRequirementUseCase = CheckAppUpdateRequirementUseCaseImpl()

// MARK: - UIComponent
Expand Down Expand Up @@ -66,17 +60,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

private extension SceneDelegate {
func configureLoginScreen() {
let userProfileUseCase = UserProfileUseCase(repository: ProfileRepositoryImpl())
let fetchUserAuthUseCase = FetchUserAuthUseCase(
loginRepository: loginRepository,
userSessionRepository: userSessionRepository
)
let updateFCMTokenUseCase = UpdateFCMTokenUseCase(repository: ProfileRepositoryImpl())
let updateUserSessionUseCase = FetchUserInformationUseCase(repository: userSessionRepository)

self.window?.rootViewController = LoginViewController(
viewModel: LoginViewModel(
updateFCMTokenUseCase: UpdateFCMTokenUseCase(
repository: ProfileRepositoryImpl()
),
fetchUserAuthUseCase: FetchUserAuthUseCase(
loginRepository: loginRepository,
userSessionRepository: userSessionRepository
),
updateUserSessionUseCase: FetchUserInformationUseCase(repository: userSessionRepository),
userProfileUseCase: UserProfileUseCase(repository: ProfileRepositoryImpl())
userProfileUseCase: userProfileUseCase,
fetchUserAuthUseCase: fetchUserAuthUseCase,
updateFCMTokenUseCase: updateFCMTokenUseCase,
updateUserSessionUseCase: updateUserSessionUseCase
)
)
}
Expand Down
4 changes: 2 additions & 2 deletions Wable-iOS/Domain/Error/WableError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ enum WableError: String, Error {
case notFoundComment = "해당하는 답글이 없습니다."
case unauthorizedMember = "권한이 없는 유저입니다."
case unauthorizedToken = "유효하지 않은 토큰입니다."
case kakaoUnauthorizedUser = "카카오 로그인 실패. 만료되었거나 잘못된 카카오 토큰입니다."
case failedToValidateAppleLogin = "애플 로그인 실패. 만료되었거나 잘못된 애플 토큰입니다."
case failedToKakaoLogin = "카카오 로그인 실패. 작업이 취소되었거나 토큰 오류입니다."
case failedToAppleLogin = "애플 로그인 실패. 작업이 취소되었거나 토큰 오류입니다."
case signinRequired = "access, refreshToken 모두 만료되었습니다. 재로그인이 필요합니다."
case validAccessToken = "아직 유효한 accessToken 입니다."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ final class UpdateFCMTokenUseCase {

extension UpdateFCMTokenUseCase {
func execute(nickname: String) -> AnyPublisher<Void, WableError> {
guard let token = repository.fetchFCMToken() else {
return .fail(WableError.noToken)
}

guard let token = repository.fetchFCMToken() else { return .fail(WableError.noToken) }
return repository.updateUserProfile(nickname: nickname, fcmToken: token)
}
}
Expand Down
33 changes: 31 additions & 2 deletions Wable-iOS/Domain/UseCase/Onboarding/UserProfileUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ final class UserProfileUseCase {
}

extension UserProfileUseCase {
func execute(profile: UserProfile? = nil, isPushAlarmAllowed: Bool? = nil, isAlarmAllowed: Bool? = nil, image: UIImage? = nil, defaultProfileType: String? = nil) -> AnyPublisher<Void, WableError> {
func updateProfile(
profile: UserProfile?,
isPushAlarmAllowed: Bool? = nil,
isAlarmAllowed: Bool? = nil,
image: UIImage? = nil,
defaultProfileType: String? = nil
) -> AnyPublisher<Void, WableError> {
return repository.updateUserProfile(
profile: profile,
isPushAlarmAllowed: isPushAlarmAllowed,
Expand All @@ -29,7 +35,30 @@ extension UserProfileUseCase {
)
}

func execute(userID: Int) -> AnyPublisher<UserProfile, WableError> {
func fetchProfile(userID: Int) -> AnyPublisher<UserProfile, WableError> {
return repository.fetchUserProfile(memberID: userID)
}

func updateProfileWithUserID(
userID: Int,
isPushAlarmAllowed: Bool? = nil,
isAlarmAllowed: Bool? = nil,
image: UIImage? = nil,
defaultProfileType: String? = nil
) -> AnyPublisher<Void, WableError> {
let fetchProfile = repository.fetchUserProfile(memberID: userID)
let updateProfile = { [weak self] (profile: UserProfile) -> AnyPublisher<Void, WableError> in
guard let self = self else { return Fail(error: WableError.unknownError).eraseToAnyPublisher() }

return self.updateProfile(
profile: profile,
isPushAlarmAllowed: isPushAlarmAllowed,
isAlarmAllowed: isAlarmAllowed,
image: image,
defaultProfileType: defaultProfileType
)
}

return fetchProfile.flatMap(updateProfile).eraseToAnyPublisher()
}
}
2 changes: 1 addition & 1 deletion Wable-iOS/Infra/Auth/AppleAuthProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension AppleAuthProvider: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
guard let promise = self.promise else { return }

promise(.failure(.failedToValidateAppleLogin))
promise(.failure(.failedToAppleLogin))
}
}

Expand Down
4 changes: 2 additions & 2 deletions Wable-iOS/Infra/Auth/KakaoAuthProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ private extension KakaoAuthProvider {
promise: @escaping (Result<String?, WableError>) -> Void
) {
if error != nil {
promise(.failure(.kakaoUnauthorizedUser))
promise(.failure(.failedToKakaoLogin))
return
}

guard let token = oauthToken?.accessToken else {
promise(.failure(.kakaoUnauthorizedUser))
promise(.failure(.failedToKakaoLogin))
return
}

Expand Down
6 changes: 5 additions & 1 deletion Wable-iOS/Infra/Local/UserDefaultsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ struct UserDefaultsStorage {
private let jsonEncoder: JSONEncoder
private let jsonDecoder: JSONDecoder

init(userDefaults: UserDefaults = .standard, jsonEncoder: JSONEncoder, jsonDecoder: JSONDecoder) {
init(
userDefaults: UserDefaults = .standard,
jsonEncoder: JSONEncoder = JSONEncoder(),
jsonDecoder: JSONDecoder = JSONDecoder()
) {
self.userDefaults = userDefaults
self.jsonEncoder = jsonEncoder
self.jsonDecoder = jsonDecoder
Expand Down
19 changes: 15 additions & 4 deletions Wable-iOS/Presentation/Login/LoginViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,24 @@ private extension LoginViewController {
.sink { owner, sessionInfo in
let condition = sessionInfo.isNewUser || sessionInfo.user.nickname == ""

if condition {
AmplitudeManager.shared.trackEvent(tag: .clickAgreePopupSignup)
}

if condition { AmplitudeManager.shared.trackEvent(tag: .clickAgreePopupSignup) }
condition ? owner.navigateToOnboarding() : owner.navigateToHome()
}
.store(in: cancelBag)

output.error
.receive(on: DispatchQueue.main)
.withUnretained(self)
.sink { owner, error in
let toast = WableSheetViewController(
title: "로그인 중 오류가 발생했어요",
message: "\(error.localizedDescription)\n다시 시도해주세요."
)

toast.addAction(.init(title: "확인", style: .primary))
owner.present(toast, animated: true)
}
.store(in: cancelBag)
}

private func navigateToOnboarding() {
Expand Down
147 changes: 62 additions & 85 deletions Wable-iOS/Presentation/Login/LoginViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,123 +14,100 @@ final class LoginViewModel {

// MARK: Property

private let updateFCMTokenUseCase: UpdateFCMTokenUseCase
private let userProfileUseCase: UserProfileUseCase
private let fetchUserAuthUseCase: FetchUserAuthUseCase
private let updateFCMTokenUseCase: UpdateFCMTokenUseCase
private let updateUserSessionUseCase: FetchUserInformationUseCase
private let userProfileUseCase: UserProfileUseCase
private let loginSuccessSubject = PassthroughSubject<Account, Never>()
private let loginErrorSubject = PassthroughSubject<WableError, Never>()
private let loginSuccessSubject = PassthroughSubject<Account, Never>()

// MARK: - LifeCycle
// MARK: - Life Cycle

init(
updateFCMTokenUseCase: UpdateFCMTokenUseCase,
userProfileUseCase: UserProfileUseCase,
fetchUserAuthUseCase: FetchUserAuthUseCase,
updateUserSessionUseCase: FetchUserInformationUseCase,
userProfileUseCase: UserProfileUseCase
updateFCMTokenUseCase: UpdateFCMTokenUseCase,
updateUserSessionUseCase: FetchUserInformationUseCase
) {
self.updateFCMTokenUseCase = updateFCMTokenUseCase
self.userProfileUseCase = userProfileUseCase
self.fetchUserAuthUseCase = fetchUserAuthUseCase
self.updateFCMTokenUseCase = updateFCMTokenUseCase
self.updateUserSessionUseCase = updateUserSessionUseCase
self.userProfileUseCase = userProfileUseCase
}
}

// MARK: - Extension

extension LoginViewModel: ViewModelType {
struct Input {
let kakaoLoginTrigger: AnyPublisher<Void, Never>
let appleLoginTrigger: AnyPublisher<Void, Never>
}

struct Output {
let error: AnyPublisher<WableError, Never>
let account: AnyPublisher<Account, Never>
}

func transform(input: Input, cancelBag: CancelBag) -> Output {
let kakaoLoginTrigger = input.kakaoLoginTrigger
.map { SocialPlatform.kakao }
let appleLoginTrigger = input.appleLoginTrigger
.map { SocialPlatform.apple }
let kakaoLoginTrigger = input.kakaoLoginTrigger.map { SocialPlatform.kakao }
let appleLoginTrigger = input.appleLoginTrigger.map { SocialPlatform.apple }

Publishers.Merge(appleLoginTrigger, kakaoLoginTrigger)
.withUnretained(self)
.flatMap { owner, flatform -> AnyPublisher<Account, Never> in
return owner.fetchUserAuthUseCase.execute(platform: flatform)
.handleEvents(receiveCompletion: { completion in
if case .failure(let error) = completion {
WableLogger.log("로그인 중 오류 발생: \(error)", for: .error)
}
})
.catch { error -> AnyPublisher<Account, Never> in
return Empty<Account, Never>().eraseToAnyPublisher()
}
.eraseToAnyPublisher()
.flatMap { owner, platform -> AnyPublisher<Account, Never> in
return owner.fetchUserAuth(platform: platform)
}
.sink(
receiveCompletion: { completion in
WableLogger.log("로그인 작업 완료", for: .debug)
},
receiveValue: { [weak self] account in
guard let self = self else { return }

self.updateFCMTokenUseCase.execute(nickname: account.user.nickname)
.sink { completion in
if case .failure(let error) = completion {
WableLogger.log("FCM 토큰 저장 중 에러 발생: \(error)", for: .error)
} else {
WableLogger.log("FCM 토큰 저장 성공", for: .network)
}
} receiveValue: { () in
}
.store(in: cancelBag)

Task {
let isAuthorized = await UNUserNotificationCenter.current().notificationSettings().authorizationStatus == .authorized

WableLogger.log("\(isAuthorized)", for: .debug)

self.updateUserSessionUseCase.updateUserSession(
userID: account.user.id,
nickname: account.user.nickname,
profileURL: account.user.profileURL,
isPushAlarmAllowed: isAuthorized,
isAdmin: account.isAdmin,
isAutoLoginEnabled: true
)
.sink(receiveCompletion: { _ in
}, receiveValue: { _ in
WableLogger.log("로컬에 세션 저장 완료", for: .debug)
})
.store(in: cancelBag)

self.userProfileUseCase.execute(userID: account.user.id)
.sink { _ in
} receiveValue: { profile in
self.userProfileUseCase.execute(profile: profile, isPushAlarmAllowed: isAuthorized)
.sink { completion in
switch completion {
case .failure(let error):
WableLogger.log("서버로 프로필 업데이트 중 오류 발생: \(error)", for: .error)
default:
WableLogger.log("로그인 및 서버로 프로필 업데이트 완료", for: .debug)
}
} receiveValue: { _ in

}
.store(in: cancelBag)
}
.store(in: cancelBag)
}

self.loginSuccessSubject.send(account)
}
)
.sink(receiveValue: { [weak self] account in
guard let self = self else { return }

self.updateFCMToken(account: account, cancelBag: cancelBag)
self.updateUserProfile(userID: account.user.id, cancelBag: cancelBag)
self.loginSuccessSubject.send(account)
})
.store(in: cancelBag)

return Output(
error: loginErrorSubject.eraseToAnyPublisher(),
account: loginSuccessSubject.eraseToAnyPublisher()
)
}
}

// MARK: - Helper Method

private extension LoginViewModel {
func fetchUserAuth(platform: SocialPlatform) -> AnyPublisher<Account, Never> {
return fetchUserAuthUseCase.execute(platform: platform)
.handleEvents(receiveCompletion: { completion in
if case .failure(let error) = completion { self.loginErrorSubject.send(error) }
})
.catch { error -> AnyPublisher<Account, Never> in
return Empty<Account, Never>().eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}

func updateFCMToken(account: Account, cancelBag: CancelBag) {
self.updateFCMTokenUseCase.execute(nickname: account.user.nickname)
.catch { error -> AnyPublisher<Void, Never> in
self.loginErrorSubject.send(error)
return .just(())
}
.sink(receiveValue: {})
.store(in: cancelBag)
}

func updateUserProfile(userID: Int, cancelBag: CancelBag) {
Task {
let authorizedStatus = await UNUserNotificationCenter.current().notificationSettings().authorizationStatus
let isAuthorized = authorizedStatus == .authorized

self.userProfileUseCase.updateProfileWithUserID(userID: userID, isPushAlarmAllowed: isAuthorized)
.catch { error -> AnyPublisher<Void, Never> in
self.loginErrorSubject.send(error)
return .just(())
}
.sink(receiveValue: {})
.store(in: cancelBag)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private extension AgreementViewController {
userSession in
guard let userSession = userSession else { return }

owner.profileUseCase.execute(
owner.profileUseCase.updateProfile(
profile: UserProfile(
user: User(
id: userSession.id,
Expand Down
Loading