Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
df60367
CHORE: 실기기 + Firebase 에뮬레이터 테스트 가능하도록 설정
sangYuLv Aug 25, 2026
8156382
FEAT: 인증/유저 저장소 프로토콜 추가
sangYuLv Sep 7, 2026
0582204
FEAT: 로그인 서비스 추상화 추가
sangYuLv Sep 7, 2026
1f23f8f
FEAT: 로그인/로그아웃 유스케이스 추가
sangYuLv Sep 7, 2026
dc8c6c6
FEAT: Firebase Auth 저장소 구현
sangYuLv Sep 7, 2026
ad670ee
FEAT: Firestore 유저 저장소 구현
sangYuLv Sep 7, 2026
cdce39a
FEAT: Apple Sign In 제공자 구현
sangYuLv Sep 7, 2026
2f44e08
FEAT: 로그인 ViewModel 및 ViewController 연결
sangYuLv Sep 7, 2026
34763f9
FEAT: 로그인 의존성 등록 및 자동 로그인
sangYuLv Sep 7, 2026
8c600f3
CHORE: Sign In with Apple capability 추가
sangYuLv Sep 7, 2026
b61aec9
CHORE: Firebase Auth 에뮬레이터 제거 및 호스트 IP 업데이트
sangYuLv Sep 7, 2026
42d5a67
FIX: 자동 로그인 시 로그인 화면 깜빡임 제거
sangYuLv Sep 7, 2026
b1a71ad
FEAT: 마이페이지 로그아웃 기능 추가
sangYuLv Sep 7, 2026
260aae3
FIX: nonce 랜덤 문자열 생성 시 문자 균등 분포 보장
sangYuLv Sep 10, 2026
bf20f50
FIX: Apple 로그인 continuation 이중 resume 방지
sangYuLv Sep 10, 2026
961512b
CHORE: emulatorHost 환경 변수 전환
sangYuLv Sep 10, 2026
7bc0035
FIX: 로그인 버튼 이중 탭 방지
sangYuLv Sep 10, 2026
1b71665
FIX: 자동 로그인 검증 에러 분기 처리
sangYuLv Sep 10, 2026
e8df8a7
REFACTOR: AppError LocalizedError 적합성 추가 및 로그인 에러 메시지 개선
sangYuLv Sep 10, 2026
4fed34f
REFACTOR: Firestore User 매핑을 UserDTO로 분리
sangYuLv Sep 10, 2026
2c863fe
REFACTOR: 로그아웃 결과를 @Published 바인딩으로 통일
sangYuLv Sep 10, 2026
2f9e40e
REFACTOR: develop 병합 및 로그인/로그아웃의 Coordinator 아키텍처 전환
sangYuLv Sep 10, 2026
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
2 changes: 2 additions & 0 deletions WhereAreYou/WhereAreYou.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = WhereAreYou/WhereAreYou.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 2ZF894QLGK;
Expand Down Expand Up @@ -218,6 +219,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = WhereAreYou/WhereAreYou.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 2ZF894QLGK;
Expand Down
12 changes: 8 additions & 4 deletions WhereAreYou/WhereAreYou/Core/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}

private static var emulatorHost: String {
ProcessInfo.processInfo.environment["FIREBASE_EMULATOR_HOST"] ?? "localhost"
}

