Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
54b5b46
fix: AuthCoordinator escaping closure inout parameter 에러 해결 #6
Roy-wonji Mar 23, 2026
e5304d1
fix: NavigationRequestObserver 다중 업데이트 에러 해결
Roy-wonji Mar 24, 2026
923b8a4
fix: TCA AuthCoordinator 최종 완성 - 모든 에러 해결
Roy-wonji Mar 24, 2026
19cbee8
feat: Home 모듈 TCA 구조 구현 및 UI 에셋 추가
Roy-wonji Mar 24, 2026
a7b3e34
feat: Home 모듈 TCA Coordinator 구조 완성 #6
Roy-wonji Mar 24, 2026
055e587
feat: Auth/Profile DTO models and mappers 추가 구현 #6
Roy-wonji Mar 25, 2026
574b970
feat: Profile API service 및 Repository 구현 #6
Roy-wonji Mar 25, 2026
e3b1a1f
feat: Profile 기능 완전 구현 및 Home 모듈 리팩터링 #6
Roy-wonji Mar 25, 2026
c1f295c
feat: Home TCA coordinator 구조 완성 및 Profile 연동 #6
Roy-wonji Mar 25, 2026
4de6424
feat: DesignSystem 내비게이션 컴포넌트 및 이미지 에셋 추가 #6
Roy-wonji Mar 25, 2026
d5e5a0f
feat: Profile 모듈 TCA 아키텍처 완전 구현 #6
Roy-wonji Mar 25, 2026
e0191d1
feat: Home-Profile 내비게이션 통합 및 앱 전환 애니메이션 개선 #6
Roy-wonji Mar 25, 2026
c96a5d6
feat: Data layer Auth/Profile service 쪽 추가 회원탈퇴 추가 #6
Roy-wonji Mar 25, 2026
0675a0e
feat: 프로필 수정 및 회원탈퇴 도메인 로직 완성 #6
Roy-wonji Mar 25, 2026
2e7df29
feat: 설정 지도 타입 선택 및 회원탈퇴 플로우 완성 #6
Roy-wonji Mar 25, 2026
c41feb2
feat: 출발 시간 알림 설정 기능 완성 #6
Roy-wonji Mar 25, 2026
dbe3cdb
feat: 애니메이션 시스템 및 에러 처리 개선 #6
Roy-wonji Mar 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ public extension InfoPlist {
])
.setUIRequiredDeviceCapabilities(["armv7"])
.setCFBundleDevelopmentRegion()
.setUISupportedInterfaceOrientations(["UIInterfaceOrientationPortrait"])
.setBaseURL("$(BASE_URL)")
.setNMFGovClientId("$(NMFGovClientId)")
.setNMFGovClientSecret("$(NMFGovClientSecret)")
.setGoogleReversedClientID("${REVERSED_CLIENT_ID}")
.setGoogleClientID("${GOOGLE_CLIENT_ID}")
.setGoogleClientiOSID("${GOOGLE_IOS_CLIENT_ID}")
.setGIDClientID("${GOOGLE_CLIENT_ID}")

.setUILaunchScreens()
.setLocationPermissions()

