diff --git a/.claude/rules/lessons-learned.md b/.claude/rules/lessons-learned.md index c906737..a578627 100644 --- a/.claude/rules/lessons-learned.md +++ b/.claude/rules/lessons-learned.md @@ -196,5 +196,73 @@ MyPage: AuthInterceptor.retry → RefreshCoordinator (기다림) --- +## 6. TCA StackAction.popFrom 타이밍 (2026-02-25) + +### ❌ 잘못된 접근: isEmpty로 root 복귀 체크 + +```swift +// 하지 말 것! +case .path(.popFrom(id: _)): + if state.path.isEmpty { // pop 완료 전 시점이므로 항상 false + return .send(.delegate(.showTabBar)) + } + return .none +``` + +**문제점**: +- `.popFrom`은 pop **시작** 시점에 호출됨 (pop 완료 전) +- 1depth → root로 pop 시: + - `.popFrom` 호출 시점: `path.count = 1` (아직 pop 전) + - `isEmpty` 체크 → `false` ❌ + - pop 완료 후: `path.count = 0` +- 결과: root로 돌아가도 TabBar가 표시되지 않음 + +### ✅ 올바른 접근: count == 1로 root 복귀 체크 + +```swift +case .path(.popFrom(id: _)): + // popFrom은 pop 시작 시점이므로 count == 1이면 root로 돌아감 + if state.path.count == 1 { + return .send(.delegate(.showTabBar)) + } + return .none +``` + +**타임라인**: +``` +1depth → root로 pop: + 1. .popFrom 호출 → path.count = 1 ✅ + 2. count == 1 체크 → true + 3. TabBar 표시 액션 전송 + 4. pop 완료 → path.count = 0 + +2depth → 1depth로 pop: + 1. .popFrom 호출 → path.count = 2 + 2. count == 1 체크 → false + 3. TabBar 숨김 유지 + 4. pop 완료 → path.count = 1 +``` + +**핵심**: +- **`.popFrom`은 pop 시작 시점, pop 완료 전** +- `path.count == 1`로 체크해야 root 복귀 감지 가능 +- `isEmpty`는 절대 true가 될 수 없음 (pop 전이므로) + +**참고**: TCA Navigation 문서 - StackAction lifecycle + +--- + +## 요약 + +| 항목 | ❌ 하지 말 것 | ✅ 해야 할 것 | +|------|-------------|-------------| +| **토큰 검증** | 클라이언트에서 만료 시간 체크 | 서버 401 응답에 반응 | +| **Splash** | refresh 시도 | 토큰 존재만 체크 | +| **AuthInterceptor** | 메인 Session 사용 | 별도 Session 사용 | +| **복잡도** | 과도한 최적화 | 단순하고 검증된 방법 | +| **StackAction.popFrom** | isEmpty로 체크 | count == 1로 체크 | + +--- + **업데이트 일자**: 2026-02-25 -**관련 이슈**: 자동 로그인 기능 구현 +**관련 이슈**: 자동 로그인 기능 구현, History Stack 네비게이션 전환 diff --git a/Projects/App/Sources/CustomTabBar.swift b/Projects/App/Sources/CustomTabBar.swift new file mode 100644 index 0000000..f2d9eb6 --- /dev/null +++ b/Projects/App/Sources/CustomTabBar.swift @@ -0,0 +1,86 @@ +// +// CustomTabBar.swift +// DoriApp +// +// Created by 강동영 on 2/25/26. +// Copyright © 2026 com.arex. All rights reserved. +// + +import SwiftUI +import DoriDesignSystem + +// MARK: - Custom TabBar + +struct CustomTabBar: View { + @Binding var selectedTab: MainTabFeature.State.Tab + + var body: some View { + HStack(spacing: 0) { + TabBarItem( + icon: selectedTab == .calendar ? Image(.tabbarCalendarFill) : Image(.tabbarCalendar), + title: "캘린더", + isSelected: selectedTab == .calendar + ) { + selectedTab = .calendar + } + + TabBarItem( + icon: selectedTab == .history ? Image(.tabbarHistoryFill) : Image(.tabbarHistory), + title: "내역", + isSelected: selectedTab == .history + ) { + selectedTab = .history + } + + TabBarItem( + icon: selectedTab == .myPage ? Image(.tabbarMypageFill) : Image(.tabbarMypage), + title: "마이페이지", + isSelected: selectedTab == .myPage + ) { + selectedTab = .myPage + } + } + .background(.doriWhite) + .shadow( + color: .black.opacity(0.05), + radius: 8, + x: 0, + y: -2 + ) + } +} + +// MARK: - TabBarItem + +private struct TabBarItem: View { + let icon: Image + let title: String + let isSelected: Bool + let action: () -> Void + + var font: TypoToken { + isSelected ? .bold(.b11) : .regular(.r11) + } + + var body: some View { + Button(action: action) { + VStack(spacing: 4) { + icon + .renderingMode(.template) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + .foregroundStyle(isSelected ? .main : .grey600) + + Text(title) + .pretendard(font) + .foregroundStyle(isSelected ? .main : .grey600) + } + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + } + .padding(.horizontal, 20) + .padding(.top, 16) + .buttonStyle(.plain) + } +} diff --git a/Projects/App/Sources/MainTabView.swift b/Projects/App/Sources/MainTabView.swift index 9b8150f..ec8bd06 100644 --- a/Projects/App/Sources/MainTabView.swift +++ b/Projects/App/Sources/MainTabView.swift @@ -23,6 +23,13 @@ struct MainTabFeature { enum Tab: Equatable { case calendar, history, myPage } + + // 파생 상태: 모든 탭이 root depth면 TabBar 표시 + var isTabBarVisible: Bool { + history.path.isEmpty && + calendar.addDori == nil && + myPage.navigationPath.isEmpty + } } enum Action { @@ -31,7 +38,7 @@ struct MainTabFeature { case history(HistoryFeature.Action) case myPage(MyPageFeature.Action) case delegate(Delegate) - + enum Delegate: Equatable { case needsAuthentication } @@ -73,19 +80,29 @@ struct MainTabView: View { @Bindable var store: StoreOf var body: some View { - TabView(selection: $store.selectedTab.sending(\.tabSelected)) { - CalendarView(store: store.scope(state: \.calendar, action: \.calendar)) - .tag(MainTabFeature.State.Tab.calendar) - .tabItem { Label("캘린더", systemImage: "calendar") } - - HistoryView(store: store.scope(state: \.history, action: \.history)) - .tag(MainTabFeature.State.Tab.history) - .tabItem { Label("내역", systemImage: "list.bullet.rectangle") } + VStack(spacing: 0) { + // Content + Group { + switch store.selectedTab { + case .calendar: + CalendarView(store: store.scope(state: \.calendar, action: \.calendar)) + case .history: + HistoryView(store: store.scope(state: \.history, action: \.history)) + case .myPage: + MyPageView(store: store.scope(state: \.myPage, action: \.myPage)) + } + } - MyPageView(store: store.scope(state: \.myPage, action: \.myPage)) - .tag(MainTabFeature.State.Tab.myPage) - .tabItem { Label("마이페이지", systemImage: "person.circle") } + // TabBar + if store.isTabBarVisible { + CustomTabBar( + selectedTab: $store.selectedTab.sending(\.tabSelected) + ) + .transition(.move(edge: .bottom)) + } } + .ignoresSafeArea(.keyboard) + .animation(.easeInOut(duration: 0.2), value: store.isTabBarVisible) } } diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar.imageset/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar.imageset/Contents.json new file mode 100644 index 0000000..489ddd4 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tabbar_calendar.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar.imageset/tabbar_calendar.svg b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar.imageset/tabbar_calendar.svg new file mode 100644 index 0000000..a160245 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar.imageset/tabbar_calendar.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar_fill.imageset/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar_fill.imageset/Contents.json new file mode 100644 index 0000000..ec85d0a --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar_fill.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tabbar_calendar_fill.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar_fill.imageset/tabbar_calendar_fill.svg b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar_fill.imageset/tabbar_calendar_fill.svg new file mode 100644 index 0000000..183230b --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_calendar_fill.imageset/tabbar_calendar_fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history.imageset/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history.imageset/Contents.json new file mode 100644 index 0000000..6a5c1cf --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tabbar_history.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history.imageset/tabbar_history.svg b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history.imageset/tabbar_history.svg new file mode 100644 index 0000000..5d8315d --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history.imageset/tabbar_history.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history_fill.imageset/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history_fill.imageset/Contents.json new file mode 100644 index 0000000..1ef2cf2 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history_fill.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tabbar_history_fill.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history_fill.imageset/tabbar_history_fill.svg b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history_fill.imageset/tabbar_history_fill.svg new file mode 100644 index 0000000..40db687 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_history_fill.imageset/tabbar_history_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage.imageset/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage.imageset/Contents.json new file mode 100644 index 0000000..31cc7e6 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tabbar_mypage.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage.imageset/tabbar_mypage.svg b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage.imageset/tabbar_mypage.svg new file mode 100644 index 0000000..b56672a --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage.imageset/tabbar_mypage.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage_fill.imageset/Contents.json b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage_fill.imageset/Contents.json new file mode 100644 index 0000000..27c3c19 --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage_fill.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tabbar_mypage_fill.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage_fill.imageset/tabbar_mypage_fill.svg b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage_fill.imageset/tabbar_mypage_fill.svg new file mode 100644 index 0000000..cd31b9d --- /dev/null +++ b/Projects/Core/DoriDesignSystem/Resources/Icons.xcassets/Tabbar/tabbar_mypage_fill.imageset/tabbar_mypage_fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Projects/Feature/Calendar/Sources/CalendarFeature.swift b/Projects/Feature/Calendar/Sources/CalendarFeature.swift index e5aa911..a373c34 100644 --- a/Projects/Feature/Calendar/Sources/CalendarFeature.swift +++ b/Projects/Feature/Calendar/Sources/CalendarFeature.swift @@ -66,15 +66,19 @@ public struct CalendarFeature { case .fabTapped: state.addDori = AddDoriFeature.State() return .none - + case .addDori(.presented(.delegate(.doriCreated))): state.addDori = nil return .none - + case .addDori(.presented(.delegate(.dismissed))): state.addDori = nil return .none - + + case .addDori(.dismiss): + state.addDori = nil + return .none + case .addDori: return .none diff --git a/Projects/Feature/Calendar/Sources/CalendarView.swift b/Projects/Feature/Calendar/Sources/CalendarView.swift index 23a0644..a75165b 100644 --- a/Projects/Feature/Calendar/Sources/CalendarView.swift +++ b/Projects/Feature/Calendar/Sources/CalendarView.swift @@ -8,7 +8,7 @@ import SwiftUI import ComposableArchitecture import DoriDesignSystem - +import FeatureAddDori public struct CalendarView: View { @Bindable var store: StoreOf @@ -69,6 +69,17 @@ public struct CalendarView: View { .navigationTitle("캘린더") .toolbarTitleDisplayMode(.inline) .onAppear { store.send(.onAppear) } + .overlay(alignment: .bottomTrailing) { + FloatingActionButton { + store.send(.fabTapped) + } + .padding(20) + } + .navigationDestination( + item: $store.scope(state: \.addDori, action: \.addDori) + ) { addDoriStore in + AddDoriView(store: addDoriStore) + } .sheet( isPresented: Binding( get: { store.selectedDay != nil }, diff --git a/Projects/Feature/Calendar/Sources/Components/CalendarGridView.swift b/Projects/Feature/Calendar/Sources/Components/CalendarGridView.swift index 1f4a045..adcfe4d 100644 --- a/Projects/Feature/Calendar/Sources/Components/CalendarGridView.swift +++ b/Projects/Feature/Calendar/Sources/Components/CalendarGridView.swift @@ -52,9 +52,7 @@ public struct CalendarGridView: View { } } } - .padding() .background(.doriWhite) - .cornerRadius(10) } } diff --git a/Projects/Feature/History/Sources/DoriList/DoriListView.swift b/Projects/Feature/History/Sources/DoriList/DoriListView.swift index 841154c..787784d 100644 --- a/Projects/Feature/History/Sources/DoriList/DoriListView.swift +++ b/Projects/Feature/History/Sources/DoriList/DoriListView.swift @@ -44,13 +44,6 @@ public struct DoriListView: View { .background(.grey100) .navigationTitle("내역") .navigationBarTitleDisplayMode(.inline) - .searchable( - text: Binding( - get: { store.searchText }, - set: { store.send(.searchTextChanged($0)) } - ), - prompt: "이름 또는 관계 검색" - ) .refreshable { store.send(.refresh) } diff --git a/Projects/Feature/History/Sources/HistoryFeature.swift b/Projects/Feature/History/Sources/HistoryFeature.swift index 45a343b..0e82d82 100644 --- a/Projects/Feature/History/Sources/HistoryFeature.swift +++ b/Projects/Feature/History/Sources/HistoryFeature.swift @@ -17,14 +17,22 @@ import FeatureAddDori public struct HistoryFeature { public init() {} + // MARK: - Path Reducer + + @Reducer + public enum Path { + case partnerHistory(PartnerDoriHistoryFeature) + case partnerDoriDetail(PartnerDoriDetailFeature) + case addDori(AddDoriFeature) + case editDori(EditDoriFeature) + } + // MARK: - State @ObservableState public struct State { public var doriList: DoriListFeature.State = .init() - @Presents public var partnerHistory: PartnerDoriHistoryFeature.State? - @Presents public var addDori: AddDoriFeature.State? - @Presents public var editDori: EditDoriFeature.State? + public var path = StackState() public init() {} } @@ -32,9 +40,7 @@ public struct HistoryFeature { public enum Action { case doriList(DoriListFeature.Action) - case partnerHistory(PresentationAction) - case addDori(PresentationAction) - case editDori(PresentationAction) + case path(StackActionOf) } // MARK: - Reducer @@ -50,66 +56,66 @@ public struct HistoryFeature { // MARK: DoriList delegate case .doriList(.delegate(.partnerTapped(let partner))): - state.partnerHistory = PartnerDoriHistoryFeature.State( - partnerId: partner.partnerId, - partnerName: partner.partnerName, - relationship: partner.relationship + state.path.append( + .partnerHistory( + PartnerDoriHistoryFeature.State( + partnerId: partner.partnerId, + partnerName: partner.partnerName, + relationship: partner.relationship + ) + ) ) return .none case .doriList(.delegate(.fabTapped)): - state.addDori = AddDoriFeature.State() + state.path.append(.addDori(AddDoriFeature.State())) return .none case .doriList: return .none - // MARK: PartnerDoriHistory delegate + // MARK: Path delegate 처리 + + // PartnerDoriHistory에서 doriTapped → Detail push + case .path(.element(id: _, action: .partnerHistory(.doriTapped(let dori)))): + state.path.append(.partnerDoriDetail(PartnerDoriDetailFeature.State(dori: dori))) + return .none - case .partnerHistory(.presented(.delegate(.allDoriDeleted))): - state.partnerHistory = nil + // PartnerDoriHistory에서 전체 삭제 + case .path(.element(id: _, action: .partnerHistory(.delegate(.allDoriDeleted)))): + state.path.removeAll() return .send(.doriList(.refresh)) - case .partnerHistory(.presented(.delegate(.editTapped(let dori)))): - state.editDori = EditDoriFeature.State(dori: dori) + // PartnerDoriDetail에서 editTapped → Edit push + case .path(.element(id: _, action: .partnerDoriDetail(.delegate(.editTapped(let dori))))): + state.path.append(.editDori(EditDoriFeature.State(dori: dori))) return .none - case .partnerHistory: + // PartnerDoriDetail에서 단건 삭제 → History로 복귀 + case .path(.element(id: _, action: .partnerDoriDetail(.delegate(.doriDeleted)))): + state.path.removeLast() return .none - // MARK: AddDori - - case .addDori(.presented(.delegate(.doriCreated(_)))): - state.addDori = nil + // AddDori 완료 → 리스트로 복귀 + case .path(.element(id: _, action: .addDori(.delegate(.doriCreated(_))))): + state.path.removeAll() return .send(.doriList(.refresh)) - case .addDori(.presented(.delegate(.dismissed))): - state.addDori = nil - return .none - - case .addDori: + // AddDori dismiss + case .path(.element(id: _, action: .addDori(.delegate(.dismissed)))): + state.path.removeAll() return .none - // MARK: EditDori - - case .editDori(.presented(.delegate(.doriUpdated(_)))): - state.editDori = nil - state.partnerHistory = nil + // EditDori 완료 → 리스트로 복귀 + case .path(.element(id: _, action: .editDori(.delegate(.doriUpdated(_))))): + state.path.removeAll() return .send(.doriList(.refresh)) - case .editDori: + case .path: return .none } } - .ifLet(\.$partnerHistory, action: \.partnerHistory) { - PartnerDoriHistoryFeature() - } - .ifLet(\.$addDori, action: \.addDori) { - AddDoriFeature() - } - .ifLet(\.$editDori, action: \.editDori) { - EditDoriFeature() - } + .forEach(\.path, action: \.path) } } @@ -123,26 +129,22 @@ public struct HistoryView: View { } public var body: some View { - NavigationStack { + NavigationStack( + path: $store.scope(state: \.path, action: \.path) + ) { DoriListView( store: store.scope(state: \.doriList, action: \.doriList) ) - .navigationDestination( - item: $store.scope(state: \.partnerHistory, action: \.partnerHistory) - ) { historyStore in + } destination: { store in + switch store.case { + case .partnerHistory(let historyStore): PartnerDoriHistoryView(store: historyStore) - } - .navigationDestination( - item: $store.scope(state: \.addDori, action: \.addDori) - ) { addDoriStore in + case .partnerDoriDetail(let detailStore): + PartnerDoriDetailView(store: detailStore) + case .addDori(let addDoriStore): AddDoriView(store: addDoriStore) - } - .navigationDestination( - item: $store.scope(state: \.editDori, action: \.editDori) - ) { editDoriStore in - NavigationStack { - EditDoriView(store: editDoriStore) - } + case .editDori(let editDoriStore): + EditDoriView(store: editDoriStore) } } } diff --git a/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryFeature.swift b/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryFeature.swift index ded4339..bbbfe9d 100644 --- a/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryFeature.swift +++ b/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryFeature.swift @@ -36,7 +36,6 @@ public struct PartnerDoriHistoryFeature { public var showDeleteAlert: Bool = false public var showFilterSheet: Bool = false public var toast: DoriToast? = nil - @Presents public var doriDetail: PartnerDoriDetailFeature.State? public var doriByDate: [(date: String, doris: [Dori])] { let filtered: [Dori] @@ -76,12 +75,10 @@ public struct PartnerDoriHistoryFeature { case doriTapped(Dori) case toastDismissed case setFilterSheet(Bool) - case doriDetail(PresentationAction) case delegate(Delegate) public enum Delegate: Equatable, Sendable { case allDoriDeleted - case editTapped(Dori) } } @@ -163,8 +160,8 @@ public struct PartnerDoriHistoryFeature { case .bulkDeleteResponse(.failure): return .none - case let .doriTapped(dori): - state.doriDetail = PartnerDoriDetailFeature.State(dori: dori) + case .doriTapped: + // 부모가 가로채서 push 처리 return .none case .toastDismissed: @@ -175,28 +172,9 @@ public struct PartnerDoriHistoryFeature { state.showFilterSheet = value return .none - // doriDetail 위임 처리 - case .doriDetail(.presented(.delegate(.editTapped(let dori)))): - return .send(.delegate(.editTapped(dori))) - - case .doriDetail(.presented(.delegate(.doriDeleted))): - let removedId = state.doriDetail?.doriId - state.doriDetail = nil - if let id = removedId { - state.inDoriList.removeAll { $0.doriId == id } - state.outDoriList.removeAll { $0.doriId == id } - } - return .none - - case .doriDetail: - return .none - case .delegate: return .none } } - .ifLet(\.$doriDetail, action: \.doriDetail) { - PartnerDoriDetailFeature() - } } } diff --git a/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryView.swift b/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryView.swift index b12372e..5c6bab7 100644 --- a/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryView.swift +++ b/Projects/Feature/History/Sources/PartnerDoriHistory/PartnerDoriHistoryView.swift @@ -100,11 +100,6 @@ public struct PartnerDoriHistoryView: View { } } } - .navigationDestination( - item: $store.scope(state: \.doriDetail, action: \.doriDetail) - ) { detailStore in - PartnerDoriDetailView(store: detailStore) - } .sheet( isPresented: Binding( get: { store.showFilterSheet },