private func configureFirebaseEmulators() {
#if DEBUG
Auth.auth().useEmulator(withHost: "localhost", port: 9099)
let host = Self.emulatorHost
let firestoreSettings = Firestore.firestore().settings
firestoreSettings.host = "localhost:8080"
firestoreSettings.host = "\(host):8080"
firestoreSettings.isSSLEnabled = false
firestoreSettings.cacheSettings = MemoryCacheSettings()
Firestore.firestore().settings = firestoreSettings
Database.database().useEmulator(withHost: "localhost", port: 9000)
Storage.storage().useEmulator(withHost: "localhost", port: 9199)
Database.database().useEmulator(withHost: host, port: 9000)
Storage.storage().useEmulator(withHost: host, port: 9199)
#endif
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import UIKit
final class AppCoordinator: Coordinator {

private let window: UIWindow
private let screenFactory: AppScreenFactory
private let screenFactory: ScreenFactory
private var tabBarCoordinator: TabBarCoordinator?

init(window: UIWindow, screenFactory: ScreenFactory = .shared) {
Expand All @@ -20,26 +20,97 @@ final class AppCoordinator: Coordinator {
}

func start() {
window.rootViewController = makeLoginViewController()
let authRepository: AuthRepository = screenFactory.container.resolve()
if authRepository.currentUserID != nil {
showHome()
validateUser()
} else {
showLogin()
}
window.makeKeyAndVisible()
}

private func makeLoginViewController() -> UIViewController {
let loginViewController = screenFactory.makeLoginViewController()
loginViewController.onAppleLoginTap = { [weak self] in
self?.switchToHome()
// MARK: - 자동 로그인 검증

private func validateUser() {
let authRepository: AuthRepository = screenFactory.container.resolve()
let userRepository: UserRepository = screenFactory.container.resolve()
guard let userID = authRepository.currentUserID else {
switchToLogin()
return
}
return loginViewController
Task { @MainActor in
do {
_ = try await userRepository.fetchUser(userID: userID)
} catch AppError.network {
self.showNetworkErrorAlert()
} catch {
self.switchToLogin()
}
}
}

private func showNetworkErrorAlert() {
let alert = UIAlertController(
title: "연결 오류",
message: "네트워크 연결을 확인 후 다시 시도해 주세요.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "재시도", style: .default) { [weak self] _ in
self?.validateUser()
})
alert.addAction(UIAlertAction(title: "로그아웃", style: .destructive) { [weak self] _ in
self?.switchToLogin()
})
window.rootViewController?.present(alert, animated: true)
}

// MARK: - 초기 화면 설정

private func showLogin() {
window.rootViewController = makeLoginViewController()
}

private func showHome() {
let coordinator = TabBarCoordinator()
tabBarCoordinator = coordinator
coordinator.onSignOut = { [weak self] in
self?.switchToLogin()
}
coordinator.start()
window.rootViewController = coordinator.tabBarController
}

// MARK: - 화면 전환

private func switchToHome() {
let coordinator = TabBarCoordinator()
tabBarCoordinator = coordinator
coordinator.onSignOut = { [weak self] in
self?.switchToLogin()
}
coordinator.start()

UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve) {
self.window.rootViewController = coordinator.tabBarController
}
}

private func switchToLogin() {
tabBarCoordinator = nil
let loginViewController = makeLoginViewController()

UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve) {
self.window.rootViewController = loginViewController
}
}

private func makeLoginViewController() -> LoginViewController {
let loginViewController = screenFactory.makeLoginViewController()
loginViewController.onLoginSuccess = { [weak self] _ in
self?.switchToHome()
}
return loginViewController
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class MyPageCoordinator: NavigationCoordinator, MyPageCoordinating {

let navigationController: UINavigationController
private let screenFactory: MyPageScreenFactory
var onSignOut: (() -> Void)?

init(
navigationController: UINavigationController,
Expand Down Expand Up @@ -55,6 +56,12 @@ final class MyPageCoordinator: NavigationCoordinator, MyPageCoordinating {
push(screenFactory.makeLocationPermissionViewController())
}

// MARK: - Sign Out

func signOut() {
onSignOut?()
}

// MARK: - Appointment Notification List

func showAppointmentNotificationList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import UIKit
final class TabBarCoordinator: Coordinator {

let tabBarController = UITabBarController()
var onSignOut: (() -> Void)?

private var homeCoordinator: HomeCoordinator?
private var appointmentListCoordinator: AppointmentListCoordinator?
Expand All @@ -37,8 +38,12 @@ final class TabBarCoordinator: Coordinator {
)

let myPageNav = UINavigationController()
myPageCoordinator = MyPageCoordinator(navigationController: myPageNav)
myPageCoordinator?.start()
let myPageCoord = MyPageCoordinator(navigationController: myPageNav)
myPageCoord.onSignOut = { [weak self] in
self?.onSignOut?()
}
myPageCoordinator = myPageCoord
myPageCoord.start()
myPageNav.tabBarItem = UITabBarItem(
title: "마이페이지",
image: UIImage(systemName: "person"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ protocol MyPageCoordinating: AnyObject {
fetchItems: @escaping () -> [AppointmentListItem],
onToggle: @escaping (String) -> Void
)
func signOut()

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@
// Created by 김성훈 on 8/26/26.
//

import UIKit

extension DIContainer {

func registerDependencies() {
// 인증
register(AuthRepository.self, instance: FirebaseAuthRepository())
register(UserRepository.self, instance: FirestoreUserRepository())
register(SignInService.self, instance: AppleSignInProvider(
windowProvider: {
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
))

// 위치
let coreLocationRepository = CoreLocationRepository()
register(LocationRepository.self, instance: coreLocationRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@
extension ScreenFactory {

func makeLoginViewController() -> LoginViewController {
LoginViewController()
let signInService: SignInService = container.resolve()
let authRepository: AuthRepository = container.resolve()
let userRepository: UserRepository = container.resolve()
let useCase = SignInUseCase(
signInService: signInService,
authRepository: authRepository,
userRepository: userRepository
)
let viewModel = LoginViewModel(signInUseCase: useCase)
return LoginViewController(viewModel: viewModel)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ extension ScreenFactory {
let viewModel = MyPageViewModel(
observeLocationPermissionUseCase: ObserveLocationPermissionUseCase(
repository: container.resolve(LocationPermissionRepository.self)
),
signOutUseCase: SignOutUseCase(
authRepository: container.resolve(AuthRepository.self)
)
)
return MyPageViewController(viewModel: viewModel)
Expand Down
Loading
Loading