Expand Down
4 changes: 3 additions & 1 deletion Projects/App/Sources/Di/DiRegister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public final class AppDIManager {
.register { AppleOAuthRepositoryImpl() as AppleOAuthInterface }
.register { AppleOAuthProvider() as AppleOAuthProviderInterface }
// MARK: - 회원가입
.register { SignUpRepositoryImpl() as SignUpInterface }
.register { SignUpRepositoryImpl() as SignUpInterface }
// MARK: - 프로필
.register(ProfileInterface.self) { ProfileRepositoryImpl() }



Expand Down
36 changes: 21 additions & 15 deletions Projects/App/Sources/Reducer/AppReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
// Created by Wonji Suh on 3/1/26.
//

import Foundation
import Presentation
import Home
import ComposableArchitecture
import Entity
import LogMacro
Expand All @@ -17,12 +19,12 @@ public struct AppReducer: Sendable {
@ObservableState
public enum State {
case splash(SplashReducer.State)
case home(HomeReducer.State)
case home(HomeCoordinator.State)
case auth(AuthCoordinator.State)


public init() {
self = .splash(SplashReducer.State())
self = .splash(.init())
}

// Animation identifier for SwiftUI transitions
Expand Down Expand Up @@ -53,7 +55,8 @@ public struct AppReducer: Sendable {

//MARK: - 앱내에서 사용하는 액션
public enum InnerAction: Equatable {

case updateToHome
case updateToAuth
}

//MARK: - 비동기 처리 액션
Expand All @@ -71,7 +74,7 @@ public struct AppReducer: Sendable {
@CasePathable
public enum ScopeAction {
case splash(SplashReducer.Action)
case home(HomeReducer.Action)
case home(HomeCoordinator.Action)
case auth(AuthCoordinator.Action)
}

Expand All @@ -89,6 +92,16 @@ public struct AppReducer: Sendable {
}

public var body: some ReducerOf<Self> {
EmptyReducer()
.ifCaseLet(\.splash, action: \.scope.splash) {
SplashReducer()
}
.ifCaseLet(\.home, action: \.scope.home) {
HomeCoordinator()
}
.ifCaseLet(\.auth, action: \.scope.auth) {
AuthCoordinator()
}
Reduce { state, action in
switch action {
case .view(let viewAction):
Expand All @@ -107,15 +120,6 @@ public struct AppReducer: Sendable {
return handleScopeAction(state: &state, action: scopeAction)
}
}
.ifCaseLet(\.splash, action: \.scope.splash) {
SplashReducer()
}
.ifCaseLet(\.home, action: \.scope.home) {
HomeReducer()
}
.ifCaseLet(\.auth, action: \.scope.auth) {
AuthCoordinator()
}
}
}

Expand Down Expand Up @@ -185,7 +189,7 @@ extension AppReducer {
// 토큰이 있어서 메인 화면으로 이동
return .run { send in
try await clock.sleep(for: Constants.splashTransitionDelay)
await send(.view(.presentAuth))
await send(.view(.presentRoot))
}

case .splash(.navigation(.presentAuth)):
Expand All @@ -196,9 +200,11 @@ extension AppReducer {
}

case .auth(.navigation(.presentMain)):
// 로그인 완료 후 메인 화면으로
return .send(.view(.presentRoot))

case .home(.router(.routeAction(id: _, action: .profile(.navigation(.presentAuth))))):
return .send(.view(.presentAuth))

default:
return .none
}
Expand Down
22 changes: 14 additions & 8 deletions Projects/App/Sources/View/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
//

import SwiftUI

import Presentation


import ComposableArchitecture
import DesignSystem

struct AppView: View {
@Bindable var store: StoreOf<AppReducer>
Expand All @@ -32,20 +30,28 @@ struct AppView: View {
case .auth:
if let store = store.scope(state: \.auth, action: \.scope.auth) {
AuthCoordinatorView(store: store)
.transition(.opacity.combined(with: .scale(scale: 0.98)))
.transition(.asymmetric(
insertion: .move(edge: .trailing),
removal: .move(edge: .leading)
))
}

case .home:
if let store = store.scope(state: \.home, action: \.scope.home) {
HomeView(store: store)
.transition(.opacity.combined(with: .scale(scale: 0.98)))
HomeCoordinatorView(store: store)
.transition(.asymmetric(
insertion: .move(edge: .trailing),
removal: .move(edge: .leading)
))
}

}
}

}

.animation(
.appDefault,
value: store.state.animationID
)
}
}

3 changes: 3 additions & 0 deletions Projects/Data/API/Sources/API/Auth/AuthAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum AuthAPI: String, CaseIterable {
case login
case logout
case refresh
case withDraw

public var description: String {
switch self {
Expand All @@ -20,6 +21,8 @@ public enum AuthAPI: String, CaseIterable {
return "/logout"
case .refresh:
return "/refresh"
case .withDraw:
return ""
}
}
}
3 changes: 3 additions & 0 deletions Projects/Data/API/Sources/API/Domain/TimeSpotDomain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AsyncMoya

public enum TimeSpotDomain {
case auth
case place
case profile
}

Expand All @@ -23,6 +24,8 @@ extension TimeSpotDomain: DomainType {
switch self {
case .auth:
return "api/v1/auth"
case .place:
return "api/v1/place"
case .profile:
return "api/v1/users"
}
Expand Down
23 changes: 23 additions & 0 deletions Projects/Data/API/Sources/API/Profile/ProfileAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// ProfileAPI.swift
// API
//
// Created by Wonji Suh on 3/25/26.
//

import Foundation

public enum ProfileAPI: String, CaseIterable {
case user
case editUser

public var description : String {
switch self {
case .user:
return ""

case .editUser:
return ""
}
}
}
41 changes: 41 additions & 0 deletions Projects/Data/Model/Sources/Auth/DTO/LogoutDTOModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// LogoutDTOModel.swift
// Model
//
// Created by Wonji Suh on 3/25/26.
//

import Foundation

public struct LogoutDTOModel: Decodable, Equatable {
public let code: Int
public let message: String
public let data: EmptyResponseDTO?

public init(
code: Int,
message: String,
data: EmptyResponseDTO? = nil
) {
self.code = code
self.message = message
self.data = data
}

enum CodingKeys: String, CodingKey {
case code
case message
case data
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.code = try container.decode(Int.self, forKey: .code)
self.message = try container.decode(String.self, forKey: .message)
self.data = try container.decodeIfPresent(EmptyResponseDTO.self, forKey: .data)
}
}

public struct EmptyResponseDTO: Decodable, Equatable {
public init() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,29 @@ public extension LoginResponseDTO {
default: .apple
}

let mapType: ExternalMapType? = if let mapName = self.map?.mapName {
switch mapName.uppercased() {
case "GOOGLE", "구글":
.googleMap
case "NAVER", "네이버", "네이버지도":
.naverMap
case "APPLE", "애플", "지도":
.appleMap
default:
nil
}
} else {
nil
}

return LoginEntity(
name: self.userInfo?.nickname ?? "",
isNewUser: self.newUser ?? false,
provider: provider,
token: token,
email: self.userInfo?.email ?? ""
email: self.userInfo?.email ?? "",
mapType: mapType,
mapURLScheme: self.map?.mapURLScheme
)
}
}
17 changes: 17 additions & 0 deletions Projects/Data/Model/Sources/Auth/Mapper/LogoutDTOModel+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// LogoutDTOModel+.swift
// Model
//
// Created by Wonji Suh on 3/25/26.
//

import Entity

public extension LogoutDTOModel {
func toDomain() -> LogoutEntity {
LogoutEntity(
code: code,
message: message
)
}
}
41 changes: 41 additions & 0 deletions Projects/Data/Model/Sources/Profile/DTO/ProfileDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// ProfileDTO.swift
// Model
//
// Created by Wonji Suh on 3/25/26.
//

import Foundation

public typealias ProfileDTOModel = BaseResponseDTO<ProfileResponseDTO>

// MARK: - DataClass
public struct ProfileResponseDTO: Decodable, Equatable {
let userID, email, nickname, mapAPI: String
let role, providerType, createdAt: String

enum CodingKeys: String, CodingKey {
case userID = "userId"
case email, nickname
case mapAPI = "mapApi"
case role, providerType, createdAt
}

public init(
userID: String,
email: String,
nickname: String,
mapAPI: String,
role: String,
providerType: String,
createdAt: String
) {
self.userID = userID
self.email = email
self.nickname = nickname
self.mapAPI = mapAPI
self.role = role
self.providerType = providerType
self.createdAt = createdAt
}
}
Loading