From 7da041241d1704c530ab1be9d42a02003dd729d6 Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 29 Sep 2026 11:58:56 +0900 Subject: [PATCH 01/40] =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=8F=84=EA=B5=AC?= =?UTF-8?q?=20=EC=95=88=EB=82=B4=EC=97=90=20Sentry=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9D=84=20=EB=AA=85=EC=8B=9C=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b5cb0a6..b6aee3ed 100644 --- a/README.md +++ b/README.md @@ -662,7 +662,7 @@ SVG 생성에는 Graphviz의 `dot`이 필요합니다. 앱 빌드나 Tuist 캐 | 저장소 | PickeStorage, Keychain, SQLiteData | | 이미지 | SDWebImageSwiftUI, Kingfisher | | 모니터링 | Firebase Crashlytics, Sentry | -| 분석·광고 | Mixpanel, Google Mobile Ads, Kakao AdFit | +| 분석·광고 | Mixpanel, Sentry, Google Mobile Ads, Kakao AdFit | | 테스트 | Swift Testing, XCTest, Tuist | 패키지 선언과 실제 해석된 버전은 [Tuist/Package.swift](Tuist/Package.swift)와 [Tuist/Package.resolved](Tuist/Package.resolved)를 기준으로 합니다. From 958a25162818e1dbfcb2d7d8a1d8149ca7668d1b Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 30 Sep 2026 23:30:56 +0900 Subject: [PATCH 02/40] =?UTF-8?q?=EC=95=B1=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=BD=94=EB=94=94=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=9D=B4=EB=A6=84=EC=97=90=EC=84=9C=20App=20?= =?UTF-8?q?=EC=A0=91=EB=91=90=EC=82=AC=EB=A5=BC=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auth·Battle·Hifi·Home·Profile·MainTab 코디네이터와 뷰, Screen 이름을 접두사 없이 맞추고 파일명과 참조를 함께 변경한다. --- ...oordinator.swift => AuthCoordinator.swift} | 20 ++++++------- ...orView.swift => AuthCoordinatorView.swift} | 8 ++--- ...rdinator.swift => BattleCoordinator.swift} | 20 ++++++------- ...View.swift => BattleCoordinatorView.swift} | 8 ++--- ...oordinator.swift => HifiCoordinator.swift} | 20 ++++++------- ...orView.swift => HifiCoordinatorView.swift} | 8 ++--- ...oordinator.swift => HomeCoordinator.swift} | 20 ++++++------- ...orView.swift => HomeCoordinatorView.swift} | 8 ++--- ...dinator.swift => MainTabCoordinator.swift} | 30 +++++++++---------- ...AppMainTabView.swift => MainTabView.swift} | 30 +++++++++---------- ...dinator.swift => ProfileCoordinator.swift} | 20 ++++++------- ...iew.swift => ProfileCoordinatorView.swift} | 8 ++--- Projects/App/Sources/Reducer/AppReducer.swift | 22 +++++++------- Projects/App/Sources/View/AppView.swift | 4 +-- Projects/App/Sources/View/ContentView.swift | 4 +-- .../Sources/AppDeeplinkRoutingTests.swift | 4 +-- 16 files changed, 117 insertions(+), 117 deletions(-) rename Projects/App/Sources/Navigation/Auth/{AppAuthCoordinator.swift => AuthCoordinator.swift} (83%) rename Projects/App/Sources/Navigation/Auth/{AppAuthCoordinatorView.swift => AuthCoordinatorView.swift} (77%) rename Projects/App/Sources/Navigation/Battle/{AppBattleCoordinator.swift => BattleCoordinator.swift} (87%) rename Projects/App/Sources/Navigation/Battle/{AppBattleCoordinatorView.swift => BattleCoordinatorView.swift} (75%) rename Projects/App/Sources/Navigation/Hifi/{AppHifiCoordinator.swift => HifiCoordinator.swift} (86%) rename Projects/App/Sources/Navigation/Hifi/{AppHifiCoordinatorView.swift => HifiCoordinatorView.swift} (77%) rename Projects/App/Sources/Navigation/Home/{AppHomeCoordinator.swift => HomeCoordinator.swift} (89%) rename Projects/App/Sources/Navigation/Home/{AppHomeCoordinatorView.swift => HomeCoordinatorView.swift} (78%) rename Projects/App/Sources/Navigation/MainTab/{AppMainTabCoordinator.swift => MainTabCoordinator.swift} (87%) rename Projects/App/Sources/Navigation/MainTab/{AppMainTabView.swift => MainTabView.swift} (85%) rename Projects/App/Sources/Navigation/Profile/{AppProfileCoordinator.swift => ProfileCoordinator.swift} (93%) rename Projects/App/Sources/Navigation/Profile/{AppProfileCoordinatorView.swift => ProfileCoordinatorView.swift} (89%) diff --git a/Projects/App/Sources/Navigation/Auth/AppAuthCoordinator.swift b/Projects/App/Sources/Navigation/Auth/AuthCoordinator.swift similarity index 83% rename from Projects/App/Sources/Navigation/Auth/AppAuthCoordinator.swift rename to Projects/App/Sources/Navigation/Auth/AuthCoordinator.swift index de517976..0b14f353 100644 --- a/Projects/App/Sources/Navigation/Auth/AppAuthCoordinator.swift +++ b/Projects/App/Sources/Navigation/Auth/AuthCoordinator.swift @@ -1,5 +1,5 @@ // -// AppAuthCoordinator.swift +// AuthCoordinator.swift // Picke // @@ -9,13 +9,13 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -@FlowCoordinator(screen: "AppAuthScreen", navigation: true) -public struct AppAuthCoordinator { +@FlowCoordinator(screen: "AuthScreen", navigation: true) +public struct AuthCoordinator { public init() {} @ObservableState public struct State: Equatable { - var routes: [Route] + var routes: [Route] public init() { routes = [.root(.login(.init()), embedInNavigationView: true)] @@ -24,7 +24,7 @@ public struct AppAuthCoordinator { @CasePathable public enum Action { - case router(IndexedRouterActionOf) + case router(IndexedRouterActionOf) case view(View) case navigation(NavigationAction) } @@ -56,10 +56,10 @@ public struct AppAuthCoordinator { } } -private extension AppAuthCoordinator { +private extension AuthCoordinator { func routerAction( state: inout State, - action: IndexedRouterActionOf + action: IndexedRouterActionOf ) -> Effect { switch action { case .routeAction(_, action: .login(.delegate(.presentOnboarding))): @@ -101,9 +101,9 @@ private extension AppAuthCoordinator { } // swiftformat:disable extensionAccessControl -extension AppAuthCoordinator { +extension AuthCoordinator { @Reducer - public enum AppAuthScreen { + public enum AuthScreen { case login(LoginFeature) case onboarding(OnBoardingFeature) case web(WebReducer) @@ -112,4 +112,4 @@ extension AppAuthCoordinator { // swiftformat:enable extensionAccessControl -extension AppAuthCoordinator.AppAuthScreen.State: Equatable {} +extension AuthCoordinator.AuthScreen.State: Equatable {} diff --git a/Projects/App/Sources/Navigation/Auth/AppAuthCoordinatorView.swift b/Projects/App/Sources/Navigation/Auth/AuthCoordinatorView.swift similarity index 77% rename from Projects/App/Sources/Navigation/Auth/AppAuthCoordinatorView.swift rename to Projects/App/Sources/Navigation/Auth/AuthCoordinatorView.swift index 88df83b3..ec6fc0bc 100644 --- a/Projects/App/Sources/Navigation/Auth/AppAuthCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Auth/AuthCoordinatorView.swift @@ -1,5 +1,5 @@ // -// AppAuthCoordinatorView.swift +// AuthCoordinatorView.swift // Picke // @@ -9,10 +9,10 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -public struct AppAuthCoordinatorView: View { - @Bindable private var store: StoreOf +public struct AuthCoordinatorView: View { + @Bindable private var store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } diff --git a/Projects/App/Sources/Navigation/Battle/AppBattleCoordinator.swift b/Projects/App/Sources/Navigation/Battle/BattleCoordinator.swift similarity index 87% rename from Projects/App/Sources/Navigation/Battle/AppBattleCoordinator.swift rename to Projects/App/Sources/Navigation/Battle/BattleCoordinator.swift index 79218520..9094a10f 100644 --- a/Projects/App/Sources/Navigation/Battle/AppBattleCoordinator.swift +++ b/Projects/App/Sources/Navigation/Battle/BattleCoordinator.swift @@ -1,5 +1,5 @@ // -// AppBattleCoordinator.swift +// BattleCoordinator.swift // Picke // @@ -12,15 +12,15 @@ import PickeDesignKit import FeatureAssembly import TCAFlow -@FlowCoordinator(screen: "AppBattleScreen", navigation: true) -public struct AppBattleCoordinator { +@FlowCoordinator(screen: "BattleScreen", navigation: true) +public struct BattleCoordinator { public init() {} @Dependency(\.audioPlayer) private var audioPlayer @ObservableState public struct State: Equatable { - public var routes: [Route] + public var routes: [Route] public init() { routes = [.root(.battle(.init()), embedInNavigationView: true)] @@ -29,7 +29,7 @@ public struct AppBattleCoordinator { @CasePathable public enum Action { - case router(IndexedRouterActionOf) + case router(IndexedRouterActionOf) case view(View) case async(AsyncAction) case inner(InnerAction) @@ -61,10 +61,10 @@ public struct AppBattleCoordinator { } } -private extension AppBattleCoordinator { +private extension BattleCoordinator { func routerAction( state: inout State, - action: IndexedRouterActionOf + action: IndexedRouterActionOf ) -> Effect { switch action { case let .routeAction(_, action: .battle(.delegate(.openBattle(battleId)))): @@ -127,9 +127,9 @@ private extension AppBattleCoordinator { } // swiftformat:disable extensionAccessControl -extension AppBattleCoordinator { +extension BattleCoordinator { @Reducer - public enum AppBattleScreen { + public enum BattleScreen { case battle(BattleFeature) case chatRoom(ChatRoomFeature) case chat(ChatCoordinator) @@ -138,4 +138,4 @@ extension AppBattleCoordinator { // swiftformat:enable extensionAccessControl -extension AppBattleCoordinator.AppBattleScreen.State: Equatable {} +extension BattleCoordinator.BattleScreen.State: Equatable {} diff --git a/Projects/App/Sources/Navigation/Battle/AppBattleCoordinatorView.swift b/Projects/App/Sources/Navigation/Battle/BattleCoordinatorView.swift similarity index 75% rename from Projects/App/Sources/Navigation/Battle/AppBattleCoordinatorView.swift rename to Projects/App/Sources/Navigation/Battle/BattleCoordinatorView.swift index 0268a21d..2d289c94 100644 --- a/Projects/App/Sources/Navigation/Battle/AppBattleCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Battle/BattleCoordinatorView.swift @@ -1,5 +1,5 @@ // -// AppBattleCoordinatorView.swift +// BattleCoordinatorView.swift // Picke // @@ -9,10 +9,10 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -public struct AppBattleCoordinatorView: View { - @Bindable private var store: StoreOf +public struct BattleCoordinatorView: View { + @Bindable private var store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } diff --git a/Projects/App/Sources/Navigation/Hifi/AppHifiCoordinator.swift b/Projects/App/Sources/Navigation/Hifi/HifiCoordinator.swift similarity index 86% rename from Projects/App/Sources/Navigation/Hifi/AppHifiCoordinator.swift rename to Projects/App/Sources/Navigation/Hifi/HifiCoordinator.swift index 85ac3a53..af7d1567 100644 --- a/Projects/App/Sources/Navigation/Hifi/AppHifiCoordinator.swift +++ b/Projects/App/Sources/Navigation/Hifi/HifiCoordinator.swift @@ -1,5 +1,5 @@ // -// AppHifiCoordinator.swift +// HifiCoordinator.swift // Picke // @@ -10,15 +10,15 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -@FlowCoordinator(screen: "AppHifiScreen", navigation: true) -public struct AppHifiCoordinator { +@FlowCoordinator(screen: "HifiScreen", navigation: true) +public struct HifiCoordinator { public init() {} @Dependency(\.audioPlayer) private var audioPlayer @ObservableState public struct State: Equatable { - public var routes: [Route] + public var routes: [Route] public init() { routes = [.root(.hifi(.init()), embedInNavigationView: true)] @@ -27,7 +27,7 @@ public struct AppHifiCoordinator { @CasePathable public enum Action { - case router(IndexedRouterActionOf) + case router(IndexedRouterActionOf) case view(View) case async(AsyncAction) case inner(InnerAction) @@ -59,10 +59,10 @@ public struct AppHifiCoordinator { } } -private extension AppHifiCoordinator { +private extension HifiCoordinator { func routerAction( state: inout State, - action: IndexedRouterActionOf + action: IndexedRouterActionOf ) -> Effect { switch action { case let .routeAction(_, action: .hifi(.delegate(.openBattle(battleId)))): @@ -116,9 +116,9 @@ private extension AppHifiCoordinator { } // swiftformat:disable extensionAccessControl -extension AppHifiCoordinator { +extension HifiCoordinator { @Reducer - public enum AppHifiScreen { + public enum HifiScreen { case hifi(HifiFeature) case chat(ChatCoordinator) case notification(NotificationCoordinator) @@ -127,4 +127,4 @@ extension AppHifiCoordinator { // swiftformat:enable extensionAccessControl -extension AppHifiCoordinator.AppHifiScreen.State: Equatable {} +extension HifiCoordinator.HifiScreen.State: Equatable {} diff --git a/Projects/App/Sources/Navigation/Hifi/AppHifiCoordinatorView.swift b/Projects/App/Sources/Navigation/Hifi/HifiCoordinatorView.swift similarity index 77% rename from Projects/App/Sources/Navigation/Hifi/AppHifiCoordinatorView.swift rename to Projects/App/Sources/Navigation/Hifi/HifiCoordinatorView.swift index e6abcf9e..e6c19b88 100644 --- a/Projects/App/Sources/Navigation/Hifi/AppHifiCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Hifi/HifiCoordinatorView.swift @@ -1,5 +1,5 @@ // -// AppHifiCoordinatorView.swift +// HifiCoordinatorView.swift // Picke // @@ -9,10 +9,10 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -public struct AppHifiCoordinatorView: View { - @Bindable private var store: StoreOf +public struct HifiCoordinatorView: View { + @Bindable private var store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } diff --git a/Projects/App/Sources/Navigation/Home/AppHomeCoordinator.swift b/Projects/App/Sources/Navigation/Home/HomeCoordinator.swift similarity index 89% rename from Projects/App/Sources/Navigation/Home/AppHomeCoordinator.swift rename to Projects/App/Sources/Navigation/Home/HomeCoordinator.swift index 0c869511..4ea4a512 100644 --- a/Projects/App/Sources/Navigation/Home/AppHomeCoordinator.swift +++ b/Projects/App/Sources/Navigation/Home/HomeCoordinator.swift @@ -1,5 +1,5 @@ // -// AppHomeCoordinator.swift +// HomeCoordinator.swift // Picke // @@ -12,15 +12,15 @@ import PickeDesignKit import FeatureAssembly import TCAFlow -@FlowCoordinator(screen: "AppHomeScreen", navigation: true) -public struct AppHomeCoordinator { +@FlowCoordinator(screen: "HomeScreen", navigation: true) +public struct HomeCoordinator { public init() {} @Dependency(\.audioPlayer) private var audioPlayer @ObservableState public struct State: Equatable { - public var routes: [Route] + public var routes: [Route] public init() { routes = [.root(.home(.init()), embedInNavigationView: true)] @@ -29,7 +29,7 @@ public struct AppHomeCoordinator { @CasePathable public enum Action { - case router(IndexedRouterActionOf) + case router(IndexedRouterActionOf) case view(View) case async(AsyncAction) case inner(InnerAction) @@ -63,10 +63,10 @@ public struct AppHomeCoordinator { } } -private extension AppHomeCoordinator { +private extension HomeCoordinator { func routerAction( state: inout State, - action: IndexedRouterActionOf + action: IndexedRouterActionOf ) -> Effect { switch action { case let .routeAction(_, action: .home(.delegate(.presentPreVote(battleId)))): @@ -140,9 +140,9 @@ private extension AppHomeCoordinator { } // swiftformat:disable extensionAccessControl -extension AppHomeCoordinator { +extension HomeCoordinator { @Reducer - public enum AppHomeScreen { + public enum HomeScreen { case home(HomeFeature) case chat(ChatCoordinator) case notification(NotificationCoordinator) @@ -151,4 +151,4 @@ extension AppHomeCoordinator { // swiftformat:enable extensionAccessControl -extension AppHomeCoordinator.AppHomeScreen.State: Equatable {} +extension HomeCoordinator.HomeScreen.State: Equatable {} diff --git a/Projects/App/Sources/Navigation/Home/AppHomeCoordinatorView.swift b/Projects/App/Sources/Navigation/Home/HomeCoordinatorView.swift similarity index 78% rename from Projects/App/Sources/Navigation/Home/AppHomeCoordinatorView.swift rename to Projects/App/Sources/Navigation/Home/HomeCoordinatorView.swift index c53e5ec5..24daee09 100644 --- a/Projects/App/Sources/Navigation/Home/AppHomeCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Home/HomeCoordinatorView.swift @@ -1,5 +1,5 @@ // -// AppHomeCoordinatorView.swift +// HomeCoordinatorView.swift // Picke // @@ -9,10 +9,10 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -public struct AppHomeCoordinatorView: View { - @Bindable private var store: StoreOf +public struct HomeCoordinatorView: View { + @Bindable private var store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } diff --git a/Projects/App/Sources/Navigation/MainTab/AppMainTabCoordinator.swift b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift similarity index 87% rename from Projects/App/Sources/Navigation/MainTab/AppMainTabCoordinator.swift rename to Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift index 808c52d9..fcaea202 100644 --- a/Projects/App/Sources/Navigation/MainTab/AppMainTabCoordinator.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift @@ -1,5 +1,5 @@ // -// AppMainTabCoordinator.swift +// MainTabCoordinator.swift // Picke // // Created by Codex on 7/11/26. @@ -14,7 +14,7 @@ import FeatureAssembly import TCAFlow @Reducer -public struct AppMainTabCoordinator { +public struct MainTabCoordinator { public init() {} public enum Tab: Int, CaseIterable { @@ -46,10 +46,10 @@ public struct AppMainTabCoordinator { public struct State: Equatable { public var selectedTab: Int public var previousTab: Int = Tab.home.rawValue - public var homeState: AppHomeCoordinator.State - public var exploreState: AppHifiCoordinator.State - public var quickBattleState: AppBattleCoordinator.State - public var myPageState: AppProfileCoordinator.State + public var homeState: HomeCoordinator.State + public var exploreState: HifiCoordinator.State + public var quickBattleState: BattleCoordinator.State + public var myPageState: ProfileCoordinator.State public var shouldHideTabBar: Bool { switch Tab(rawValue: selectedTab) { @@ -79,10 +79,10 @@ public struct AppMainTabCoordinator { public enum Action { case selectTab(Int) case tabReselected(Int) - case home(AppHomeCoordinator.Action) - case explore(AppHifiCoordinator.Action) - case quickBattle(AppBattleCoordinator.Action) - case myPage(AppProfileCoordinator.Action) + case home(HomeCoordinator.Action) + case explore(HifiCoordinator.Action) + case quickBattle(BattleCoordinator.Action) + case myPage(ProfileCoordinator.Action) case delegate(DelegateAction) } @@ -94,16 +94,16 @@ public struct AppMainTabCoordinator { public var body: some ReducerOf { Scope(state: \.homeState, action: \.home) { - AppHomeCoordinator() + HomeCoordinator() } Scope(state: \.exploreState, action: \.explore) { - AppHifiCoordinator() + HifiCoordinator() } Scope(state: \.quickBattleState, action: \.quickBattle) { - AppBattleCoordinator() + BattleCoordinator() } Scope(state: \.myPageState, action: \.myPage) { - AppProfileCoordinator() + ProfileCoordinator() } Reduce { state, action in @@ -151,7 +151,7 @@ public struct AppMainTabCoordinator { } } -private extension AppMainTabCoordinator { +private extension MainTabCoordinator { func trackTabSelection(_ tab: Int) { switch Tab(rawValue: tab) { case .home: diff --git a/Projects/App/Sources/Navigation/MainTab/AppMainTabView.swift b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift similarity index 85% rename from Projects/App/Sources/Navigation/MainTab/AppMainTabView.swift rename to Projects/App/Sources/Navigation/MainTab/MainTabView.swift index b0df67c9..5bd338f7 100644 --- a/Projects/App/Sources/Navigation/MainTab/AppMainTabView.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift @@ -1,5 +1,5 @@ // -// AppMainTabView.swift +// MainTabView.swift // Picke // // Created by Codex on 7/11/26. @@ -14,10 +14,10 @@ import PickeDesignKit import FeatureAssembly import TCAFlow -public struct AppMainTabView: View { - @Bindable private var store: StoreOf +public struct MainTabView: View { + @Bindable private var store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { Self.configureTabBarAppearance() self.store = store } @@ -25,7 +25,7 @@ public struct AppMainTabView: View { public var body: some View { TCAFlowTabRouter( selectedTab: $store.selectedTab.sending(\.selectTab), - tabs: AppMainTabCoordinator.Tab.allCases.map { + tabs: MainTabCoordinator.Tab.allCases.map { TabItem( title: $0.title, icon: $0.iconAsset(isSelected: false).rawValue, @@ -46,7 +46,7 @@ public struct AppMainTabView: View { } } -private extension AppMainTabView { +private extension MainTabView { static func configureTabBarAppearance() { let selectedColor = UIColor.neutral900 let normalColor = UIColor.gray200 @@ -118,7 +118,7 @@ private extension AppMainTabView { @ViewBuilder func tabIcon(for tab: TabItem) -> some View { let isSelected = store.selectedTab == tab.tag - let asset = AppMainTabCoordinator.Tab(rawValue: tab.tag)? + let asset = MainTabCoordinator.Tab(rawValue: tab.tag)? .iconAsset(isSelected: isSelected) ?? .none Image(asset: asset) @@ -132,31 +132,31 @@ private extension AppMainTabView { // 각 탭이 처음 화면에 올라올 때까지 스토어 스코프와 뷰 생성을 미룬다. @ViewBuilder func tabContent(for tab: Int) -> some View { - switch AppMainTabCoordinator.Tab(rawValue: tab) { + switch MainTabCoordinator.Tab(rawValue: tab) { case .home: LazyView { - AppHomeCoordinatorView( + HomeCoordinatorView( store: store.scope(state: \.homeState, action: \.home) ) } case .explore: LazyView { - AppHifiCoordinatorView( + HifiCoordinatorView( store: store.scope(state: \.exploreState, action: \.explore) ) } case .quickBattle: LazyView { - AppBattleCoordinatorView( + BattleCoordinatorView( store: store.scope(state: \.quickBattleState, action: \.quickBattle) ) } case .myPage: LazyView { - AppProfileCoordinatorView( + ProfileCoordinatorView( store: store.scope(state: \.myPageState, action: \.myPage) ) } @@ -168,9 +168,9 @@ private extension AppMainTabView { } #Preview { - AppMainTabView( - store: Store(initialState: AppMainTabCoordinator.State()) { - AppMainTabCoordinator() + MainTabView( + store: Store(initialState: MainTabCoordinator.State()) { + MainTabCoordinator() } ) } diff --git a/Projects/App/Sources/Navigation/Profile/AppProfileCoordinator.swift b/Projects/App/Sources/Navigation/Profile/ProfileCoordinator.swift similarity index 93% rename from Projects/App/Sources/Navigation/Profile/AppProfileCoordinator.swift rename to Projects/App/Sources/Navigation/Profile/ProfileCoordinator.swift index 4dafc6e1..cac37db0 100644 --- a/Projects/App/Sources/Navigation/Profile/AppProfileCoordinator.swift +++ b/Projects/App/Sources/Navigation/Profile/ProfileCoordinator.swift @@ -1,5 +1,5 @@ // -// AppProfileCoordinator.swift +// ProfileCoordinator.swift // Picke // @@ -11,15 +11,15 @@ import DomainAssembly import FeatureAssembly import TCAFlow -@FlowCoordinator(screen: "AppProfileScreen", navigation: true) -public struct AppProfileCoordinator { +@FlowCoordinator(screen: "ProfileScreen", navigation: true) +public struct ProfileCoordinator { public init() {} @Dependency(\.audioPlayer) private var audioPlayer @ObservableState public struct State: Equatable { - public var routes: [Route] + public var routes: [Route] public init() { routes = [.root(.profile(.init()), embedInNavigationView: true)] @@ -28,7 +28,7 @@ public struct AppProfileCoordinator { @CasePathable public enum Action { - case router(IndexedRouterActionOf) + case router(IndexedRouterActionOf) case view(View) case async(AsyncAction) case inner(InnerAction) @@ -62,10 +62,10 @@ public struct AppProfileCoordinator { } } -private extension AppProfileCoordinator { +private extension ProfileCoordinator { func routerAction( state: inout State, - action: IndexedRouterActionOf + action: IndexedRouterActionOf ) -> Effect { switch action { case .routeAction(_, action: .profile(.delegate(.editProfile))): @@ -207,9 +207,9 @@ private extension AppProfileCoordinator { } // swiftformat:disable extensionAccessControl -extension AppProfileCoordinator { +extension ProfileCoordinator { @Reducer - public enum AppProfileScreen { + public enum ProfileScreen { case profile(ProfileFeature) case pointHistory(PointHistoryFeature) case settings(SettingsFeature) @@ -228,4 +228,4 @@ extension AppProfileCoordinator { // swiftformat:enable extensionAccessControl -extension AppProfileCoordinator.AppProfileScreen.State: Equatable {} +extension ProfileCoordinator.ProfileScreen.State: Equatable {} diff --git a/Projects/App/Sources/Navigation/Profile/AppProfileCoordinatorView.swift b/Projects/App/Sources/Navigation/Profile/ProfileCoordinatorView.swift similarity index 89% rename from Projects/App/Sources/Navigation/Profile/AppProfileCoordinatorView.swift rename to Projects/App/Sources/Navigation/Profile/ProfileCoordinatorView.swift index 5c9a37f6..a3ae939c 100644 --- a/Projects/App/Sources/Navigation/Profile/AppProfileCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Profile/ProfileCoordinatorView.swift @@ -1,5 +1,5 @@ // -// AppProfileCoordinatorView.swift +// ProfileCoordinatorView.swift // Picke // @@ -9,10 +9,10 @@ import ComposableArchitecture import FeatureAssembly import TCAFlow -public struct AppProfileCoordinatorView: View { - @Bindable private var store: StoreOf +public struct ProfileCoordinatorView: View { + @Bindable private var store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } diff --git a/Projects/App/Sources/Reducer/AppReducer.swift b/Projects/App/Sources/Reducer/AppReducer.swift index 4c84a299..c8548448 100644 --- a/Projects/App/Sources/Reducer/AppReducer.swift +++ b/Projects/App/Sources/Reducer/AppReducer.swift @@ -21,8 +21,8 @@ public struct AppReducer: Sendable { @ObservableState public enum State: Equatable { case splash(SplashFeature.State) - case auth(AppAuthCoordinator.State) - case mainTab(AppMainTabCoordinator.State) + case auth(AuthCoordinator.State) + case mainTab(MainTabCoordinator.State) public init() { self = .splash(SplashFeature.State()) @@ -79,8 +79,8 @@ public struct AppReducer: Sendable { @CasePathable public enum ScopeAction { case splash(SplashFeature.Action) - case auth(AppAuthCoordinator.Action) - case mainTab(AppMainTabCoordinator.Action) + case auth(AuthCoordinator.Action) + case mainTab(MainTabCoordinator.Action) } @Dependency(\.continuousClock) var clock @@ -144,10 +144,10 @@ public struct AppReducer: Sendable { SplashFeature() } .ifCaseLet(\.auth, action: \.scope.auth) { - AppAuthCoordinator() + AuthCoordinator() } .ifCaseLet(\.mainTab, action: \.scope.mainTab) { - AppMainTabCoordinator() + MainTabCoordinator() } } @@ -207,30 +207,30 @@ public struct AppReducer: Sendable { case let .battle(battleId): // 홈 탭 전환 후 HomeCoordinator(Chat 보유)가 배틀 상세 push. return .merge( - .send(.scope(.mainTab(.selectTab(AppMainTabCoordinator.Tab.home.rawValue)))), + .send(.scope(.mainTab(.selectTab(MainTabCoordinator.Tab.home.rawValue)))), .send(.scope(.mainTab(.home(.view(.openBattle(battleId: battleId)))))) ) case let .perspective(perspectiveId, commentId): // 홈 탭 전환 후 HomeCoordinator(Chat 보유)가 관점(답글) 화면 push. return .merge( - .send(.scope(.mainTab(.selectTab(AppMainTabCoordinator.Tab.home.rawValue)))), + .send(.scope(.mainTab(.selectTab(MainTabCoordinator.Tab.home.rawValue)))), .send(.scope(.mainTab(.home(.view(.openPerspective(perspectiveId: perspectiveId, commentId: commentId)))))) ) case .point: // 마이페이지 탭 전환 후 포인트 내역 push. return .merge( - .send(.scope(.mainTab(.selectTab(AppMainTabCoordinator.Tab.myPage.rawValue)))), + .send(.scope(.mainTab(.selectTab(MainTabCoordinator.Tab.myPage.rawValue)))), .send(.scope(.mainTab(.myPage(.view(.openPointHistory))))) ) case .terms: // 마이페이지 탭 전환 후 서비스 약관 웹뷰 push. return .merge( - .send(.scope(.mainTab(.selectTab(AppMainTabCoordinator.Tab.myPage.rawValue)))), + .send(.scope(.mainTab(.selectTab(MainTabCoordinator.Tab.myPage.rawValue)))), .send(.scope(.mainTab(.myPage(.view(.openTerms))))) ) case .quickBattle: // 빠른 배틀은 탭 자체가 목적지라 전환만 한다. - return .send(.scope(.mainTab(.selectTab(AppMainTabCoordinator.Tab.quickBattle.rawValue)))) + return .send(.scope(.mainTab(.selectTab(MainTabCoordinator.Tab.quickBattle.rawValue)))) } } } diff --git a/Projects/App/Sources/View/AppView.swift b/Projects/App/Sources/View/AppView.swift index f957788e..79731deb 100644 --- a/Projects/App/Sources/View/AppView.swift +++ b/Projects/App/Sources/View/AppView.swift @@ -31,7 +31,7 @@ struct AppView: View { case .auth: if let store = store.scope(state: \.auth, action: \.scope.auth) { - AppAuthCoordinatorView(store: store) + AuthCoordinatorView(store: store) .transition(.asymmetric( insertion: .move(edge: .trailing), removal: .move(edge: .leading) @@ -40,7 +40,7 @@ struct AppView: View { case .mainTab: if let store = store.scope(state: \.mainTab, action: \.scope.mainTab) { - AppMainTabView(store: store) + MainTabView(store: store) .transition(.asymmetric( insertion: .move(edge: .trailing), removal: .move(edge: .leading) diff --git a/Projects/App/Sources/View/ContentView.swift b/Projects/App/Sources/View/ContentView.swift index c4eca408..3be00843 100644 --- a/Projects/App/Sources/View/ContentView.swift +++ b/Projects/App/Sources/View/ContentView.swift @@ -15,7 +15,7 @@ public struct ContentView: View { } #Preview { - AppAuthCoordinatorView(store: .init(initialState: AppAuthCoordinator.State(), reducer: { - AppAuthCoordinator() + AuthCoordinatorView(store: .init(initialState: AuthCoordinator.State(), reducer: { + AuthCoordinator() })) } diff --git a/Projects/App/Tests/Sources/AppDeeplinkRoutingTests.swift b/Projects/App/Tests/Sources/AppDeeplinkRoutingTests.swift index 980f9e14..af2397ec 100644 --- a/Projects/App/Tests/Sources/AppDeeplinkRoutingTests.swift +++ b/Projects/App/Tests/Sources/AppDeeplinkRoutingTests.swift @@ -37,13 +37,13 @@ struct AppDeeplinkRoutingTests { } await store.receive { guard case let .scope(.mainTab(.selectTab(tab))) = $0 else { return false } - return tab == AppMainTabCoordinator.Tab.quickBattle.rawValue + return tab == MainTabCoordinator.Tab.quickBattle.rawValue } guard case let .mainTab(state) = store.state else { Issue.record("메인 화면으로 전환되지 않음") return } - #expect(state.selectedTab == AppMainTabCoordinator.Tab.quickBattle.rawValue) + #expect(state.selectedTab == MainTabCoordinator.Tab.quickBattle.rawValue) #expect(pending == nil) await store.send(.async(.consumePendingDeeplink)) await store.finish() From e2095f606aaded91c692415ac8b60007c36e346b Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 30 Sep 2026 23:46:15 +0900 Subject: [PATCH 03/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9D=B4=20=EA=B3=B5=ED=86=B5=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B3=84=EC=95=BD=EC=9D=84=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rejected: 확인되지 않은 API 계약을 추측해 연결하기 | 서버 계약이 바뀌면 재작업이 발생한다. --- .../TargetDependency+Module/Modules.swift | 2 + .../Sources/Entity/ClassModels.swift | 129 ++++++++++++++ .../Sources/Repository/ClassInterface.swift | 34 ++++ .../Default/MockClassRepository.swift | 158 ++++++++++++++++++ Projects/Domain/ClassDomain/Project.swift | 19 +++ .../Sources/ClassLiveDependencies.swift | 14 ++ .../Exported/ClassDomainExported.swift | 1 + .../Repository/ClassRepositoryImpl.swift | 28 ++++ .../Sources/UseCase/ClassUseCase.swift | 29 ++++ .../Tests/Sources/ClassDomainTests.swift | 51 ++++++ Projects/Domain/DomainAssembly/Project.swift | 1 + .../Exported/DomainAssemblyExported.swift | 6 +- .../Sources/DomainAssemblyExportTests.swift | 3 +- 13 files changed, 472 insertions(+), 3 deletions(-) create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift create mode 100644 Projects/Domain/ClassDomain/Project.swift create mode 100644 Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift create mode 100644 Projects/Domain/ClassDomain/Sources/Exported/ClassDomainExported.swift create mode 100644 Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift create mode 100644 Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift create mode 100644 Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/TargetDependency+Module/Modules.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/TargetDependency+Module/Modules.swift index df0657ac..a5bafcef 100644 --- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/TargetDependency+Module/Modules.swift +++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/TargetDependency+Module/Modules.swift @@ -16,6 +16,7 @@ public enum FeatureModule: String, CaseIterable { case hifi = "Hifi" case web = "Web" case battle = "Battle" + case classroom = "Class" case profile = "Profile" case notification = "Notification" case ad = "Ad" @@ -64,6 +65,7 @@ public enum DomainModule: String, CaseIterable { case attendance = "AttendanceDomain" case auth = "AuthDomain" case battle = "BattleDomain" + case classroom = "ClassDomain" case comment = "CommentDomain" case ad = "AdDomain" case home = "HomeDomain" diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift new file mode 100644 index 00000000..f03f51b0 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift @@ -0,0 +1,129 @@ +import Foundation + +public enum ClassRole: String, Equatable, Sendable { + case owner + case member +} + +public enum ClassStatus: String, Equatable, Sendable { + case open + case closed +} + +public struct ClassRoom: Equatable, Identifiable, Sendable { + public let id: Int + public let name: String + public let joinCode: String + public let battleId: Int + public let battleTitle: String + public let deadline: Date + public let memberCount: Int + public let role: ClassRole + public let status: ClassStatus + public let isVoteEnabled: Bool + public let requiresComment: Bool + + public init( + id: Int, + name: String, + joinCode: String, + battleId: Int, + battleTitle: String, + deadline: Date, + memberCount: Int, + role: ClassRole, + status: ClassStatus, + isVoteEnabled: Bool, + requiresComment: Bool + ) { + self.id = id + self.name = name + self.joinCode = joinCode + self.battleId = battleId + self.battleTitle = battleTitle + self.deadline = deadline + self.memberCount = memberCount + self.role = role + self.status = status + self.isVoteEnabled = isVoteEnabled + self.requiresComment = requiresComment + } +} + +public struct ClassTopicFilter: Equatable, Sendable { + public var keyword: String + public var level: String? + public var category: String? + + public init( + keyword: String = "", + level: String? = nil, + category: String? = nil + ) { + self.keyword = keyword + self.level = level + self.category = category + } +} + +public struct ClassBattleSummary: Equatable, Identifiable, Sendable { + public let id: Int + public let title: String + public let summary: String + public let category: String + public let thumbnailURL: URL? + + public init( + id: Int, + title: String, + summary: String, + category: String, + thumbnailURL: URL? + ) { + self.id = id + self.title = title + self.summary = summary + self.category = category + self.thumbnailURL = thumbnailURL + } +} + +public struct ClassCreation: Equatable, Sendable { + public var name: String + public var deadline: Date + public var battleId: Int + public var isVoteEnabled: Bool + public var requiresComment: Bool + + public init( + name: String, + deadline: Date, + battleId: Int, + isVoteEnabled: Bool, + requiresComment: Bool + ) { + self.name = name + self.deadline = deadline + self.battleId = battleId + self.isVoteEnabled = isVoteEnabled + self.requiresComment = requiresComment + } +} + +public enum ClassError: Error, Equatable, Sendable { + case invalidCode + case closed + case alreadyJoined + case network(String) + case unknown(String) + + public static func from(_ error: Error) -> ClassError { + if let error = error as? ClassError { + return error + } + if let error = error as? URLError { + return .network(error.localizedDescription) + } + return .unknown(String(describing: error)) + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift new file mode 100644 index 00000000..75f82ca4 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift @@ -0,0 +1,34 @@ +import ComposableArchitecture +import Foundation + +public protocol ClassInterface: Sendable { + func fetchMyClasses() async throws -> [ClassRoom] + func fetchRecommendedBattles(filter: ClassTopicFilter) async throws -> [ClassBattleSummary] + func createClass(_ creation: ClassCreation) async throws -> ClassRoom + func fetchClass(joinCode: String) async throws -> ClassRoom + func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom +} + +public enum ClassRepositoryDependency: TestDependencyKey { + public static var testValue: any ClassInterface { + MockClassRepository() + } +} + +public enum ClassUseCaseDependency: TestDependencyKey { + public static var testValue: any ClassInterface { + MockClassRepository() + } +} + +public extension DependencyValues { + var classRepository: any ClassInterface { + get { self[ClassRepositoryDependency.self] } + set { self[ClassRepositoryDependency.self] = newValue } + } + + var classUseCase: any ClassInterface { + get { self[ClassUseCaseDependency.self] } + set { self[ClassUseCaseDependency.self] = newValue } + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift new file mode 100644 index 00000000..a68e0863 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift @@ -0,0 +1,158 @@ +import Foundation + +public extension ClassRoom { + static let mocks: [ClassRoom] = [ + ClassRoom( + id: 1, + name: "1학년 3반 사회 토론", + joinCode: "PK7M2Q", + battleId: 101, + battleTitle: "촉법소년 연령을 낮춰야 할까?", + deadline: Date(timeIntervalSince1970: 1_799_000_000), + memberCount: 20, + role: .owner, + status: .open, + isVoteEnabled: true, + requiresComment: true + ), + ClassRoom( + id: 2, + name: "함께 생각하는 미술 수업", + joinCode: "PK4B8R", + battleId: 102, + battleTitle: "뒤샹의 변기, 예술인가 도발인가", + deadline: Date(timeIntervalSince1970: 1_799_000_000), + memberCount: 18, + role: .member, + status: .open, + isVoteEnabled: true, + requiresComment: false + ), + ] + + static let mockJoinable = ClassRoom( + id: 3, + name: "철학 토론 수업", + joinCode: "PK9T3S", + battleId: 101, + battleTitle: "촉법소년 연령을 낮춰야 할까?", + deadline: Date(timeIntervalSince1970: 1_799_000_000), + memberCount: 12, + role: .member, + status: .open, + isVoteEnabled: true, + requiresComment: true + ) +} + +public extension ClassBattleSummary { + static let mocks: [ClassBattleSummary] = [ + ClassBattleSummary( + id: 101, + title: "촉법소년 연령을 낮춰야 할까?", + summary: "청소년 범죄와 보호의 균형을 이야기해요.", + category: "사회", + thumbnailURL: nil + ), + ClassBattleSummary( + id: 102, + title: "뒤샹의 변기, 예술인가 도발인가", + summary: "예술의 경계를 함께 생각해요.", + category: "예술", + thumbnailURL: nil + ), + ] +} + +public actor MockClassRepository: ClassInterface { + private var rooms: [ClassRoom] + private var availableRooms: [ClassRoom] + private var nextID: Int + + public init( + rooms: [ClassRoom] = ClassRoom.mocks, + availableRooms: [ClassRoom] = [ClassRoom.mockJoinable] + ) { + self.rooms = rooms + self.availableRooms = availableRooms + nextID = (rooms + availableRooms).map(\.id).max().map { $0 + 1 } ?? 1 + } + + public func fetchMyClasses() async throws -> [ClassRoom] { + rooms + } + + public func fetchRecommendedBattles(filter: ClassTopicFilter) async throws -> [ClassBattleSummary] { + ClassBattleSummary.mocks.filter { battle in + let matchesKeyword = filter.keyword.isEmpty + || battle.title.localizedCaseInsensitiveContains(filter.keyword) + || battle.summary.localizedCaseInsensitiveContains(filter.keyword) + let matchesCategory = filter.category.map { battle.category == $0 } ?? true + return matchesKeyword && matchesCategory + } + } + + public func createClass(_ creation: ClassCreation) async throws -> ClassRoom { + guard !creation.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, + let battle = ClassBattleSummary.mocks.first(where: { $0.id == creation.battleId }) + else { + throw ClassError.unknown("클래스 이름 또는 배틀이 올바르지 않습니다.") + } + + let id = nextID + nextID += 1 + let room = ClassRoom( + id: id, + name: creation.name, + joinCode: String(format: "PK%04d", id), + battleId: battle.id, + battleTitle: battle.title, + deadline: creation.deadline, + memberCount: 1, + role: .owner, + status: .open, + isVoteEnabled: creation.isVoteEnabled, + requiresComment: creation.requiresComment + ) + rooms.append(room) + return room + } + + public func fetchClass(joinCode: String) async throws -> ClassRoom { + guard let room = (rooms + availableRooms).first(where: { $0.joinCode == joinCode }) else { + throw ClassError.invalidCode + } + return room + } + + public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { + guard !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + throw ClassError.unknown("이름을 입력해주세요.") + } + guard !rooms.contains(where: { $0.joinCode == joinCode }) else { + throw ClassError.alreadyJoined + } + guard let index = availableRooms.firstIndex(where: { $0.joinCode == joinCode }) else { + throw ClassError.invalidCode + } + + let room = availableRooms[index] + guard room.status == .open else { throw ClassError.closed } + let joined = ClassRoom( + id: room.id, + name: room.name, + joinCode: room.joinCode, + battleId: room.battleId, + battleTitle: room.battleTitle, + deadline: room.deadline, + memberCount: room.memberCount + 1, + role: .member, + status: room.status, + isVoteEnabled: room.isVoteEnabled, + requiresComment: room.requiresComment + ) + rooms.append(joined) + availableRooms.remove(at: index) + return joined + } +} diff --git a/Projects/Domain/ClassDomain/Project.swift b/Projects/Domain/ClassDomain/Project.swift new file mode 100644 index 00000000..691c5847 --- /dev/null +++ b/Projects/Domain/ClassDomain/Project.swift @@ -0,0 +1,19 @@ +import DependencyPackagePlugin +import DependencyPlugin +import Foundation +import ProjectDescription +import ProjectTemplatePlugin + +let project = Project.makeModule( + name: "ClassDomain", + bundleId: .appBundleID(name: ".ClassDomain"), + product: .framework, + settings: .settings(), + dependencies: [], + hasTests: true, + hasInterface: true, + interfaceDependencies: [ + .SPM.composableArchitecture, + ], + hasTesting: false +) diff --git a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift new file mode 100644 index 00000000..9bebe255 --- /dev/null +++ b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift @@ -0,0 +1,14 @@ +import ClassDomainInterface +import ComposableArchitecture + +extension ClassUseCaseDependency: DependencyKey { + public static var liveValue: any ClassInterface { + ClassUseCaseImpl() + } +} + +extension ClassRepositoryDependency: DependencyKey { + public static var liveValue: any ClassInterface { + ClassRepositoryImpl() + } +} diff --git a/Projects/Domain/ClassDomain/Sources/Exported/ClassDomainExported.swift b/Projects/Domain/ClassDomain/Sources/Exported/ClassDomainExported.swift new file mode 100644 index 00000000..614f78a1 --- /dev/null +++ b/Projects/Domain/ClassDomain/Sources/Exported/ClassDomainExported.swift @@ -0,0 +1 @@ +@_exported import ClassDomainInterface diff --git a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift new file mode 100644 index 00000000..e7e5b862 --- /dev/null +++ b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift @@ -0,0 +1,28 @@ +import ClassDomainInterface +import Foundation + +public struct ClassRepositoryImpl: ClassInterface { + private let mock = MockClassRepository() + + public init() {} + + public func fetchMyClasses() async throws -> [ClassRoom] { + try await mock.fetchMyClasses() + } + + public func fetchRecommendedBattles(filter: ClassTopicFilter) async throws -> [ClassBattleSummary] { + try await mock.fetchRecommendedBattles(filter: filter) + } + + public func createClass(_ creation: ClassCreation) async throws -> ClassRoom { + try await mock.createClass(creation) + } + + public func fetchClass(joinCode: String) async throws -> ClassRoom { + try await mock.fetchClass(joinCode: joinCode) + } + + public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { + try await mock.joinClass(joinCode: joinCode, nickname: nickname) + } +} diff --git a/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift b/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift new file mode 100644 index 00000000..77e93f11 --- /dev/null +++ b/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift @@ -0,0 +1,29 @@ +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +public struct ClassUseCaseImpl: ClassInterface { + @Dependency(\.classRepository) private var repository + + public init() {} + + public func fetchMyClasses() async throws -> [ClassRoom] { + try await repository.fetchMyClasses() + } + + public func fetchRecommendedBattles(filter: ClassTopicFilter) async throws -> [ClassBattleSummary] { + try await repository.fetchRecommendedBattles(filter: filter) + } + + public func createClass(_ creation: ClassCreation) async throws -> ClassRoom { + try await repository.createClass(creation) + } + + public func fetchClass(joinCode: String) async throws -> ClassRoom { + try await repository.fetchClass(joinCode: joinCode) + } + + public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { + try await repository.joinClass(joinCode: joinCode, nickname: nickname) + } +} diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift new file mode 100644 index 00000000..75d18420 --- /dev/null +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift @@ -0,0 +1,51 @@ +import ClassDomainInterface +import Foundation +import Testing + +struct ClassDomainTests { + @Test + func 생성한_클래스는_목록에_추가되고_선생님_역할을_갖는다() async throws { + let repository = MockClassRepository(rooms: [], availableRooms: []) + let created = try await repository.createClass( + ClassCreation( + name: "토론 수업", + deadline: Date(timeIntervalSince1970: 1_800_000_000), + battleId: 101, + isVoteEnabled: true, + requiresComment: false + ) + ) + + #expect(created.role == .owner) + #expect(created.battleId == 101) + #expect(try await repository.fetchMyClasses() == [created]) + #expect(try await repository.fetchClass(joinCode: created.joinCode) == created) + } + + @Test + func 참여하면_학생_역할로_목록에_추가되고_중복_참여는_거부한다() async throws { + let repository = MockClassRepository(rooms: [], availableRooms: [.mockJoinable]) + let joined = try await repository.joinClass(joinCode: "PK9T3S", nickname: "민지") + + #expect(joined.role == .member) + #expect(joined.memberCount == ClassRoom.mockJoinable.memberCount + 1) + #expect(try await repository.fetchMyClasses() == [joined]) + do { + _ = try await repository.joinClass(joinCode: "PK9T3S", nickname: "민지") + Issue.record("중복 참여가 허용됨") + } catch { + #expect(ClassError.from(error) == .alreadyJoined) + } + } + + @Test + func 없는_참여_코드는_거부한다() async throws { + let repository = MockClassRepository(rooms: [], availableRooms: []) + do { + _ = try await repository.fetchClass(joinCode: "INVALID") + Issue.record("없는 참여 코드가 허용됨") + } catch { + #expect(ClassError.from(error) == .invalidCode) + } + } +} diff --git a/Projects/Domain/DomainAssembly/Project.swift b/Projects/Domain/DomainAssembly/Project.swift index 4a3ba505..38e4c9bd 100644 --- a/Projects/Domain/DomainAssembly/Project.swift +++ b/Projects/Domain/DomainAssembly/Project.swift @@ -16,6 +16,7 @@ let project = Project.makeModule( .domain(.attendance, .implementation), .domain(.auth, .implementation), .domain(.battle, .implementation), + .domain(.classroom, .implementation), .domain(.ad, .implementation), .domain(.search, .implementation), .domain(.comment, .implementation), diff --git a/Projects/Domain/DomainAssembly/Sources/Exported/DomainAssemblyExported.swift b/Projects/Domain/DomainAssembly/Sources/Exported/DomainAssemblyExported.swift index 04377248..52b1e2a9 100644 --- a/Projects/Domain/DomainAssembly/Sources/Exported/DomainAssemblyExported.swift +++ b/Projects/Domain/DomainAssembly/Sources/Exported/DomainAssemblyExported.swift @@ -5,6 +5,8 @@ // MARK: - Domain 레이어 한번에 노출 +@_exported import AdDomain +@_exported import AdDomainInterface @_exported import AppUpdateDomain @_exported import AppUpdateDomainInterface @_exported import AttendanceDomain @@ -13,8 +15,8 @@ @_exported import AuthDomainInterface @_exported import BattleDomain @_exported import BattleDomainInterface -@_exported import AdDomain -@_exported import AdDomainInterface +@_exported import ClassDomain +@_exported import ClassDomainInterface @_exported import CommentDomain @_exported import CommentDomainInterface @_exported import HomeDomain diff --git a/Projects/Domain/DomainAssembly/Tests/Sources/DomainAssemblyExportTests.swift b/Projects/Domain/DomainAssembly/Tests/Sources/DomainAssemblyExportTests.swift index c92d8e7a..7e88105e 100644 --- a/Projects/Domain/DomainAssembly/Tests/Sources/DomainAssemblyExportTests.swift +++ b/Projects/Domain/DomainAssembly/Tests/Sources/DomainAssemblyExportTests.swift @@ -18,6 +18,7 @@ struct DomainAssemblyExportTests { (any AttendanceInterface).self, (any AuthUseCaseInterface).self, (any BattleInterface).self, + (any ClassInterface).self, (any CommentInterface).self, (any HomeInterface).self, (any NotificationInterface).self, @@ -26,6 +27,6 @@ struct DomainAssemblyExportTests { (any SearchInterface).self, ] - #expect(interfaces.count == 10) + #expect(interfaces.count == 11) } } From 6cbc9fa99c35fa286b44937f9da00b27cea920f7 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 30 Sep 2026 23:46:33 +0900 Subject: [PATCH 04/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=83=AD?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=EA=B3=BC=20=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=90=EC=85=8B?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GNB 클래스 탭 아이콘(선택/비선택) SVG 추가 - 클래스 인트로 배경·카드 이미지 PNG 추가 (Figma 래스터 이미지) - ImageAsset 에 클래스 케이스, AnalyticsButton 에 tabClass 추가 --- .../Interface/Event/AnalyticsButton.swift | 1 + .../ImageAssets.xcassets/Class/Contents.json | 6 ++++++ .../Contents.json | 12 ++++++++++++ .../classIntroBackground.png | Bin 0 -> 598123 bytes .../Class/classJoin.imageset/Contents.json | 12 ++++++++++++ .../Class/classJoin.imageset/classJoin.png | Bin 0 -> 38142 bytes .../Class/classMine.imageset/Contents.json | 12 ++++++++++++ .../Class/classMine.imageset/classMine.png | Bin 0 -> 55397 bytes .../GNB/tabClass.imageset/Contents.json | 16 ++++++++++++++++ .../GNB/tabClass.imageset/tabClass.svg | 3 +++ .../GNB/tabClassActive.imageset/Contents.json | 16 ++++++++++++++++ .../tabClassActive.imageset/tabClassActive.svg | 3 +++ .../Sources/Token/Image/ImageAsset.swift | 8 ++++++++ 13 files changed, 89 insertions(+) create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/Contents.json create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/Contents.json create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/classIntroBackground.png create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classJoin.imageset/Contents.json create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classJoin.imageset/classJoin.png create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classMine.imageset/Contents.json create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classMine.imageset/classMine.png create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/Contents.json create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/tabClass.svg create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/Contents.json create mode 100644 Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/tabClassActive.svg diff --git a/Projects/Service/PickeAnalytics/Interface/Event/AnalyticsButton.swift b/Projects/Service/PickeAnalytics/Interface/Event/AnalyticsButton.swift index c588c93e..67c2b8c9 100644 --- a/Projects/Service/PickeAnalytics/Interface/Event/AnalyticsButton.swift +++ b/Projects/Service/PickeAnalytics/Interface/Event/AnalyticsButton.swift @@ -24,6 +24,7 @@ public enum AnalyticsButton: String, Sendable { case tabHome = "tab_home" case tabExplore = "tab_explore" case tabQuickBattle = "tab_quick_battle" + case tabClass = "tab_class" case tabMypage = "tab_mypage" // 댓글 case commentShare = "comment_share" diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/Contents.json b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/Contents.json b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/Contents.json new file mode 100644 index 00000000..35a13f23 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "classIntroBackground.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/classIntroBackground.png b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/Class/classIntroBackground.imageset/classIntroBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..2675e6262ac99900effe28fe8abe0d9c52d352f4 GIT binary patch literal 598123 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≺M^j)s$L@<) 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 s$P)OO0 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

@@qGx#a$< z|McLY*AI8jTgYX>`O*|~(?~d_2@YmFtb*xYPc;5y!pO(C!pbR(4|%|5UYdLv0N7Tu zi>F+F?1|^T^{q#Xr5R4?p?j;-!FIV5!h{8eVL|Ir39zU+adb|et}ZSDy~0&qmy$}J zuq{z0M-Yv{fXr>{eu%3#4bBi3Vv{!p(9gf{O#gzmFMRR0Q;F`+|NfUohdK0KJ0d|p z10OlNaYab84fYNmp3V`o>D6CeeMiwW6&hMXs0M=pcLelybe=hR^60_yKmEZ?x8Ah5 zQaIZ-Q#1u7C)y%BF{fc|5VJ3K1UERy8r!gZb_zky+MVxQl&7_5YRM2ziN>rn3{ywz zKOzD*-hdpw@+1U@rz!bLa3hHebj(hEIxF~-v4b`u_;L6|6^M^>9_-SYNQzqRH#d)I zAwe?X2a;_qiKLnm(ZsIF+%pi2506hFQGjQ_;Hk0%FKDpciyqqrayg6`*`A`#!TCUq zBFD}#PiFLOj|b*=ce9P2Aw}k4Z1O-QhB>@=4;_d-x@`Wzg8(HD6AEnbm-@wbiSqSj2qs=4TyauhfaAJ2E(^<5 zLx>CH>HI)K4#cO=i#42uzt

& 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 ax&H;(xUen?qsef`(RQI<**PL^@ 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 C+JR#L literal 0 HcmV?d00001 diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/Contents.json b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/Contents.json new file mode 100644 index 00000000..106f074a --- /dev/null +++ b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "tabClass.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "original" + } +} diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/tabClass.svg b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/tabClass.svg new file mode 100644 index 00000000..165e5a01 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClass.imageset/tabClass.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/Contents.json b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/Contents.json new file mode 100644 index 00000000..4081c1c5 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "tabClassActive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "original" + } +} diff --git a/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/tabClassActive.svg b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/tabClassActive.svg new file mode 100644 index 00000000..864de85d --- /dev/null +++ b/Projects/UI/PickeDesignKit/Resources/ImageAssets.xcassets/GNB/tabClassActive.imageset/tabClassActive.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/UI/PickeDesignKit/Sources/Token/Image/ImageAsset.swift b/Projects/UI/PickeDesignKit/Sources/Token/Image/ImageAsset.swift index dcb0871f..82af4909 100644 --- a/Projects/UI/PickeDesignKit/Sources/Token/Image/ImageAsset.swift +++ b/Projects/UI/PickeDesignKit/Sources/Token/Image/ImageAsset.swift @@ -35,6 +35,8 @@ public enum ImageAsset: String { case tabExploreActive case tabQuickBattle case tabQuickBattleActive + case tabClass + case tabClassActive case tabMyPage case tabMyPageActive @@ -59,4 +61,10 @@ public enum ImageAsset: String { // MARK: - 댓글(관점) case heartPlus + + // MARK: - 클래스 + + case classIntroBackground + case classJoin + case classMine } From 27be6da35bd301a7a20377b0ab122d8049d3c413 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 30 Sep 2026 23:55:49 +0900 Subject: [PATCH 05/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=83=AD?= =?UTF-8?q?=EA=B3=BC=20=EC=9D=B8=ED=8A=B8=EB=A1=9C=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Feature/Class 모듈 신설, ClassIntroFeature·ClassIntroView·ClassMenuCard 추가 - ClassCoordinator(TCAFlow) 추가, 인트로를 루트 화면으로 연결 - MainTab 에 클래스 탭(빠른 배틀과 마이 사이) 추가, 분석 이벤트·루트 리셋 포함 - FeatureAssembly 에 Class 모듈 조립 --- .../Navigation/Class/ClassCoordinator.swift | 94 +++++++++++++++ .../Class/ClassCoordinatorView.swift | 27 +++++ .../MainTab/MainTabCoordinator.swift | 17 ++- .../Navigation/MainTab/MainTabView.swift | 19 +-- .../Sources/ClassFeatureInterface.swift | 3 + Projects/Feature/Class/Project.swift | 26 ++++ .../Reducer/ClassIntroFeature.swift | 75 ++++++++++++ .../ClassIntro/View/ClassIntroView.swift | 111 ++++++++++++++++++ .../View/Components/ClassMenuCard.swift | 49 ++++++++ .../Class/Tests/Sources/ClassTests.swift | 31 +++++ .../Feature/FeatureAssembly/Project.swift | 1 + .../Exported/FeatureAssemblyExported.swift | 1 + 12 files changed, 446 insertions(+), 8 deletions(-) create mode 100644 Projects/App/Sources/Navigation/Class/ClassCoordinator.swift create mode 100644 Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift create mode 100644 Projects/Feature/Class/Interface/Sources/ClassFeatureInterface.swift create mode 100644 Projects/Feature/Class/Project.swift create mode 100644 Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift create mode 100644 Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift new file mode 100644 index 00000000..e1ff70f9 --- /dev/null +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -0,0 +1,94 @@ +// +// ClassCoordinator.swift +// Picke +// + +import Foundation + +import ComposableArchitecture +import DomainAssembly +import FeatureAssembly +import TCAFlow + +@FlowCoordinator(screen: "ClassScreen", navigation: true) +public struct ClassCoordinator { + public init() {} + + @ObservableState + public struct State: Equatable { + public var routes: [Route] + + public init() { + routes = [.root(.intro(.init()), embedInNavigationView: true)] + } + } + + @CasePathable + public enum Action { + case router(IndexedRouterActionOf) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case navigation(NavigationAction) + } + + @CasePathable + public enum View { + case backAction + case backToRootAction + } + + public enum AsyncAction: Equatable {} + public enum InnerAction: Equatable {} + public enum NavigationAction: Equatable {} + + func handleRoute( + state: inout State, + action: Action + ) -> Effect { + switch action { + case let .router(routeAction): + routerAction(state: &state, action: routeAction) + case let .view(viewAction): + handleViewAction(state: &state, action: viewAction) + case .async, .inner, .navigation: + .none + } + } +} + +private extension ClassCoordinator { + func routerAction( + state _: inout State, + action _: IndexedRouterActionOf + ) -> Effect { + return .none + } + + func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .backAction: + state.routes.goBack() + return .none + + case .backToRootAction: + state.routes.goBackToRoot() + return .none + } + } +} + +// swiftformat:disable extensionAccessControl +extension ClassCoordinator { + @Reducer + public enum ClassScreen { + case intro(ClassIntroFeature) + } +} + +// swiftformat:enable extensionAccessControl + +extension ClassCoordinator.ClassScreen.State: Equatable {} diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift new file mode 100644 index 00000000..1e153c7c --- /dev/null +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -0,0 +1,27 @@ +// +// ClassCoordinatorView.swift +// Picke +// + +import SwiftUI + +import ComposableArchitecture +import FeatureAssembly +import TCAFlow + +public struct ClassCoordinatorView: View { + @Bindable private var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + TCAFlowRouter(store.scope(state: \.routes, action: \.router)) { screen in + switch screen.case { + case let .intro(introStore): + ClassIntroView(store: introStore) + } + } + } +} diff --git a/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift index fcaea202..a71def14 100644 --- a/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift @@ -21,6 +21,7 @@ public struct MainTabCoordinator { case home case explore case quickBattle + case classroom case myPage public var title: String { @@ -28,6 +29,7 @@ public struct MainTabCoordinator { case .home: return "홈" case .explore: return "탐색" case .quickBattle: return "빠른 배틀" + case .classroom: return "클래스" case .myPage: return "마이" } } @@ -37,6 +39,7 @@ public struct MainTabCoordinator { case .home: return isSelected ? .tabHomeActive : .tabHome case .explore: return isSelected ? .tabExploreActive : .tabExplore case .quickBattle: return isSelected ? .tabQuickBattleActive : .tabQuickBattle + case .classroom: return isSelected ? .tabClassActive : .tabClass case .myPage: return isSelected ? .tabMyPageActive : .tabMyPage } } @@ -49,6 +52,7 @@ public struct MainTabCoordinator { public var homeState: HomeCoordinator.State public var exploreState: HifiCoordinator.State public var quickBattleState: BattleCoordinator.State + public var classState: ClassCoordinator.State public var myPageState: ProfileCoordinator.State public var shouldHideTabBar: Bool { @@ -59,6 +63,8 @@ public struct MainTabCoordinator { return exploreState.routes.count > 1 case .quickBattle: return quickBattleState.routes.count > 1 + case .classroom: + return classState.routes.count > 1 case .myPage: return myPageState.routes.count > 1 case .none: @@ -71,6 +77,7 @@ public struct MainTabCoordinator { homeState = .init() exploreState = .init() quickBattleState = .init() + classState = .init() myPageState = .init() } } @@ -82,6 +89,7 @@ public struct MainTabCoordinator { case home(HomeCoordinator.Action) case explore(HifiCoordinator.Action) case quickBattle(BattleCoordinator.Action) + case classroom(ClassCoordinator.Action) case myPage(ProfileCoordinator.Action) case delegate(DelegateAction) } @@ -102,6 +110,9 @@ public struct MainTabCoordinator { Scope(state: \.quickBattleState, action: \.quickBattle) { BattleCoordinator() } + Scope(state: \.classState, action: \.classroom) { + ClassCoordinator() + } Scope(state: \.myPageState, action: \.myPage) { ProfileCoordinator() } @@ -144,7 +155,7 @@ public struct MainTabCoordinator { case .delegate: return .none - case .home, .explore, .quickBattle, .myPage: + case .home, .explore, .quickBattle, .classroom, .myPage: return .none } } @@ -160,6 +171,8 @@ private extension MainTabCoordinator { analyticsUseCase.track(.uiAction(action: .tabExplore, screen: .mainTab)) case .quickBattle: analyticsUseCase.track(.uiAction(action: .tabQuickBattle, screen: .mainTab)) + case .classroom: + analyticsUseCase.track(.uiAction(action: .tabClass, screen: .mainTab)) case .myPage: analyticsUseCase.track(.uiAction(action: .tabMypage, screen: .mainTab)) case .none: @@ -178,6 +191,8 @@ private extension MainTabCoordinator { state.exploreState.routes.goBackToRoot() case .quickBattle: state.quickBattleState.routes.goBackToRoot() + case .classroom: + state.classState.routes.goBackToRoot() case .myPage: state.myPageState.routes.goBackToRoot() case .none: diff --git a/Projects/App/Sources/Navigation/MainTab/MainTabView.swift b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift index 5bd338f7..27e2a769 100644 --- a/Projects/App/Sources/Navigation/MainTab/MainTabView.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift @@ -5,14 +5,13 @@ // Created by Codex on 7/11/26. // -import SwiftUI -import UIKit - import ComposableArchitecture +import FeatureAssembly import PickeCoreUI import PickeDesignKit -import FeatureAssembly +import SwiftUI import TCAFlow +import UIKit public struct MainTabView: View { @Bindable private var store: StoreOf @@ -104,7 +103,6 @@ private extension MainTabView { ] } - @ViewBuilder func tabLabel(for tab: TabItem) -> some View { Label { Text(tab.title) @@ -128,8 +126,8 @@ private extension MainTabView { .frame(width: 24, height: 24) } - // TabView 는 네 탭의 콘텐츠를 한꺼번에 만들기 때문에, LazyView 로 감싸 - // 각 탭이 처음 화면에 올라올 때까지 스토어 스코프와 뷰 생성을 미룬다. + /// TabView 는 네 탭의 콘텐츠를 한꺼번에 만들기 때문에, LazyView 로 감싸 + /// 각 탭이 처음 화면에 올라올 때까지 스토어 스코프와 뷰 생성을 미룬다. @ViewBuilder func tabContent(for tab: Int) -> some View { switch MainTabCoordinator.Tab(rawValue: tab) { @@ -154,6 +152,13 @@ private extension MainTabView { ) } + case .classroom: + LazyView { + ClassCoordinatorView( + store: store.scope(state: \.classState, action: \.classroom) + ) + } + case .myPage: LazyView { ProfileCoordinatorView( diff --git a/Projects/Feature/Class/Interface/Sources/ClassFeatureInterface.swift b/Projects/Feature/Class/Interface/Sources/ClassFeatureInterface.swift new file mode 100644 index 00000000..2b94582b --- /dev/null +++ b/Projects/Feature/Class/Interface/Sources/ClassFeatureInterface.swift @@ -0,0 +1,3 @@ +import ClassDomainInterface + +public enum ClassFeatureInterface {} diff --git a/Projects/Feature/Class/Project.swift b/Projects/Feature/Class/Project.swift new file mode 100644 index 00000000..13896dc5 --- /dev/null +++ b/Projects/Feature/Class/Project.swift @@ -0,0 +1,26 @@ +import Foundation + +import DependencyPackagePlugin +import DependencyPlugin +import ProjectTemplatePlugin + +import ProjectDescription + +let project = Project.makeModule( + name: "Class", + bundleId: .appBundleID(name: ".Class"), + product: .staticFramework, + settings: .settings(), + dependencies: [ + .ui(.designKit), + .ui(.sharedUI), + .domain(.classroom, .interface), + .SPM.composableArchitecture, + ], + hasTests: true, + hasInterface: true, + interfaceDependencies: [ + .domain(.classroom, .interface), + ], + hasTesting: false +) diff --git a/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift b/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift new file mode 100644 index 00000000..0ba8d673 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift @@ -0,0 +1,75 @@ +// +// ClassIntroFeature.swift +// Class +// + +import Foundation + +import ComposableArchitecture + +@Reducer +public struct ClassIntroFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public init() {} + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View { + case joinTapped + case myClassesTapped + case createTapped + case ticketTapped + } + + @CasePathable + public enum DelegateAction: Equatable { + case join + case myClasses + case create + case ticket + } + + public var body: some Reducer { + Reduce { state, action in + switch action { + case let .view(viewAction): + return handleViewAction( + state: &state, + action: viewAction + ) + + case .delegate: + return .none + } + } + } +} + +extension ClassIntroFeature { + private func handleViewAction( + state _: inout State, + action: View + ) -> Effect { + switch action { + case .joinTapped: + return .send(.delegate(.join)) + + case .myClassesTapped: + return .send(.delegate(.myClasses)) + + case .createTapped: + return .send(.delegate(.create)) + + case .ticketTapped: + return .send(.delegate(.ticket)) + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift new file mode 100644 index 00000000..0ecb62a9 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -0,0 +1,111 @@ +// +// ClassIntroView.swift +// Class +// + +import SwiftUI + +import ComposableArchitecture +import PickeDesignKit +import PickeSharedUI + +@ViewAction(for: ClassIntroFeature.self) +public struct ClassIntroView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(centerTitle: "클래스") + .foregroundStyle(.white) + + VStack(alignment: .leading, spacing: 0) { + titleSection() + Spacer(minLength: 24) + menuCards() + .padding(.bottom, 16) + createButton() + ticketButton() + } + .padding(.top, 24) + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .background(backgroundImage()) + .toolbar(.hidden, for: .navigationBar) + } +} + +private extension ClassIntroView { + @ViewBuilder + func backgroundImage() -> some View { + ZStack { + Color.beige200 + Image(asset: .classIntroBackground) + .resizable() + .scaledToFill() + } + .ignoresSafeArea() + } + + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 8) { + Text("함께 생각하는\n수업을 시작해 보세요") + .pretendardFont(family: .SemiBold, size: 24) + .lineSpacing(7) + .foregroundStyle(.white) + + Text("콘텐츠를 고르고, 클래스로 함께 나눠요.") + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func menuCards() -> some View { + HStack(spacing: 6) { + ClassMenuCard( + title: "클래스 참여하기", + description: "참여 코드 입력", + image: .classJoin, + action: { send(.joinTapped) } + ) + + ClassMenuCard( + title: "내 클래스", + description: "참여중인 클래스", + image: .classMine, + action: { send(.myClassesTapped) } + ) + } + } + + @ViewBuilder + func createButton() -> some View { + Button { + send(.createTapped) + } label: { + Text("새 클래스 만들기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + } + + @ViewBuilder + func ticketButton() -> some View { + Button { + send(.ticketTapped) + } label: { + Text("이용권 등록하기") + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(.gray300) + .underline() + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + } + .buttonStyle(.plain) + } +} diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift b/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift new file mode 100644 index 00000000..f3e03109 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift @@ -0,0 +1,49 @@ +// +// ClassMenuCard.swift +// Class +// + +import SwiftUI + +import PickeDesignKit + +/// 클래스 인트로의 진입 카드 (참여하기 / 내 클래스). +struct ClassMenuCard: View { + let title: String + let description: String + let image: ImageAsset + let action: () -> Void + + var body: some View { + Button(action: action) { + ZStack(alignment: .bottomTrailing) { + Image(asset: image) + .resizable() + .scaledToFit() + .frame(width: 96, height: 96) + .offset(x: 12, y: 12) + + VStack(alignment: .leading, spacing: 4) { + Text(title) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(.white) + + Text(description) + .pretendardFont(family: .Regular, size: 13) + .foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } + .padding(16) + .frame(maxWidth: .infinity) + .frame(height: 118) + .background(.gray700, in: RoundedRectangle(cornerRadius: 2)) + .overlay( + RoundedRectangle(cornerRadius: 2) + .stroke(.gray800, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 2)) + } + .buttonStyle(.plain) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassTests.swift b/Projects/Feature/Class/Tests/Sources/ClassTests.swift new file mode 100644 index 00000000..d849642b --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassTests.swift @@ -0,0 +1,31 @@ +// +// ClassTests.swift +// Feature.ClassTests +// + +@testable import Class +import ComposableArchitecture +import Testing + +@MainActor +struct ClassTests { + @Test + func 참여하기_탭하면_참여_델리게이트를_보낸다() async { + let store = TestStore(initialState: ClassIntroFeature.State()) { + ClassIntroFeature() + } + + await store.send(.view(.joinTapped)) + await store.receive(\.delegate.join) + } + + @Test + func 새_클래스_만들기_탭하면_생성_델리게이트를_보낸다() async { + let store = TestStore(initialState: ClassIntroFeature.State()) { + ClassIntroFeature() + } + + await store.send(.view(.createTapped)) + await store.receive(\.delegate.create) + } +} diff --git a/Projects/Feature/FeatureAssembly/Project.swift b/Projects/Feature/FeatureAssembly/Project.swift index c143a097..0fa3b727 100644 --- a/Projects/Feature/FeatureAssembly/Project.swift +++ b/Projects/Feature/FeatureAssembly/Project.swift @@ -19,6 +19,7 @@ let project = Project.makeModule( .feature(.hifi, .implementation), .feature(.battle, .implementation), .feature(.profile, .implementation), + .feature(.classroom, .implementation), .feature(.notification, .implementation), .feature(.ad, .implementation), .feature(.featureSharedUI, .implementation), diff --git a/Projects/Feature/FeatureAssembly/Sources/Exported/FeatureAssemblyExported.swift b/Projects/Feature/FeatureAssembly/Sources/Exported/FeatureAssemblyExported.swift index 31e1d7d3..04cbe83e 100644 --- a/Projects/Feature/FeatureAssembly/Sources/Exported/FeatureAssemblyExported.swift +++ b/Projects/Feature/FeatureAssembly/Sources/Exported/FeatureAssemblyExported.swift @@ -12,6 +12,7 @@ @_exported import Auth @_exported import Battle @_exported import Chat +@_exported import Class @_exported import Hifi @_exported import Home @_exported import Notification From fb35ef45b5bfadd6a98e9d0b210c9efc2e115189 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:01:55 +0900 Subject: [PATCH 06/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=AA=A9?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=EC=86=8C=EC=9D=98=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A5=BC=20=ED=99=94=EB=A9=B4=20=EA=B0=84=EC=97=90=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 Domain과 같은 의존성 등록 방식을 유지하고, 생성 후 다른 저장소 인스턴스에서 조회되는 동작을 검증한다. --- .../Sources/ClassLiveDependencies.swift | 8 ++------ .../Repository/ClassRepositoryImpl.swift | 12 ++++++------ .../Tests/Sources/ClassDomainTests.swift | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift index 9bebe255..78608474 100644 --- a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift +++ b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift @@ -2,13 +2,9 @@ import ClassDomainInterface import ComposableArchitecture extension ClassUseCaseDependency: DependencyKey { - public static var liveValue: any ClassInterface { - ClassUseCaseImpl() - } + public static var liveValue: any ClassInterface { ClassUseCaseImpl() } } extension ClassRepositoryDependency: DependencyKey { - public static var liveValue: any ClassInterface { - ClassRepositoryImpl() - } + public static var liveValue: any ClassInterface { ClassRepositoryImpl() } } diff --git a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift index e7e5b862..ae20c2ca 100644 --- a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift +++ b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift @@ -2,27 +2,27 @@ import ClassDomainInterface import Foundation public struct ClassRepositoryImpl: ClassInterface { - private let mock = MockClassRepository() + private static let mock = MockClassRepository() public init() {} public func fetchMyClasses() async throws -> [ClassRoom] { - try await mock.fetchMyClasses() + try await Self.mock.fetchMyClasses() } public func fetchRecommendedBattles(filter: ClassTopicFilter) async throws -> [ClassBattleSummary] { - try await mock.fetchRecommendedBattles(filter: filter) + try await Self.mock.fetchRecommendedBattles(filter: filter) } public func createClass(_ creation: ClassCreation) async throws -> ClassRoom { - try await mock.createClass(creation) + try await Self.mock.createClass(creation) } public func fetchClass(joinCode: String) async throws -> ClassRoom { - try await mock.fetchClass(joinCode: joinCode) + try await Self.mock.fetchClass(joinCode: joinCode) } public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { - try await mock.joinClass(joinCode: joinCode, nickname: nickname) + try await Self.mock.joinClass(joinCode: joinCode, nickname: nickname) } } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift index 75d18420..05051d27 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift @@ -1,8 +1,25 @@ +import ClassDomain import ClassDomainInterface import Foundation import Testing struct ClassDomainTests { + @Test + func 서로_다른_live_저장소에서_생성한_클래스를_조회할_수_있다() async throws { + let created = try await ClassRepositoryImpl().createClass( + ClassCreation( + name: "공유 상태 확인", + deadline: Date(timeIntervalSince1970: 1_800_000_000), + battleId: 101, + isVoteEnabled: true, + requiresComment: false + ) + ) + + let rooms = try await ClassRepositoryImpl().fetchMyClasses() + #expect(rooms.contains(created)) + } + @Test func 생성한_클래스는_목록에_추가되고_선생님_역할을_갖는다() async throws { let repository = MockClassRepository(rooms: [], availableRooms: []) From 3a7c013f4bfc69ef17fe0d26a718136679e0fd6a Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:05:32 +0900 Subject: [PATCH 07/40] =?UTF-8?q?=ED=83=AD=EB=B0=94=20=EC=99=B8=ED=98=95?= =?UTF-8?q?=EA=B3=BC=20=ED=83=AD=20=EB=9D=BC=EB=B2=A8=EC=9D=84=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20UI=20=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PickeSharedUI 에 PickeTabBarAppearance, PickeTabItemLabel 추가 - MainTabView 는 공용 컴포넌트를 호출하도록 정리 --- .../Navigation/MainTab/MainTabView.swift | 83 ++----------------- .../TabBar/PickeTabBarAppearance.swift | 70 ++++++++++++++++ .../Sources/TabBar/PickeTabItemLabel.swift | 39 +++++++++ 3 files changed, 117 insertions(+), 75 deletions(-) create mode 100644 Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabBarAppearance.swift create mode 100644 Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabItemLabel.swift diff --git a/Projects/App/Sources/Navigation/MainTab/MainTabView.swift b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift index 27e2a769..5d1dc718 100644 --- a/Projects/App/Sources/Navigation/MainTab/MainTabView.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift @@ -9,15 +9,15 @@ import ComposableArchitecture import FeatureAssembly import PickeCoreUI import PickeDesignKit +import PickeSharedUI import SwiftUI import TCAFlow -import UIKit public struct MainTabView: View { @Bindable private var store: StoreOf public init(store: StoreOf) { - Self.configureTabBarAppearance() + PickeTabBarAppearance.configure() self.store = store } @@ -46,84 +46,17 @@ public struct MainTabView: View { } private extension MainTabView { - static func configureTabBarAppearance() { - let selectedColor = UIColor.neutral900 - let normalColor = UIColor.gray200 - let backgroundColor = UIColor.bgDefault - let borderColor = UIColor.borderDefault.withAlphaComponent(0.4) - let font = UIFont.pretendardFontFamily(family: .Medium, size: 12) - - let appearance = UITabBarAppearance() - appearance.configureWithOpaqueBackground() - appearance.backgroundColor = backgroundColor - appearance.shadowColor = borderColor - appearance.selectionIndicatorImage = UIImage() - - configureItemAppearance( - appearance.stackedLayoutAppearance, - selectedColor, - normalColor, - font - ) - configureItemAppearance( - appearance.inlineLayoutAppearance, - selectedColor, - normalColor, - font - ) - configureItemAppearance( - appearance.compactInlineLayoutAppearance, - selectedColor, - normalColor, - font - ) - - UITabBar.appearance().standardAppearance = appearance - UITabBar.appearance().scrollEdgeAppearance = appearance - UITabBar.appearance().tintColor = selectedColor - UITabBar.appearance().unselectedItemTintColor = normalColor - } - - static func configureItemAppearance( - _ itemAppearance: UITabBarItemAppearance, - _ selectedColor: UIColor, - _ normalColor: UIColor, - _ font: UIFont - ) { - itemAppearance.normal.iconColor = normalColor - itemAppearance.normal.titleTextAttributes = [ - .font: font, - .foregroundColor: normalColor, - ] - - itemAppearance.selected.iconColor = selectedColor - itemAppearance.selected.titleTextAttributes = [ - .font: font, - .foregroundColor: selectedColor, - ] - } - - func tabLabel(for tab: TabItem) -> some View { - Label { - Text(tab.title) - .pretendardFont(.labelSmall) - } icon: { - tabIcon(for: tab) - } - .accessibilityIdentifier("tab.\(tab.tag)") - } - @ViewBuilder - func tabIcon(for tab: TabItem) -> some View { + func tabLabel(for tab: TabItem) -> some View { let isSelected = store.selectedTab == tab.tag let asset = MainTabCoordinator.Tab(rawValue: tab.tag)? .iconAsset(isSelected: isSelected) ?? .none - Image(asset: asset) - .renderingMode(.template) - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) + PickeTabItemLabel( + title: tab.title, + icon: asset, + tag: tab.tag + ) } /// TabView 는 네 탭의 콘텐츠를 한꺼번에 만들기 때문에, LazyView 로 감싸 diff --git a/Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabBarAppearance.swift b/Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabBarAppearance.swift new file mode 100644 index 00000000..ec699129 --- /dev/null +++ b/Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabBarAppearance.swift @@ -0,0 +1,70 @@ +// +// PickeTabBarAppearance.swift +// PickeSharedUI +// + +import UIKit + +import PickeDesignKit + +/// 앱 전역 UITabBar 외형 (배경·구분선·아이템 색/폰트). +public enum PickeTabBarAppearance { + public static func configure() { + let selectedColor = UIColor.neutral900 + let normalColor = UIColor.gray200 + let backgroundColor = UIColor.bgDefault + let borderColor = UIColor.borderDefault.withAlphaComponent(0.4) + let font = UIFont.pretendardFontFamily(family: .Medium, size: 12) + + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = backgroundColor + appearance.shadowColor = borderColor + appearance.selectionIndicatorImage = UIImage() + + configureItemAppearance( + appearance.stackedLayoutAppearance, + selectedColor, + normalColor, + font + ) + configureItemAppearance( + appearance.inlineLayoutAppearance, + selectedColor, + normalColor, + font + ) + configureItemAppearance( + appearance.compactInlineLayoutAppearance, + selectedColor, + normalColor, + font + ) + + UITabBar.appearance().standardAppearance = appearance + UITabBar.appearance().scrollEdgeAppearance = appearance + UITabBar.appearance().tintColor = selectedColor + UITabBar.appearance().unselectedItemTintColor = normalColor + } +} + +private extension PickeTabBarAppearance { + static func configureItemAppearance( + _ itemAppearance: UITabBarItemAppearance, + _ selectedColor: UIColor, + _ normalColor: UIColor, + _ font: UIFont + ) { + itemAppearance.normal.iconColor = normalColor + itemAppearance.normal.titleTextAttributes = [ + .font: font, + .foregroundColor: normalColor, + ] + + itemAppearance.selected.iconColor = selectedColor + itemAppearance.selected.titleTextAttributes = [ + .font: font, + .foregroundColor: selectedColor, + ] + } +} diff --git a/Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabItemLabel.swift b/Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabItemLabel.swift new file mode 100644 index 00000000..24f0fdbd --- /dev/null +++ b/Projects/UI/PickeSharedUI/Sources/TabBar/PickeTabItemLabel.swift @@ -0,0 +1,39 @@ +// +// PickeTabItemLabel.swift +// PickeSharedUI +// + +import SwiftUI + +import PickeDesignKit + +/// 탭바 아이템 라벨 — 타이틀 + 24pt 템플릿 아이콘. +public struct PickeTabItemLabel: View { + private let title: String + private let icon: ImageAsset + private let tag: Int + + public init( + title: String, + icon: ImageAsset, + tag: Int + ) { + self.title = title + self.icon = icon + self.tag = tag + } + + public var body: some View { + Label { + Text(title) + .pretendardFont(.labelSmall) + } icon: { + Image(asset: icon) + .renderingMode(.template) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + } + .accessibilityIdentifier("tab.\(tag)") + } +} From 55be0f1c02ff8160af851349f44160a544328ed5 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:12:31 +0900 Subject: [PATCH 08/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=84=A0=ED=83=9D=EA=B3=BC=20=EB=B0=B0=ED=8B=80=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Figma 화면의 수준과 카테고리, 익명 의견 설정을 모델에 반영하고 공개 타입을 파일별로 분리한다. --- .../Sources/Entity/ClassAudienceLevel.swift | 13 ++ .../Sources/Entity/ClassBattleSummary.swift | 85 ++++++++++++ .../Sources/Entity/ClassCategory.swift | 19 +++ .../Sources/Entity/ClassCreation.swift | 23 ++++ .../Interface/Sources/Entity/ClassError.swift | 19 +++ .../Sources/Entity/ClassModels.swift | 129 ------------------ .../Interface/Sources/Entity/ClassRole.swift | 4 + .../Interface/Sources/Entity/ClassRoom.swift | 80 +++++++++++ .../Sources/Entity/ClassStatus.swift | 4 + .../Sources/Entity/ClassTopicFilter.swift | 15 ++ .../Default/MockClassRepository.swift | 76 +---------- .../Tests/Sources/ClassDomainTests.swift | 20 ++- 12 files changed, 284 insertions(+), 203 deletions(-) create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassAudienceLevel.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassBattleSummary.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCategory.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCreation.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassError.swift delete mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRole.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRoom.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassStatus.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassTopicFilter.swift diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassAudienceLevel.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassAudienceLevel.swift new file mode 100644 index 00000000..c27b4775 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassAudienceLevel.swift @@ -0,0 +1,13 @@ +public enum ClassAudienceLevel: String, CaseIterable, Equatable, Sendable { + case middle + case high + case adult + + public var title: String { + switch self { + case .middle: "중등" + case .high: "고등" + case .adult: "성인" + } + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassBattleSummary.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassBattleSummary.swift new file mode 100644 index 00000000..5dcbf96e --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassBattleSummary.swift @@ -0,0 +1,85 @@ +import Foundation + +public struct ClassBattleSummary: Equatable, Identifiable, Sendable { + public let id: Int + public let title: String + public let summary: String + public let thumbnailURL: URL? + public let philosopherA: String + public let optionATitle: String + public let philosopherAImageURL: URL? + public let philosopherB: String + public let optionBTitle: String + public let philosopherBImageURL: URL? + public let category: ClassCategory + public let level: ClassAudienceLevel + public let audioDuration: Int + public let viewCount: Int + + public init( + id: Int, + title: String, + summary: String, + thumbnailURL: URL? = nil, + philosopherA: String, + optionATitle: String, + philosopherAImageURL: URL? = nil, + philosopherB: String, + optionBTitle: String, + philosopherBImageURL: URL? = nil, + category: ClassCategory, + level: ClassAudienceLevel, + audioDuration: Int, + viewCount: Int + ) { + self.id = id + self.title = title + self.summary = summary + self.thumbnailURL = thumbnailURL + self.philosopherA = philosopherA + self.optionATitle = optionATitle + self.philosopherAImageURL = philosopherAImageURL + self.philosopherB = philosopherB + self.optionBTitle = optionBTitle + self.philosopherBImageURL = philosopherBImageURL + self.category = category + self.level = level + self.audioDuration = audioDuration + self.viewCount = viewCount + } + + public var durationMinutes: Int { + max(1, audioDuration / 60) + } +} + +public extension ClassBattleSummary { + static let mocks: [ClassBattleSummary] = [ + .init( + id: 101, + title: "촉법소년 연령을 낮춰야 할까?", + summary: "청소년 범죄와 보호의 균형을 이야기해요.", + philosopherA: "엄격한 처벌", + optionATitle: "연령을 낮춰야 한다", + philosopherB: "회복적 보호", + optionBTitle: "현행 연령을 유지한다", + category: .society, + level: .middle, + audioDuration: 5 * 60, + viewCount: 726 + ), + .init( + id: 102, + title: "뒤샹의 변기, 예술인가 도발인가", + summary: "예술의 경계를 함께 생각해요.", + philosopherA: "예술의 확장", + optionATitle: "예술이다", + philosopherB: "전통적 관점", + optionBTitle: "도발이다", + category: .art, + level: .high, + audioDuration: 6 * 60, + viewCount: 902 + ), + ] +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCategory.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCategory.swift new file mode 100644 index 00000000..045f9afe --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCategory.swift @@ -0,0 +1,19 @@ +public enum ClassCategory: String, CaseIterable, Equatable, Sendable { + case philosophy + case society + case literature + case science + case art + case history + + public var title: String { + switch self { + case .philosophy: "철학" + case .society: "사회" + case .literature: "문학" + case .science: "과학" + case .art: "예술" + case .history: "역사" + } + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCreation.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCreation.swift new file mode 100644 index 00000000..849b314d --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassCreation.swift @@ -0,0 +1,23 @@ +import Foundation + +public struct ClassCreation: Equatable, Sendable { + public var name: String + public var deadline: Date + public var battleId: Int + public var allowsAnonymousOpinion: Bool + public var requiresComment: Bool + + public init( + name: String, + deadline: Date, + battleId: Int, + allowsAnonymousOpinion: Bool, + requiresComment: Bool + ) { + self.name = name + self.deadline = deadline + self.battleId = battleId + self.allowsAnonymousOpinion = allowsAnonymousOpinion + self.requiresComment = requiresComment + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassError.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassError.swift new file mode 100644 index 00000000..67eeeacd --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassError.swift @@ -0,0 +1,19 @@ +import Foundation + +public enum ClassError: Error, Equatable, Sendable { + case invalidCode + case closed + case alreadyJoined + case network(String) + case unknown(String) + + public static func from(_ error: Error) -> ClassError { + if let error = error as? ClassError { + return error + } + if let error = error as? URLError { + return .network(error.localizedDescription) + } + return .unknown(String(describing: error)) + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift deleted file mode 100644 index f03f51b0..00000000 --- a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassModels.swift +++ /dev/null @@ -1,129 +0,0 @@ -import Foundation - -public enum ClassRole: String, Equatable, Sendable { - case owner - case member -} - -public enum ClassStatus: String, Equatable, Sendable { - case open - case closed -} - -public struct ClassRoom: Equatable, Identifiable, Sendable { - public let id: Int - public let name: String - public let joinCode: String - public let battleId: Int - public let battleTitle: String - public let deadline: Date - public let memberCount: Int - public let role: ClassRole - public let status: ClassStatus - public let isVoteEnabled: Bool - public let requiresComment: Bool - - public init( - id: Int, - name: String, - joinCode: String, - battleId: Int, - battleTitle: String, - deadline: Date, - memberCount: Int, - role: ClassRole, - status: ClassStatus, - isVoteEnabled: Bool, - requiresComment: Bool - ) { - self.id = id - self.name = name - self.joinCode = joinCode - self.battleId = battleId - self.battleTitle = battleTitle - self.deadline = deadline - self.memberCount = memberCount - self.role = role - self.status = status - self.isVoteEnabled = isVoteEnabled - self.requiresComment = requiresComment - } -} - -public struct ClassTopicFilter: Equatable, Sendable { - public var keyword: String - public var level: String? - public var category: String? - - public init( - keyword: String = "", - level: String? = nil, - category: String? = nil - ) { - self.keyword = keyword - self.level = level - self.category = category - } -} - -public struct ClassBattleSummary: Equatable, Identifiable, Sendable { - public let id: Int - public let title: String - public let summary: String - public let category: String - public let thumbnailURL: URL? - - public init( - id: Int, - title: String, - summary: String, - category: String, - thumbnailURL: URL? - ) { - self.id = id - self.title = title - self.summary = summary - self.category = category - self.thumbnailURL = thumbnailURL - } -} - -public struct ClassCreation: Equatable, Sendable { - public var name: String - public var deadline: Date - public var battleId: Int - public var isVoteEnabled: Bool - public var requiresComment: Bool - - public init( - name: String, - deadline: Date, - battleId: Int, - isVoteEnabled: Bool, - requiresComment: Bool - ) { - self.name = name - self.deadline = deadline - self.battleId = battleId - self.isVoteEnabled = isVoteEnabled - self.requiresComment = requiresComment - } -} - -public enum ClassError: Error, Equatable, Sendable { - case invalidCode - case closed - case alreadyJoined - case network(String) - case unknown(String) - - public static func from(_ error: Error) -> ClassError { - if let error = error as? ClassError { - return error - } - if let error = error as? URLError { - return .network(error.localizedDescription) - } - return .unknown(String(describing: error)) - } -} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRole.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRole.swift new file mode 100644 index 00000000..4d3721c0 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRole.swift @@ -0,0 +1,4 @@ +public enum ClassRole: String, Equatable, Sendable { + case owner + case member +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRoom.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRoom.swift new file mode 100644 index 00000000..4af220b8 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassRoom.swift @@ -0,0 +1,80 @@ +import Foundation + +public struct ClassRoom: Equatable, Identifiable, Sendable { + public let id: Int + public let name: String + public let joinCode: String + public let battle: ClassBattleSummary + public let deadline: Date + public let memberCount: Int + public let role: ClassRole + public let status: ClassStatus + public let allowsAnonymousOpinion: Bool + public let requiresComment: Bool + + public init( + id: Int, + name: String, + joinCode: String, + battle: ClassBattleSummary, + deadline: Date, + memberCount: Int, + role: ClassRole, + status: ClassStatus, + allowsAnonymousOpinion: Bool, + requiresComment: Bool + ) { + self.id = id + self.name = name + self.joinCode = joinCode + self.battle = battle + self.deadline = deadline + self.memberCount = memberCount + self.role = role + self.status = status + self.allowsAnonymousOpinion = allowsAnonymousOpinion + self.requiresComment = requiresComment + } +} + +public extension ClassRoom { + static let mocks: [ClassRoom] = [ + .init( + id: 1, + name: "1학년 3반 사회 토론", + joinCode: "PK7M2Q", + battle: ClassBattleSummary.mocks[0], + deadline: Date(timeIntervalSince1970: 1_799_000_000), + memberCount: 20, + role: .owner, + status: .open, + allowsAnonymousOpinion: true, + requiresComment: true + ), + .init( + id: 2, + name: "함께 생각하는 미술 수업", + joinCode: "PK4B8R", + battle: ClassBattleSummary.mocks[1], + deadline: Date(timeIntervalSince1970: 1_799_000_000), + memberCount: 18, + role: .member, + status: .open, + allowsAnonymousOpinion: true, + requiresComment: false + ), + ] + + static let mockJoinable = ClassRoom( + id: 3, + name: "사회 토론 수업", + joinCode: "PK9T3S", + battle: ClassBattleSummary.mocks[0], + deadline: Date(timeIntervalSince1970: 1_799_000_000), + memberCount: 12, + role: .member, + status: .open, + allowsAnonymousOpinion: true, + requiresComment: true + ) +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassStatus.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassStatus.swift new file mode 100644 index 00000000..1c83487d --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassStatus.swift @@ -0,0 +1,4 @@ +public enum ClassStatus: String, Equatable, Sendable { + case open + case closed +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassTopicFilter.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassTopicFilter.swift new file mode 100644 index 00000000..41635b2b --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassTopicFilter.swift @@ -0,0 +1,15 @@ +public struct ClassTopicFilter: Equatable, Sendable { + public var keyword: String + public var level: ClassAudienceLevel + public var category: ClassCategory? + + public init( + keyword: String = "", + level: ClassAudienceLevel = .middle, + category: ClassCategory? = nil + ) { + self.keyword = keyword + self.level = level + self.category = category + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift index a68e0863..86b76003 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift @@ -1,69 +1,5 @@ import Foundation -public extension ClassRoom { - static let mocks: [ClassRoom] = [ - ClassRoom( - id: 1, - name: "1학년 3반 사회 토론", - joinCode: "PK7M2Q", - battleId: 101, - battleTitle: "촉법소년 연령을 낮춰야 할까?", - deadline: Date(timeIntervalSince1970: 1_799_000_000), - memberCount: 20, - role: .owner, - status: .open, - isVoteEnabled: true, - requiresComment: true - ), - ClassRoom( - id: 2, - name: "함께 생각하는 미술 수업", - joinCode: "PK4B8R", - battleId: 102, - battleTitle: "뒤샹의 변기, 예술인가 도발인가", - deadline: Date(timeIntervalSince1970: 1_799_000_000), - memberCount: 18, - role: .member, - status: .open, - isVoteEnabled: true, - requiresComment: false - ), - ] - - static let mockJoinable = ClassRoom( - id: 3, - name: "철학 토론 수업", - joinCode: "PK9T3S", - battleId: 101, - battleTitle: "촉법소년 연령을 낮춰야 할까?", - deadline: Date(timeIntervalSince1970: 1_799_000_000), - memberCount: 12, - role: .member, - status: .open, - isVoteEnabled: true, - requiresComment: true - ) -} - -public extension ClassBattleSummary { - static let mocks: [ClassBattleSummary] = [ - ClassBattleSummary( - id: 101, - title: "촉법소년 연령을 낮춰야 할까?", - summary: "청소년 범죄와 보호의 균형을 이야기해요.", - category: "사회", - thumbnailURL: nil - ), - ClassBattleSummary( - id: 102, - title: "뒤샹의 변기, 예술인가 도발인가", - summary: "예술의 경계를 함께 생각해요.", - category: "예술", - thumbnailURL: nil - ), - ] -} - public actor MockClassRepository: ClassInterface { private var rooms: [ClassRoom] private var availableRooms: [ClassRoom] @@ -88,7 +24,7 @@ public actor MockClassRepository: ClassInterface { || battle.title.localizedCaseInsensitiveContains(filter.keyword) || battle.summary.localizedCaseInsensitiveContains(filter.keyword) let matchesCategory = filter.category.map { battle.category == $0 } ?? true - return matchesKeyword && matchesCategory + return battle.level == filter.level && matchesCategory && matchesKeyword } } @@ -105,13 +41,12 @@ public actor MockClassRepository: ClassInterface { id: id, name: creation.name, joinCode: String(format: "PK%04d", id), - battleId: battle.id, - battleTitle: battle.title, + battle: battle, deadline: creation.deadline, memberCount: 1, role: .owner, status: .open, - isVoteEnabled: creation.isVoteEnabled, + allowsAnonymousOpinion: creation.allowsAnonymousOpinion, requiresComment: creation.requiresComment ) rooms.append(room) @@ -142,13 +77,12 @@ public actor MockClassRepository: ClassInterface { id: room.id, name: room.name, joinCode: room.joinCode, - battleId: room.battleId, - battleTitle: room.battleTitle, + battle: room.battle, deadline: room.deadline, memberCount: room.memberCount + 1, role: .member, status: room.status, - isVoteEnabled: room.isVoteEnabled, + allowsAnonymousOpinion: room.allowsAnonymousOpinion, requiresComment: room.requiresComment ) rooms.append(joined) diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift index 05051d27..cef93e1e 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift @@ -11,7 +11,7 @@ struct ClassDomainTests { name: "공유 상태 확인", deadline: Date(timeIntervalSince1970: 1_800_000_000), battleId: 101, - isVoteEnabled: true, + allowsAnonymousOpinion: true, requiresComment: false ) ) @@ -28,13 +28,13 @@ struct ClassDomainTests { name: "토론 수업", deadline: Date(timeIntervalSince1970: 1_800_000_000), battleId: 101, - isVoteEnabled: true, + allowsAnonymousOpinion: true, requiresComment: false ) ) #expect(created.role == .owner) - #expect(created.battleId == 101) + #expect(created.battle.id == 101) #expect(try await repository.fetchMyClasses() == [created]) #expect(try await repository.fetchClass(joinCode: created.joinCode) == created) } @@ -65,4 +65,18 @@ struct ClassDomainTests { #expect(ClassError.from(error) == .invalidCode) } } + + @Test + func 추천_배틀은_수준과_카테고리와_검색어로_걸러진다() async throws { + let repository = MockClassRepository() + let middle = try await repository.fetchRecommendedBattles( + filter: .init(keyword: "소년", level: .middle, category: .society) + ) + let high = try await repository.fetchRecommendedBattles( + filter: .init(level: .high, category: .society) + ) + + #expect(middle.map(\.id) == [101]) + #expect(high.isEmpty) + } } From 97e1d23e7d7efda62863db226646142e71541200 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:13:48 +0900 Subject: [PATCH 09/40] =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EC=99=80=20=EC=84=A0=ED=83=9D=20=EC=B9=A9,=20=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=ED=98=95=20=EC=84=B8=EA=B7=B8=EB=A8=BC=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=EC=97=90=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PickeTextField: beige50 배경 + beige600 테두리 한 줄 입력 - pickeChoiceChip: 그리드 단일 선택 칩 - PickeBoxSegment: 회색 트랙 위 박스형 세그먼트 --- .../Sources/UI/Chip/PickeChoiceChip.swift | 22 +++++++ .../Sources/UI/Input/PickeTextField.swift | 41 +++++++++++++ .../UI/Navigaion/PickeBoxSegment.swift | 57 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 Projects/UI/PickeDesignKit/Sources/UI/Chip/PickeChoiceChip.swift create mode 100644 Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift create mode 100644 Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Chip/PickeChoiceChip.swift b/Projects/UI/PickeDesignKit/Sources/UI/Chip/PickeChoiceChip.swift new file mode 100644 index 00000000..d33e3232 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Sources/UI/Chip/PickeChoiceChip.swift @@ -0,0 +1,22 @@ +// +// PickeChoiceChip.swift +// PickeDesignKit +// + +import SwiftUI + +public extension View { + /// 그리드 단일 선택 칩. 선택 시 primary50 채움 + primary500 테두리, 미선택 시 beige 카드. + func pickeChoiceChip(isSelected: Bool) -> some View { + pretendardFont(isSelected ? .headingSmall : .bodyMedium) + .foregroundStyle(isSelected ? .primary500 : .gray300) + .lineLimit(1) + .frame(maxWidth: .infinity) + .frame(height: 44) + .contentShape(Rectangle()) + .pickeCard( + isSelected ? .primary50 : .beige50, + border: isSelected ? .primary500 : .beige600 + ) + } +} diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift b/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift new file mode 100644 index 00000000..b67fcd56 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift @@ -0,0 +1,41 @@ +// +// PickeTextField.swift +// PickeDesignKit +// + +import SwiftUI + +/// 한 줄 입력 필드. beige50 배경 + beige600 테두리, 비어 있으면 gray300 플레이스홀더. +public struct PickeTextField: View { + private let placeholder: String + @Binding private var text: String + private let height: CGFloat + + public init( + _ placeholder: String, + text: Binding, + height: CGFloat = 44 + ) { + self.placeholder = placeholder + self._text = text + self.height = height + } + + public var body: some View { + ZStack(alignment: .leading) { + if text.isEmpty { + Text(placeholder) + .pretendardFont(.labelMedium) + .foregroundStyle(.gray300) + .lineLimit(1) + } + TextField("", text: $text) + .pretendardFont(.labelMedium) + .foregroundStyle(.gray800) + } + .padding(.horizontal, 8) + .frame(height: height) + .frame(maxWidth: .infinity) + .pickeCard(.beige50, border: .beige600) + } +} diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift b/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift new file mode 100644 index 00000000..2c1f5d7b --- /dev/null +++ b/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift @@ -0,0 +1,57 @@ +// +// PickeBoxSegment.swift +// PickeDesignKit +// + +import SwiftUI + +/// 회색 트랙 위에서 한 칸이 beige 카드로 떠오르는 박스형 세그먼트. +public struct PickeBoxSegment: View { + private let items: [Item] + @Binding private var selection: Item + private let title: (Item) -> String + + public init( + _ items: [Item], + selection: Binding, + title: @escaping (Item) -> String + ) { + self.items = items + self._selection = selection + self.title = title + } + + public var body: some View { + HStack(spacing: 0) { + ForEach(items, id: \.self) { item in + segment(item) + } + } + .padding(4) + .roundedBackground(.gray50) + } +} + +private extension PickeBoxSegment { + @ViewBuilder + func segment(_ item: Item) -> some View { + let isSelected = selection == item + Button { + selection = item + } label: { + Text(title(item)) + .pretendardFont(isSelected ? .headingSmall : .bodyMedium) + .foregroundStyle(isSelected ? .primary500 : .gray300) + .frame(maxWidth: .infinity) + .frame(height: 36) + .background { + if isSelected { + RoundedRectangle(cornerRadius: .radiusDefault) + .fill(.beige50) + } + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } +} From b28ea2fa156fba7b38b12387bfad7f57a8263f58 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:13:48 +0900 Subject: [PATCH 10/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=9C=C2=B7=EC=A1=B0=EA=B1=B4=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 찾고 싶은 주제, 대상 수준, 카테고리를 골라 추천 조건을 만든다 - 인트로의 새 클래스 만들기에서 조건 선택 화면으로 이동 - 카테고리 토글과 검색 조건 전달 테스트 추가 --- .../Navigation/Class/ClassCoordinator.swift | 17 ++- .../Class/ClassCoordinatorView.swift | 3 + .../Reducer/ClassTopicFeature.swift | 85 ++++++++++++ .../ClassTopic/View/ClassTopicView.swift | 126 ++++++++++++++++++ .../Class/Tests/Sources/ClassTests.swift | 33 +++++ 5 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 Projects/Feature/Class/Sources/ClassTopic/Reducer/ClassTopicFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index e1ff70f9..f85601fb 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -59,10 +59,20 @@ public struct ClassCoordinator { private extension ClassCoordinator { func routerAction( - state _: inout State, - action _: IndexedRouterActionOf + state: inout State, + action: IndexedRouterActionOf ) -> Effect { - return .none + switch action { + case .routeAction(_, action: .intro(.delegate(.create))): + state.routes.push(.topic(.init())) + return .none + + case .routeAction(_, action: .topic(.delegate(.dismiss))): + return .send(.view(.backAction)) + + default: + return .none + } } func handleViewAction( @@ -86,6 +96,7 @@ extension ClassCoordinator { @Reducer public enum ClassScreen { case intro(ClassIntroFeature) + case topic(ClassTopicFeature) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index 1e153c7c..ea5c37d0 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -21,6 +21,9 @@ public struct ClassCoordinatorView: View { switch screen.case { case let .intro(introStore): ClassIntroView(store: introStore) + + case let .topic(topicStore): + ClassTopicView(store: topicStore) } } } diff --git a/Projects/Feature/Class/Sources/ClassTopic/Reducer/ClassTopicFeature.swift b/Projects/Feature/Class/Sources/ClassTopic/Reducer/ClassTopicFeature.swift new file mode 100644 index 00000000..26ed83d1 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassTopic/Reducer/ClassTopicFeature.swift @@ -0,0 +1,85 @@ +// +// ClassTopicFeature.swift +// Class +// + +import Foundation + +import ClassDomainInterface +import ComposableArchitecture + +@Reducer +public struct ClassTopicFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public var keyword: String = "" + public var level: ClassAudienceLevel = .middle + public var category: ClassCategory? + + public init() {} + + public var filter: ClassTopicFilter { + ClassTopicFilter( + keyword: keyword.trimmingCharacters(in: .whitespacesAndNewlines), + level: level, + category: category + ) + } + } + + public enum Action: ViewAction, BindableAction { + case binding(BindingAction) + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View { + case backTapped + case categoryTapped(ClassCategory) + case searchTapped + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case search(ClassTopicFilter) + } + + public var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: + return .none + + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + + case .delegate: + return .none + } + } + } +} + +extension ClassTopicFeature { + private func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .backTapped: + return .send(.delegate(.dismiss)) + + case let .categoryTapped(category): + state.category = state.category == category ? nil : category + return .none + + case .searchTapped: + return .send(.delegate(.search(state.filter))) + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift new file mode 100644 index 00000000..b6920a31 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift @@ -0,0 +1,126 @@ +// +// ClassTopicView.swift +// Class +// + +import SwiftUI + +import ClassDomainInterface +import ComposableArchitecture +import PickeDesignKit + +@ViewAction(for: ClassTopicFeature.self) +public struct ClassTopicView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar( + onBack: { send(.backTapped) }, + centerTitle: "새 클래스 만들기" + ) + .foregroundStyle(.gray500) + + ScrollView { + VStack(alignment: .leading, spacing: 32) { + titleSection() + formSection() + } + .padding(16) + } + .scrollIndicators(.hidden) + .scrollBounceBehavior(.basedOnSize) + + searchButton() + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .screenBackground() + .hidesSystemBars() + } +} + +private extension ClassTopicView { + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 6) { + Text("어떤 주제로\n이야기 나눌까요?") + .pretendardFont(.semiBold24) + .lineSpacing(7) + .foregroundStyle(.gray800) + + Text("관심 있는 주제나 수업 조건을 선택해 주세요.") + .pretendardFont(.medium15) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func formSection() -> some View { + VStack(alignment: .leading, spacing: 20) { + field("찾고 싶은 주제") { + PickeTextField( + "예) 촉법소년 · AI가 인간의 일을 대신해도 될까?", + text: $store.keyword + ) + } + + field("대상 수준") { + PickeBoxSegment( + ClassAudienceLevel.allCases, + selection: $store.level, + title: \.title + ) + } + + field("카테고리") { + categoryGrid() + } + } + } + + @ViewBuilder + func field( + _ label: String, + @ViewBuilder content: () -> some View + ) -> some View { + VStack(alignment: .leading, spacing: 12) { + Text(label) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + content() + } + } + + @ViewBuilder + func categoryGrid() -> some View { + LazyVGrid( + columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), + spacing: 8 + ) { + ForEach(ClassCategory.allCases, id: \.self) { category in + Button { + send(.categoryTapped(category)) + } label: { + Text(category.title) + .pickeChoiceChip(isSelected: store.category == category) + } + .buttonStyle(.plain) + } + } + } + + @ViewBuilder + func searchButton() -> some View { + Button { + send(.searchTapped) + } label: { + Text("조건에 맞는 콘텐츠 보기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassTests.swift b/Projects/Feature/Class/Tests/Sources/ClassTests.swift index d849642b..5bf545a0 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassTests.swift @@ -4,6 +4,7 @@ // @testable import Class +import ClassDomainInterface import ComposableArchitecture import Testing @@ -28,4 +29,36 @@ struct ClassTests { await store.send(.view(.createTapped)) await store.receive(\.delegate.create) } + + @Test + func 같은_카테고리를_다시_누르면_선택이_해제된다() async { + let store = TestStore(initialState: ClassTopicFeature.State()) { + ClassTopicFeature() + } + + await store.send(.view(.categoryTapped(.philosophy))) { + $0.category = .philosophy + } + await store.send(.view(.categoryTapped(.philosophy))) { + $0.category = nil + } + } + + @Test + func 콘텐츠_보기_탭하면_선택한_조건으로_검색_델리게이트를_보낸다() async { + var state = ClassTopicFeature.State() + state.keyword = " 촉법소년 " + state.level = .high + state.category = .society + let store = TestStore(initialState: state) { + ClassTopicFeature() + } + + await store.send(.view(.searchTapped)) + await store.receive(\.delegate.search, ClassTopicFilter( + keyword: "촉법소년", + level: .high, + category: .society + )) + } } From e7974636094915e4f8dbc720bbe2e31c5e887f5b Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:22:00 +0900 Subject: [PATCH 11/40] =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EC=99=80=20=EB=B0=95=EC=8A=A4=ED=98=95=20=EC=84=B8=EA=B7=B8?= =?UTF-8?q?=EB=A8=BC=ED=8A=B8=EB=A5=BC=20=EB=AA=A8=EB=94=94=ED=8C=8C?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=B2=B4=EC=9D=B4=EB=8B=9D=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EA=BE=BC=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PickeTextField 구조체 → TextField(prompt:) + .pickeTextField() - PickeBoxSegment 구조체 → .pickeBoxSegment(isSelected:) + .pickeBoxSegmentTrack() - 클래스 주제 화면 호출부 교체 --- .../ClassTopic/View/ClassTopicView.swift | 38 +++++++----- .../Sources/UI/Input/PickeTextField.swift | 41 +++--------- .../UI/Navigaion/PickeBoxSegment.swift | 62 +++++-------------- 3 files changed, 49 insertions(+), 92 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift index b6920a31..3329a0a8 100644 --- a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift +++ b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift @@ -3,11 +3,10 @@ // Class // -import SwiftUI - import ClassDomainInterface import ComposableArchitecture import PickeDesignKit +import SwiftUI @ViewAction(for: ClassTopicFeature.self) public struct ClassTopicView: View { @@ -45,7 +44,6 @@ public struct ClassTopicView: View { } private extension ClassTopicView { - @ViewBuilder func titleSection() -> some View { VStack(alignment: .leading, spacing: 6) { Text("어떤 주제로\n이야기 나눌까요?") @@ -59,22 +57,20 @@ private extension ClassTopicView { } } - @ViewBuilder func formSection() -> some View { VStack(alignment: .leading, spacing: 20) { field("찾고 싶은 주제") { - PickeTextField( - "예) 촉법소년 · AI가 인간의 일을 대신해도 될까?", - text: $store.keyword + TextField( + "", + text: $store.keyword, + prompt: Text("예) 촉법소년 · AI가 인간의 일을 대신해도 될까?") + .foregroundStyle(.gray300) ) + .pickeTextField() } field("대상 수준") { - PickeBoxSegment( - ClassAudienceLevel.allCases, - selection: $store.level, - title: \.title - ) + levelSegment() } field("카테고리") { @@ -83,7 +79,6 @@ private extension ClassTopicView { } } - @ViewBuilder func field( _ label: String, @ViewBuilder content: () -> some View @@ -96,7 +91,21 @@ private extension ClassTopicView { } } - @ViewBuilder + func levelSegment() -> some View { + HStack(spacing: 0) { + ForEach(ClassAudienceLevel.allCases, id: \.self) { level in + Button { + store.level = level + } label: { + Text(level.title) + .pickeBoxSegment(isSelected: store.level == level) + } + .buttonStyle(.plain) + } + } + .pickeBoxSegmentTrack() + } + func categoryGrid() -> some View { LazyVGrid( columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), @@ -114,7 +123,6 @@ private extension ClassTopicView { } } - @ViewBuilder func searchButton() -> some View { Button { send(.searchTapped) diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift b/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift index b67fcd56..35bcb660 100644 --- a/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift +++ b/Projects/UI/PickeDesignKit/Sources/UI/Input/PickeTextField.swift @@ -5,37 +5,14 @@ import SwiftUI -/// 한 줄 입력 필드. beige50 배경 + beige600 테두리, 비어 있으면 gray300 플레이스홀더. -public struct PickeTextField: View { - private let placeholder: String - @Binding private var text: String - private let height: CGFloat - - public init( - _ placeholder: String, - text: Binding, - height: CGFloat = 44 - ) { - self.placeholder = placeholder - self._text = text - self.height = height - } - - public var body: some View { - ZStack(alignment: .leading) { - if text.isEmpty { - Text(placeholder) - .pretendardFont(.labelMedium) - .foregroundStyle(.gray300) - .lineLimit(1) - } - TextField("", text: $text) - .pretendardFont(.labelMedium) - .foregroundStyle(.gray800) - } - .padding(.horizontal, 8) - .frame(height: height) - .frame(maxWidth: .infinity) - .pickeCard(.beige50, border: .beige600) +public extension View { + /// 한 줄 입력 필드. beige50 배경 + beige600 테두리. 플레이스홀더는 `prompt:` 로 넘긴 Text 에 gray300 을 준다. + func pickeTextField(height: CGFloat = 44) -> some View { + pretendardFont(.labelMedium) + .foregroundStyle(.gray800) + .padding(.horizontal, 8) + .frame(height: height) + .frame(maxWidth: .infinity) + .pickeCard(.beige50, border: .beige600) } } diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift b/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift index 2c1f5d7b..e2fbfc83 100644 --- a/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift +++ b/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift @@ -5,53 +5,25 @@ import SwiftUI -/// 회색 트랙 위에서 한 칸이 beige 카드로 떠오르는 박스형 세그먼트. -public struct PickeBoxSegment: View { - private let items: [Item] - @Binding private var selection: Item - private let title: (Item) -> String - - public init( - _ items: [Item], - selection: Binding, - title: @escaping (Item) -> String - ) { - self.items = items - self._selection = selection - self.title = title - } - - public var body: some View { - HStack(spacing: 0) { - ForEach(items, id: \.self) { item in - segment(item) +public extension View { + /// 박스형 세그먼트의 한 칸. 선택 시 beige50 박스 + primary500 텍스트. + func pickeBoxSegment(isSelected: Bool) -> some View { + pretendardFont(isSelected ? .headingSmall : .bodyMedium) + .foregroundStyle(isSelected ? .primary500 : .gray300) + .frame(maxWidth: .infinity) + .frame(height: 36) + .background { + if isSelected { + RoundedRectangle(cornerRadius: .radiusDefault) + .fill(.beige50) + } } - } - .padding(4) - .roundedBackground(.gray50) + .contentShape(Rectangle()) } -} -private extension PickeBoxSegment { - @ViewBuilder - func segment(_ item: Item) -> some View { - let isSelected = selection == item - Button { - selection = item - } label: { - Text(title(item)) - .pretendardFont(isSelected ? .headingSmall : .bodyMedium) - .foregroundStyle(isSelected ? .primary500 : .gray300) - .frame(maxWidth: .infinity) - .frame(height: 36) - .background { - if isSelected { - RoundedRectangle(cornerRadius: .radiusDefault) - .fill(.beige50) - } - } - .contentShape(Rectangle()) - } - .buttonStyle(.plain) + /// 박스형 세그먼트 칸들을 감싸는 gray50 트랙. + func pickeBoxSegmentTrack() -> some View { + padding(4) + .roundedBackground(.gray50) } } From e4ccecb972dab091ac929235fd428893bd863ea8 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:27:43 +0900 Subject: [PATCH 12/40] =?UTF-8?q?sub-view=20=EB=8A=94=20=EC=9E=90=EC=8B=9D?= =?UTF-8?q?=20=EC=88=98=EC=97=90=20=EB=94=B0=EB=9D=BC=20func=20/=20var=20?= =?UTF-8?q?=EB=A1=9C=20=EB=82=98=EB=88=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자식 2개 이상 stack 은 @ViewBuilder func, 단일 뷰는 var 로 AGENTS.md 규칙 변경 - 포맷터가 @ViewBuilder 를 지우지 않도록 redundantViewBuilder 비활성화 - 클래스 인트로·주제 화면에 적용 --- .swiftformat | 1 + AGENTS.md | 58 +++++++------------ .../ClassIntro/View/ClassIntroView.swift | 10 ++-- .../ClassTopic/View/ClassTopicView.swift | 9 ++- 4 files changed, 32 insertions(+), 46 deletions(-) diff --git a/.swiftformat b/.swiftformat index 82430650..8fabc057 100644 --- a/.swiftformat +++ b/.swiftformat @@ -2,3 +2,4 @@ --swiftversion 6.0 --maxwidth 120 --disable redundantReturn +--disable redundantViewBuilder diff --git a/AGENTS.md b/AGENTS.md index 2f39d747..2d1d51aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -148,57 +148,39 @@ public var body: some View { - 한 메서드 안에서 다시 큰 블록이 생기면 더 작게 쪼개기 (재귀 적용) - 공통 컴포넌트는 별도 파일 (`Components/*.swift`) 로 추출 -#### 🧱 `@ViewBuilder` + `private func` — 모든 sub-view 는 함수 형태 (필수) +#### 🧱 sub-view 형태 — 자식 2개 이상 stack 은 `@ViewBuilder func`, 단일 뷰는 `var` (필수) -분리한 sub-view 는 **자식 개수 / 분기 유무와 상관없이 무조건 `@ViewBuilder` + `private func` 형태** 로 통일한다. +분리한 sub-view 는 **자식 개수로 형태를 정한다**. -`private var ...: some View` 형태는 **금지** — 모든 sub-view 는 호출부에서 일관되게 `()` 호출이 보이도록 함수 형태로만 작성한다. +- stack / grid 안에 자식이 **2개 이상** (`ForEach` 반복, `if` 분기 포함) → `@ViewBuilder private func name() -> some View` +- **단일 뷰** (Button 하나, Text 하나, 이미 만든 컴포넌트 하나) → `private var name: some View` +- 파라미터가 필요하면 자식 수와 상관없이 func (`func thumbnail(url:)`) ```swift -// ✅ 다중 자식 / 분기 / 반복 +// ✅ 자식 2개 이상 — @ViewBuilder func @ViewBuilder -private func hotBattlesSection() -> some View { - VStack(alignment: .leading, spacing: 12) { - HomeSectionHeader(title: "지금 뜨는 배틀") { send(.seeMoreTapped(.hotBattles)) } - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 16) { - ForEach(store.hotBattles) { HotBattleCardView(battle: $0) } - } - } +private func titleSection() -> some View { + VStack(alignment: .leading, spacing: 6) { + Text("어떤 주제로\n이야기 나눌까요?") + Text("관심 있는 주제나 수업 조건을 선택해 주세요.") } } -@ViewBuilder -private func thumbnail(url: URL?) -> some View { - if let url { - KFImage(url).resizable().scaledToFill() - } else { - Color.neutral200 +// ✅ 단일 뷰 — var +private var searchButton: some View { + Button { + send(.searchTapped) + } label: { + Text("조건에 맞는 콘텐츠 보기") } + .ctaButtonStyle(.primary, size: .large, height: 52) } -// ✅ 단일 뷰여도 함수 형태로 -@ViewBuilder -private func primaryButton() -> some View { - CustomButton( - action: { send(.primaryButtonTapped) }, - title: "사전 투표하기", - config: CustomButtonConfig.primary(.large, height: 52), - isEnable: store.isPrimaryButtonEnabled - ) -} - -// ❌ 금지 — sub-view 를 var 로 선언 -private var primaryButton: some View { ... } -private var section: some View { VStack { ... } } +// ❌ 금지 — 단일 뷰를 func 로, 다중 자식을 var 로 +private func searchButton() -> some View { Button { ... } } +private var titleSection: some View { VStack { Text(...); Text(...) } } ``` -규칙: -- **모든 sub-view = `@ViewBuilder private func name() -> some View`** (단일 뷰 / 다중 자식 / 분기 무관) -- `private var ...: some View` 패턴 금지 — 호출부에서 `name` vs `name()` 형태 혼재되는 것을 방지 -- `body` 만 `var body: some View` 유지 (View 프로토콜 요구사항) -- body 안에서 호출은 항상 `()` 가 붙은 함수 형태로 — 가독성 통일 - #### 📏 함수 시그니처 — 파라미터 2개 이상이면 멀티 라인 (필수) 함수 파라미터가 **2개 이상이면 각 파라미터를 새 줄에** 풀어 쓴다. 인라인 한 줄 시그니처는 1-파라미터 이하에서만 허용. diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift index 0ecb62a9..1a5e96e6 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -27,8 +27,8 @@ public struct ClassIntroView: View { Spacer(minLength: 24) menuCards() .padding(.bottom, 16) - createButton() - ticketButton() + createButton + ticketButton } .padding(.top, 24) .padding(.horizontal, 16) @@ -84,8 +84,7 @@ private extension ClassIntroView { } } - @ViewBuilder - func createButton() -> some View { + var createButton: some View { Button { send(.createTapped) } label: { @@ -94,8 +93,7 @@ private extension ClassIntroView { .ctaButtonStyle(.primary, size: .large, height: 52) } - @ViewBuilder - func ticketButton() -> some View { + var ticketButton: some View { Button { send(.ticketTapped) } label: { diff --git a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift index 3329a0a8..d637fb12 100644 --- a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift +++ b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift @@ -34,7 +34,7 @@ public struct ClassTopicView: View { .scrollIndicators(.hidden) .scrollBounceBehavior(.basedOnSize) - searchButton() + searchButton .padding(.horizontal, 16) .padding(.bottom, 16) } @@ -44,6 +44,7 @@ public struct ClassTopicView: View { } private extension ClassTopicView { + @ViewBuilder func titleSection() -> some View { VStack(alignment: .leading, spacing: 6) { Text("어떤 주제로\n이야기 나눌까요?") @@ -57,6 +58,7 @@ private extension ClassTopicView { } } + @ViewBuilder func formSection() -> some View { VStack(alignment: .leading, spacing: 20) { field("찾고 싶은 주제") { @@ -79,6 +81,7 @@ private extension ClassTopicView { } } + @ViewBuilder func field( _ label: String, @ViewBuilder content: () -> some View @@ -91,6 +94,7 @@ private extension ClassTopicView { } } + @ViewBuilder func levelSegment() -> some View { HStack(spacing: 0) { ForEach(ClassAudienceLevel.allCases, id: \.self) { level in @@ -106,6 +110,7 @@ private extension ClassTopicView { .pickeBoxSegmentTrack() } + @ViewBuilder func categoryGrid() -> some View { LazyVGrid( columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), @@ -123,7 +128,7 @@ private extension ClassTopicView { } } - func searchButton() -> some View { + var searchButton: some View { Button { send(.searchTapped) } label: { From a813cca78417351ccd450bf90e03bd9cc71d1347 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:37:42 +0900 Subject: [PATCH 13/40] =?UTF-8?q?=EC=9B=90=ED=98=95=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=B0=95=EC=8A=A4=EC=99=80=20=EC=BA=A1=EC=8A=90=ED=98=95=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EB=B1=83=EC=A7=80=EB=A5=BC=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pickeCheckbox(isChecked:) 는 ComponentToken.Checkbox 토큰을 따른다 - pickePill() 은 primary50 캡슐 + primary500 텍스트 --- .../Sources/UI/Badge/PickePill.swift | 18 +++++++++++ .../Sources/UI/Checkbox/PickeCheckbox.swift | 30 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 Projects/UI/PickeDesignKit/Sources/UI/Badge/PickePill.swift create mode 100644 Projects/UI/PickeDesignKit/Sources/UI/Checkbox/PickeCheckbox.swift diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Badge/PickePill.swift b/Projects/UI/PickeDesignKit/Sources/UI/Badge/PickePill.swift new file mode 100644 index 00000000..0d78e5a1 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Sources/UI/Badge/PickePill.swift @@ -0,0 +1,18 @@ +// +// PickePill.swift +// PickeDesignKit +// + +import SwiftUI + +public extension View { + /// 캡슐형 조건 뱃지. primary50 채움 + primary500 텍스트. + func pickePill() -> some View { + pretendardFont(.semiBold12) + .foregroundStyle(.primary500) + .lineLimit(1) + .padding(.horizontal, 8) + .padding(.vertical, 2) + .background(.primary50, in: Capsule()) + } +} diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Checkbox/PickeCheckbox.swift b/Projects/UI/PickeDesignKit/Sources/UI/Checkbox/PickeCheckbox.swift new file mode 100644 index 00000000..978c2002 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Sources/UI/Checkbox/PickeCheckbox.swift @@ -0,0 +1,30 @@ +// +// PickeCheckbox.swift +// PickeDesignKit +// + +import SwiftUI + +public extension View { + /// 원형 체크박스. 체크 아이콘 뷰에 붙이면 선택 시에만 아이콘이 보인다. + func pickeCheckbox( + isChecked: Bool, + size: CGFloat = 20 + ) -> some View { + pretendardFont(.bold10) + .foregroundStyle(.beige50) + .opacity(isChecked ? 1 : 0) + .frame(width: size, height: size) + .background { + Circle() + .fill(isChecked ? ComponentToken.Checkbox.Background.selected : ComponentToken.Checkbox.Background.default) + } + .overlay { + Circle() + .stroke( + isChecked ? ComponentToken.Checkbox.Border.selected : ComponentToken.Checkbox.Border.default, + lineWidth: 1 + ) + } + } +} From 1e5a48a55eabde5978aff97379e0466e9d88d82c Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:37:42 +0900 Subject: [PATCH 14/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EB=B0=B0=ED=8B=80=20=ED=99=94=EB=A9=B4=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 선택한 조건으로 추천 배틀을 불러오고 한 개를 선택한다 - 미리듣기 버튼은 두되 동작은 넣지 않는다 - 주제 화면의 검색 델리게이트를 추천 배틀 화면 push 로 연결한다 --- .../Navigation/Class/ClassCoordinator.swift | 8 + .../Class/ClassCoordinatorView.swift | 6 +- .../Reducer/ClassRecommendFeature.swift | 138 +++++++++++++++++ .../View/ClassRecommendView.swift | 137 +++++++++++++++++ .../View/Components/ClassBattleListCard.swift | 140 ++++++++++++++++++ .../Tests/Sources/ClassRecommendTests.swift | 53 +++++++ 6 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift create mode 100644 Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index f85601fb..3f3845dc 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -70,6 +70,13 @@ private extension ClassCoordinator { case .routeAction(_, action: .topic(.delegate(.dismiss))): return .send(.view(.backAction)) + case let .routeAction(_, action: .topic(.delegate(.search(filter)))): + state.routes.push(.recommend(.init(filter: filter))) + return .none + + case .routeAction(_, action: .recommend(.delegate(.dismiss))): + return .send(.view(.backAction)) + default: return .none } @@ -97,6 +104,7 @@ extension ClassCoordinator { public enum ClassScreen { case intro(ClassIntroFeature) case topic(ClassTopicFeature) + case recommend(ClassRecommendFeature) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index ea5c37d0..c0d4a540 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -3,10 +3,9 @@ // Picke // -import SwiftUI - import ComposableArchitecture import FeatureAssembly +import SwiftUI import TCAFlow public struct ClassCoordinatorView: View { @@ -24,6 +23,9 @@ public struct ClassCoordinatorView: View { case let .topic(topicStore): ClassTopicView(store: topicStore) + + case let .recommend(recommendStore): + ClassRecommendView(store: recommendStore) } } } diff --git a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift new file mode 100644 index 00000000..5d6eaa1e --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift @@ -0,0 +1,138 @@ +// +// ClassRecommendFeature.swift +// Class +// + +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassRecommendFeature { + @Dependency(\.classUseCase) private var classUseCase + + public init() {} + + @ObservableState + public struct State: Equatable { + public var filter: ClassTopicFilter + public var battles: [ClassBattleSummary] = [] + public var selectedBattleId: Int? + public var isLoading = false + + public init(filter: ClassTopicFilter) { + self.filter = filter + } + + public var conditionTitles: [String] { + [filter.level.title, filter.category?.title].compactMap(\.self) + } + + public var resultTitle: String { + "추천 결과 \(battles.count)개" + } + + public var selectedBattle: ClassBattleSummary? { + battles.first { $0.id == selectedBattleId } + } + + public var canSelect: Bool { + selectedBattle != nil + } + } + + public enum Action: ViewAction { + case view(View) + case response(Response) + case delegate(DelegateAction) + } + + @CasePathable + public enum View { + case onAppear + case backTapped + case editConditionTapped + case battleTapped(Int) + case previewTapped(Int) + case selectTapped + } + + @CasePathable + public enum Response: Equatable { + case battles(Result<[ClassBattleSummary], ClassError>) + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case select(ClassBattleSummary) + } + + nonisolated enum CancelID: Hashable { + case fetch + } + + public var body: some Reducer { + Reduce { state, action in + switch action { + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + + case let .response(response): + return handleResponse(state: &state, response: response) + + case .delegate: + return .none + } + } + } +} + +private extension ClassRecommendFeature { + func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .onAppear: + guard state.battles.isEmpty else { return .none } + state.isLoading = true + return .run { [classUseCase, filter = state.filter] send in + let result = await Result { + try await classUseCase.fetchRecommendedBattles(filter: filter) + } + .mapError(ClassError.from) + await send(.response(.battles(result))) + } + .cancellable(id: CancelID.fetch, cancelInFlight: true) + + case .backTapped, .editConditionTapped: + return .send(.delegate(.dismiss)) + + case let .battleTapped(id): + state.selectedBattleId = state.selectedBattleId == id ? nil : id + return .none + + case .previewTapped: + return .none + + case .selectTapped: + guard let battle = state.selectedBattle else { return .none } + return .send(.delegate(.select(battle))) + } + } + + func handleResponse( + state: inout State, + response: Response + ) -> Effect { + switch response { + case let .battles(result): + state.isLoading = false + if case let .success(battles) = result { + state.battles = battles + } + return .none + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift new file mode 100644 index 00000000..9d39e503 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift @@ -0,0 +1,137 @@ +// +// ClassRecommendView.swift +// Class +// + +import ClassDomainInterface +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassRecommendFeature.self) +public struct ClassRecommendView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar( + onBack: { send(.backTapped) }, + centerTitle: "추천 배틀" + ) + .foregroundStyle(.gray500) + + ScrollView { + VStack(alignment: .leading, spacing: 0) { + introSection() + resultSection() + } + .padding(.bottom, 148) + } + .scrollIndicators(.hidden) + } + .overlay(alignment: .bottom) { + bottomSection() + } + .screenBackground() + .hidesSystemBars() + .onAppear { send(.onAppear) } + } +} + +private extension ClassRecommendView { + @ViewBuilder + func introSection() -> some View { + VStack(alignment: .leading, spacing: 20) { + VStack(alignment: .leading, spacing: 6) { + Text("수업에 어울리는\n배틀을 찾았어요") + .pretendardFont(.semiBold24) + .lineSpacing(7) + .foregroundStyle(.gray800) + + Text("미리 듣고 클래스에 사용할 배틀을 골라보세요.") + .pretendardFont(.medium15) + .foregroundStyle(.gray300) + } + + conditionRow() + } + .padding(16) + } + + @ViewBuilder + func conditionRow() -> some View { + HStack(spacing: 8) { + Text("선택한 조건") + .pretendardFont(.medium13) + .foregroundStyle(.gray300) + + HStack(spacing: 6) { + ForEach(store.conditionTitles, id: \.self) { + Text($0).pickePill() + } + } + + Spacer(minLength: 0) + + Button { + send(.editConditionTapped) + } label: { + Text("조건 수정") + .pretendardFont(.semiBold13) + .foregroundStyle(.primary500) + } + .buttonStyle(.plain) + } + } + + @ViewBuilder + func resultSection() -> some View { + VStack(alignment: .leading, spacing: 16) { + Text(store.resultTitle) + .pretendardFont(.headingSmall) + .foregroundStyle(.gray800) + + ForEach(store.battles) { battle in + ClassBattleListCard( + battle: battle, + isSelected: store.selectedBattleId == battle.id, + onPreview: { send(.previewTapped(battle.id)) } + ) + .onTapGesture { send(.battleTapped(battle.id)) } + } + } + .padding([.horizontal, .top], 16) + } + + @ViewBuilder + func bottomSection() -> some View { + VStack(spacing: 0) { + LinearGradient( + colors: [.beige200.opacity(0), .beige200], + startPoint: .top, + endPoint: .bottom + ) + .frame(height: 70) + .allowsHitTesting(false) + + selectButton + .padding(.horizontal, 12) + .padding(.bottom, 16) + .background(.beige200) + } + } + + var selectButton: some View { + Button { + send(.selectTapped) + } label: { + Text("배틀 선택하기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(!store.canSelect) + } +} diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift new file mode 100644 index 00000000..fbb82018 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift @@ -0,0 +1,140 @@ +// +// ClassBattleListCard.swift +// Class +// + +import ClassDomainInterface +import PickeDesignKit +import PickeSharedUI +import SwiftUI + +/// 추천 배틀 목록 카드. 선택 시 primary500 테두리 + 체크박스가 채워진다. +struct ClassBattleListCard: View { + let battle: ClassBattleSummary + let isSelected: Bool + let onPreview: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 12) { + metaRow() + titleSection() + } + versusRow() + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .pickeCard(.beige50, border: isSelected ? .primary500 : .beige600) + .contentShape(Rectangle()) + } +} + +private extension ClassBattleListCard { + @ViewBuilder + func metaRow() -> some View { + HStack(spacing: 10) { + Text("#\(battle.category.title)") + .pickeBadge(.filled, size: .tag) + + Spacer(minLength: 0) + + HStack(spacing: 2) { + Image(systemName: "clock") + .pretendardFont(.labelXSmall) + Text("\(battle.durationMinutes)분") + .pretendardFont(.labelSmall) + } + .foregroundStyle(.gray300) + + previewButton + + Image(systemName: "checkmark") + .pickeCheckbox(isChecked: isSelected) + } + } + + var previewButton: some View { + Button(action: onPreview) { + HStack(spacing: 2) { + Image(systemName: "play.fill") + .pretendardFont(.labelXSmall) + Text("미리듣기") + .pretendardFont(.semiBold12) + } + .foregroundStyle(.primary500) + } + .buttonStyle(.plain) + } + + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(battle.title) + .pretendardFont(.headingSmall) + .foregroundStyle(.gray500) + .lineLimit(1) + + Text(battle.summary) + .pretendardFont(.labelSmall) + .foregroundStyle(.gray200) + .lineLimit(1) + } + } + + @ViewBuilder + func versusRow() -> some View { + HStack(spacing: 8) { + optionSide( + title: battle.optionATitle, + philosopher: battle.philosopherA, + imageURL: battle.philosopherAImageURL + ) + + versusBadge + + optionSide( + title: battle.optionBTitle, + philosopher: battle.philosopherB, + imageURL: battle.philosopherBImageURL + ) + } + } + + @ViewBuilder + func optionSide( + title: String, + philosopher: String, + imageURL: URL? + ) -> some View { + HStack(spacing: 4) { + PickeAvatarView( + imageURL: imageURL?.absoluteString, + fallback: philosopher, + size: 40 + ) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .pretendardFont(.semiBold11) + .foregroundStyle(.gray500) + Text(philosopher) + .pretendardFont(.regular10) + .foregroundStyle(.gray300) + } + .lineLimit(1) + + Spacer(minLength: 0) + } + .padding(8) + .frame(maxWidth: .infinity) + .pickeCard(.beige300, border: .beige600) + } + + var versusBadge: some View { + Text("VS") + .pretendardFont(.bold8) + .foregroundStyle(.gray900) + .frame(width: 24, height: 24) + .background(.secondary200, in: Circle()) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift new file mode 100644 index 00000000..7b926760 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift @@ -0,0 +1,53 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct ClassRecommendTests { + @Test + func 화면이_뜨면_조건에_맞는_추천_배틀을_불러온다() async { + let store = TestStore(initialState: ClassRecommendFeature.State(filter: .init(level: .middle))) { + ClassRecommendFeature() + } + + await store.send(.view(.onAppear)) { + $0.isLoading = true + } + await store.receive(\.response.battles) { + $0.isLoading = false + $0.battles = ClassBattleSummary.mocks.filter { $0.level == .middle } + } + } + + @Test + func 배틀을_선택하고_선택하기를_누르면_선택_델리게이트를_보낸다() async { + let battle = ClassBattleSummary.mocks[0] + var state = ClassRecommendFeature.State(filter: .init()) + state.battles = [battle] + let store = TestStore(initialState: state) { + ClassRecommendFeature() + } + + await store.send(.view(.battleTapped(battle.id))) { + $0.selectedBattleId = battle.id + } + await store.send(.view(.selectTapped)) + await store.receive(\.delegate.select, battle) + } + + @Test + func 선택한_배틀을_다시_누르면_선택이_해제된다() async { + var state = ClassRecommendFeature.State(filter: .init()) + state.battles = ClassBattleSummary.mocks + state.selectedBattleId = 101 + let store = TestStore(initialState: state) { + ClassRecommendFeature() + } + + await store.send(.view(.battleTapped(101))) { + $0.selectedBattleId = nil + } + await store.send(.view(.selectTapped)) + } +} From 64fd9fdfdfb61338c0326d44ba2eb64862c408c3 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:41:38 +0900 Subject: [PATCH 15/40] =?UTF-8?q?=EC=8A=A4=EC=9C=84=EC=B9=98=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=EC=9D=84=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Toggle(...).toggleStyle(.picke) 로 쓰고 toggle 토큰을 따른다 --- .../Sources/UI/Toggle/PickeToggleStyle.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Projects/UI/PickeDesignKit/Sources/UI/Toggle/PickeToggleStyle.swift diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Toggle/PickeToggleStyle.swift b/Projects/UI/PickeDesignKit/Sources/UI/Toggle/PickeToggleStyle.swift new file mode 100644 index 00000000..57e5d511 --- /dev/null +++ b/Projects/UI/PickeDesignKit/Sources/UI/Toggle/PickeToggleStyle.swift @@ -0,0 +1,33 @@ +// +// PickeToggleStyle.swift +// PickeDesignKit +// + +import SwiftUI + +/// 32x18 스위치. `Toggle(...).toggleStyle(.picke)` 로 쓴다. +public struct PickeToggleStyle: ToggleStyle { + public func makeBody(configuration: Configuration) -> some View { + HStack(spacing: 16) { + configuration.label + Spacer(minLength: 0) + Capsule() + .fill(configuration.isOn ? .toggleTrackOn : .toggleTrackOff) + .frame(width: 32, height: 18) + .overlay(alignment: configuration.isOn ? .trailing : .leading) { + Circle() + .fill(.toggleThumbDefault) + .frame(width: 14, height: 14) + .padding(2) + } + .animation(.easeInOut(duration: 0.15), value: configuration.isOn) + .onTapGesture { configuration.isOn.toggle() } + } + } +} + +public extension ToggleStyle where Self == PickeToggleStyle { + static var picke: PickeToggleStyle { + .init() + } +} From 9f57e43e16ddd33c3b7b02bc42c0aca68f93cda5 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:41:38 +0900 Subject: [PATCH 16/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 클래스 명, 참여 마감일, 선택한 배틀, 댓글 필수 여부를 설정한다 - 추천 배틀 선택 델리게이트를 설정 화면 push 로 연결한다 --- .../Navigation/Class/ClassCoordinator.swift | 11 + .../Class/ClassCoordinatorView.swift | 3 + Projects/Feature/Class/Project.swift | 1 + .../Reducer/ClassSettingFeature.swift | 151 +++++++++++++ .../ClassSetting/View/ClassSettingView.swift | 209 ++++++++++++++++++ .../Tests/Sources/ClassSettingTests.swift | 50 +++++ 6 files changed, 425 insertions(+) create mode 100644 Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index 3f3845dc..9aa0d4e4 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -77,6 +77,16 @@ private extension ClassCoordinator { case .routeAction(_, action: .recommend(.delegate(.dismiss))): return .send(.view(.backAction)) + case let .routeAction(_, action: .recommend(.delegate(.select(battle)))): + state.routes.push(.setting(.init(battle: battle))) + return .none + + case .routeAction(_, action: .setting(.delegate(.dismiss))): + return .send(.view(.backAction)) + + case .routeAction(_, action: .setting(.delegate(.created))): + return .send(.view(.backToRootAction)) + default: return .none } @@ -105,6 +115,7 @@ extension ClassCoordinator { case intro(ClassIntroFeature) case topic(ClassTopicFeature) case recommend(ClassRecommendFeature) + case setting(ClassSettingFeature) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index c0d4a540..92fcc85a 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -26,6 +26,9 @@ public struct ClassCoordinatorView: View { case let .recommend(recommendStore): ClassRecommendView(store: recommendStore) + + case let .setting(settingStore): + ClassSettingView(store: settingStore) } } } diff --git a/Projects/Feature/Class/Project.swift b/Projects/Feature/Class/Project.swift index 13896dc5..566eb2b8 100644 --- a/Projects/Feature/Class/Project.swift +++ b/Projects/Feature/Class/Project.swift @@ -12,6 +12,7 @@ let project = Project.makeModule( product: .staticFramework, settings: .settings(), dependencies: [ + .core(.coreUtility), .ui(.designKit), .ui(.sharedUI), .domain(.classroom, .interface), diff --git a/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift b/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift new file mode 100644 index 00000000..648a6afc --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift @@ -0,0 +1,151 @@ +// +// ClassSettingFeature.swift +// Class +// + +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassSettingFeature { + @Dependency(\.classUseCase) private var classUseCase + + public init() {} + + @ObservableState + public struct State: Equatable { + public var battle: ClassBattleSummary + public var name = "" + public var deadline: Date + public var isDeadlineEnabled = true + public var isDatePickerPresented = false + public var requiresComment = false + public var isLoading = false + + public init( + battle: ClassBattleSummary, + deadline: Date = Date().addingTimeInterval(7 * 24 * 60 * 60) + ) { + self.battle = battle + self.deadline = deadline + } + + public var deadlineText: String { + Self.deadlineFormatter.string(from: deadline) + } + + public var canCreate: Bool { + !trimmedName.isEmpty && !isLoading + } + + public var creation: ClassCreation { + ClassCreation( + name: trimmedName, + deadline: isDeadlineEnabled ? deadline : .distantFuture, + battleId: battle.id, + allowsAnonymousOpinion: false, + requiresComment: requiresComment + ) + } + + private var trimmedName: String { + name.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private static let deadlineFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.dateFormat = "yyyy년 MM월 dd일 HH:mm" + return formatter + }() + } + + public enum Action: ViewAction, BindableAction { + case binding(BindingAction) + case view(View) + case response(Response) + case delegate(DelegateAction) + } + + @CasePathable + public enum View { + case backTapped + case deadlineFieldTapped + case createTapped + } + + @CasePathable + public enum Response: Equatable { + case created(Result) + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case created(ClassRoom) + } + + nonisolated enum CancelID: Hashable { + case create + } + + public var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: + return .none + + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + + case let .response(response): + return handleResponse(state: &state, response: response) + + case .delegate: + return .none + } + } + } +} + +private extension ClassSettingFeature { + func handleViewAction( + state: inout State, + action: View + ) -> Effect { + switch action { + case .backTapped: + return .send(.delegate(.dismiss)) + + case .deadlineFieldTapped: + state.isDatePickerPresented.toggle() + return .none + + case .createTapped: + guard state.canCreate else { return .none } + state.isLoading = true + return .run { [classUseCase, creation = state.creation] send in + let result = await Result { + try await classUseCase.createClass(creation) + } + .mapError(ClassError.from) + await send(.response(.created(result))) + } + .cancellable(id: CancelID.create, cancelInFlight: true) + } + } + + func handleResponse( + state: inout State, + response: Response + ) -> Effect { + switch response { + case let .created(result): + state.isLoading = false + guard case let .success(room) = result else { return .none } + return .send(.delegate(.created(room))) + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift new file mode 100644 index 00000000..38546ad7 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift @@ -0,0 +1,209 @@ +// +// ClassSettingView.swift +// Class +// + +import ClassDomainInterface +import ComposableArchitecture +import PickeCoreUtility +import PickeDesignKit +import PickeSharedUI +import SwiftUI + +@ViewAction(for: ClassSettingFeature.self) +public struct ClassSettingView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar( + onBack: { send(.backTapped) }, + centerTitle: "클래스 설정" + ) + .foregroundStyle(.gray500) + + ScrollView { + VStack(alignment: .leading, spacing: 32) { + Text("수업 정보에 맞게\n클래스를 설정해 주세요") + .pretendardFont(.semiBold24) + .lineSpacing(7) + .foregroundStyle(.gray500) + + nameSection() + deadlineSection() + battleSection() + commentToggle + } + .padding(16) + } + .scrollIndicators(.hidden) + .scrollBounceBehavior(.basedOnSize) + + createButton + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .screenBackground() + .hidesSystemBars() + } +} + +private extension ClassSettingView { + @ViewBuilder + func nameSection() -> some View { + VStack(alignment: .leading, spacing: 8) { + sectionLabel("클래스 명") + + TextField( + "", + text: $store.name, + prompt: Text("ex) 2학년 3반 1학기 토론") + .foregroundStyle(.gray300) + ) + .pickeTextField() + } + } + + @ViewBuilder + func deadlineSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + Toggle(isOn: $store.isDeadlineEnabled) { + VStack(alignment: .leading, spacing: 2) { + sectionLabel("참여 마감일") + Text("마감 전까지 학생이 클래스에 참여할 수 있어요.") + .pretendardFont(.bodySmall) + .foregroundStyle(.gray300) + } + } + .toggleStyle(.picke) + + if store.isDeadlineEnabled { + deadlineField + } + + if store.isDeadlineEnabled, store.isDatePickerPresented { + DatePicker( + "", + selection: $store.deadline, + in: Date()..., + displayedComponents: [.date, .hourAndMinute] + ) + .datePickerStyle(.graphical) + .tint(.primary500) + .pickeCard(.beige50, border: .beige600) + } + } + } + + var deadlineField: some View { + Button { + send(.deadlineFieldTapped) + } label: { + Text(store.deadlineText) + .pretendardFont(.labelMedium) + .foregroundStyle(.gray800) + .padding(.leading, 8) + .frame(maxWidth: .infinity, minHeight: 44, alignment: .leading) + .pickeCard(.beige50, border: .beige600) + } + .buttonStyle(.plain) + } + + @ViewBuilder + func battleSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + sectionLabel("선택한 배틀") + selectedBattleCard() + } + } + + @ViewBuilder + func selectedBattleCard() -> some View { + HStack(alignment: .top, spacing: 8) { + PickeRemoteImage(url: store.battle.thumbnailURL) { Color.beige600 } + .frame(width: 76, height: 106) + .clipShape(RoundedRectangle(cornerRadius: .radiusDefault)) + + VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 6) { + Text("#\(store.battle.category.title)") + .pickeBadge(.filled, size: .tag) + Text(store.battle.title) + .pretendardFont(.headingSmall) + .foregroundStyle(.gray500) + .lineLimit(1) + } + + Text(store.battle.summary) + .pretendardFont(.regular13) + .foregroundStyle(.gray400) + .lineLimit(2) + .padding(.horizontal, 2) + } + + HStack(spacing: 6) { + Spacer(minLength: 0) + metaLabel( + systemImage: "clock", + text: "\(store.battle.durationMinutes)분" + ) + metaLabel( + systemImage: "eye", + text: store.battle.viewCount.decimalFormatted + ) + } + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + .pickeCard(.beige50, border: .beige600, radius: 0) + } + + @ViewBuilder + func metaLabel( + systemImage: String, + text: String + ) -> some View { + HStack(spacing: 2) { + Image(systemName: systemImage) + .pretendardFont(.labelXSmall) + Text(text) + .pretendardFont(.labelSmall) + } + .foregroundStyle(.gray300) + } + + var commentToggle: some View { + Toggle(isOn: $store.requiresComment) { + VStack(alignment: .leading, spacing: 2) { + sectionLabel("댓글 달기 필수") + Text("학생들의 생각을 댓글로 확인해요.") + .pretendardFont(.bodySmall) + .foregroundStyle(.gray300) + } + } + .toggleStyle(.picke) + } + + func sectionLabel(_ title: String) -> some View { + Text(title) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + } + + var createButton: some View { + Button { + send(.createTapped) + } label: { + Text("클래스 만들기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(!store.canCreate) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift b/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift new file mode 100644 index 00000000..d0916a47 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift @@ -0,0 +1,50 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Foundation +import Testing + +@MainActor +struct ClassSettingTests { + private let battle = ClassBattleSummary.mocks[0] + private let deadline = Date(timeIntervalSince1970: 1_800_000_000) + + @Test + func 클래스명이_비어있으면_만들기를_누를_수_없다() { + var state = ClassSettingFeature.State(battle: battle, deadline: deadline) + state.name = " " + #expect(state.canCreate == false) + + state.name = "2학년 3반" + #expect(state.canCreate) + } + + @Test + func 마감일을_끄면_마감일_없이_클래스를_만든다() { + var state = ClassSettingFeature.State(battle: battle, deadline: deadline) + state.name = " 2학년 3반 " + state.isDeadlineEnabled = false + + #expect(state.creation.name == "2학년 3반") + #expect(state.creation.deadline == .distantFuture) + #expect(state.creation.battleId == battle.id) + } + + @Test + func 클래스를_만들면_생성_델리게이트를_보낸다() async { + var state = ClassSettingFeature.State(battle: battle, deadline: deadline) + state.name = "2학년 3반" + let store = TestStore(initialState: state) { + ClassSettingFeature() + } + store.exhaustivity = .off + + await store.send(.view(.createTapped)) { + $0.isLoading = true + } + await store.receive(\.response.created) { + $0.isLoading = false + } + await store.receive(\.delegate.created) + } +} From b9d79a3ef9baf3e8f5bcb65b2cc3aadd06c51c9d Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:44:27 +0900 Subject: [PATCH 17/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=EC=99=80=20=EB=82=B4=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 참여 코드 조회와 이름 입력, 내 클래스 필터, 클래스 상세와 멤버 목록 흐름을 추가한다. 기존 Feature Reducer의 view/async/inner/delegate 구분을 따른다. --- .../Reducer/ClassDetailFeature.swift | 94 ++++++++++ .../ClassDetail/View/ClassDetailView.swift | 166 +++++++++++++++++ .../ClassJoin/Reducer/ClassJoinFeature.swift | 168 ++++++++++++++++++ .../ClassJoin/View/ClassJoinView.swift | 150 ++++++++++++++++ .../Reducer/ClassMemberFeature.swift | 84 +++++++++ .../ClassMember/View/ClassMemberView.swift | 73 ++++++++ .../MyClass/Reducer/MyClassFeature.swift | 137 ++++++++++++++ .../Sources/MyClass/View/MyClassView.swift | 150 ++++++++++++++++ .../Tests/Sources/ClassDetailTests.swift | 49 +++++ .../Class/Tests/Sources/ClassJoinTests.swift | 116 ++++++++++++ .../Tests/Sources/ClassMemberTests.swift | 34 ++++ .../Class/Tests/Sources/MyClassTests.swift | 36 ++++ 12 files changed, 1257 insertions(+) create mode 100644 Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift create mode 100644 Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift create mode 100644 Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift create mode 100644 Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift create mode 100644 Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift create mode 100644 Projects/Feature/Class/Tests/Sources/MyClassTests.swift diff --git a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift new file mode 100644 index 00000000..8efca48c --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift @@ -0,0 +1,94 @@ +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassDetailFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public let room: ClassRoom + public var deadline: Date + public var draftDeadline: Date + public var isDeadlineSheetPresented = false + public var isDeleteAlertPresented = false + + public init(room: ClassRoom) { + self.room = room + deadline = room.deadline + draftDeadline = room.deadline + } + } + + public enum Action: ViewAction, BindableAction { + case binding(BindingAction) + case view(View) + case delegate(DelegateAction) + } + + public enum View { + case backTapped + case codeShareTapped + case membersTapped + case battleTapped + case deadlineTapped + case deadlineSaved + case deleteTapped + case deleteConfirmed + } + + public enum DelegateAction: Equatable { + case dismiss + case shareCode(String) + case openMembers(ClassRoom) + case openBattle(ClassBattleSummary) + case deleted(Int) + } + + public var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: + return .none + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + case .delegate: + return .none + } + } + } +} + +extension ClassDetailFeature { + private func handleViewAction(state: inout State, action: View) -> Effect { + switch action { + case .backTapped: + return .send(.delegate(.dismiss)) + case .codeShareTapped: + return .send(.delegate(.shareCode(state.room.joinCode))) + case .membersTapped: + return .send(.delegate(.openMembers(state.room))) + case .battleTapped: + return .send(.delegate(.openBattle(state.room.battle))) + case .deadlineTapped: + guard state.room.role == .owner else { return .none } + state.draftDeadline = state.deadline + state.isDeadlineSheetPresented = true + return .none + case .deadlineSaved: + state.deadline = state.draftDeadline + state.isDeadlineSheetPresented = false + return .none + case .deleteTapped: + guard state.room.role == .owner else { return .none } + state.isDeleteAlertPresented = true + return .none + case .deleteConfirmed: + guard state.room.role == .owner else { return .none } + state.isDeleteAlertPresented = false + return .send(.delegate(.deleted(state.room.id))) + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift new file mode 100644 index 00000000..c01de3f8 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift @@ -0,0 +1,166 @@ +import ClassDomainInterface +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassDetailFeature.self) +public struct ClassDetailView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "클래스 상세") + .foregroundStyle(.gray500) + + ScrollView { + VStack(alignment: .leading, spacing: 24) { + headerSection() + classInformation() + battleSection() + if store.room.role == .owner { + deleteButton + } + } + .padding(16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .hidesSystemBars() + .sheet(isPresented: $store.isDeadlineSheetPresented) { + deadlineSheet() + .presentationDetents([.height(320)]) + .presentationDragIndicator(.visible) + } + .alert("클래스를 삭제할까요?", isPresented: $store.isDeleteAlertPresented) { + Button("취소", role: .cancel) {} + Button("삭제", role: .destructive) { send(.deleteConfirmed) } + } message: { + Text("삭제한 클래스는 다시 복구할 수 없어요.") + } + } +} + +private extension ClassDetailView { + @ViewBuilder + func headerSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text(store.room.status == .open ? "진행 중" : "종료") + .pretendardFont(.medium13) + .foregroundStyle(.gray500) + + Text(store.room.name) + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + + HStack(spacing: 16) { + Text("멤버 \(store.room.memberCount)명") + .pretendardFont(.medium15) + .foregroundStyle(.gray500) + + if store.room.role == .owner { + Button("멤버 보기") { send(.membersTapped) } + .pretendardFont(.semiBold15) + } + } + } + } + + @ViewBuilder + func classInformation() -> some View { + VStack(alignment: .leading, spacing: 16) { + informationRow("참여 코드", value: store.room.joinCode) + + if store.room.role == .owner { + Button("코드 공유하기") { send(.codeShareTapped) } + .pretendardFont(.semiBold15) + } + + informationRow( + "마감일", + value: store.deadline.formatted(date: .numeric, time: .shortened) + ) + + if store.room.role == .owner { + Button("마감일 수정") { send(.deadlineTapped) } + .pretendardFont(.semiBold15) + } + + informationRow("익명 의견", value: store.room.allowsAnonymousOpinion ? "허용" : "허용 안 함") + informationRow("댓글 필수", value: store.room.requiresComment ? "필수" : "선택") + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.white, in: RoundedRectangle(cornerRadius: 12)) + } + + @ViewBuilder + func informationRow(_ title: String, value: String) -> some View { + HStack(alignment: .top, spacing: 16) { + Text(title) + .pretendardFont(.medium15) + .foregroundStyle(.gray300) + .frame(width: 80, alignment: .leading) + + Text(value) + .pretendardFont(.medium15) + .foregroundStyle(.gray800) + } + } + + @ViewBuilder + func battleSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text("이번 클래스 배틀") + .pretendardFont(.bold18) + .foregroundStyle(.gray800) + + Button { send(.battleTapped) } label: { + VStack(alignment: .leading, spacing: 8) { + Text(store.room.battle.title) + .pretendardFont(.bold18) + .foregroundStyle(.gray800) + Text(store.room.battle.summary) + .pretendardFont(.medium15) + .foregroundStyle(.gray500) + .lineLimit(2) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.white, in: RoundedRectangle(cornerRadius: 12)) + } + .buttonStyle(.plain) + } + } + + var deleteButton: some View { + Button("클래스 삭제") { send(.deleteTapped) } + .pretendardFont(.medium15) + .foregroundStyle(.textError) + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + } + + @ViewBuilder + func deadlineSheet() -> some View { + VStack(alignment: .leading, spacing: 20) { + Text("마감일 수정") + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + + DatePicker("마감일", selection: $store.draftDeadline, displayedComponents: [.date, .hourAndMinute]) + .datePickerStyle(.compact) + + Spacer(minLength: 0) + + Button("수정하기") { send(.deadlineSaved) } + .ctaButtonStyle(.primary, size: .large, height: 52) + } + .padding(20) + .screenBackground() + } +} diff --git a/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift b/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift new file mode 100644 index 00000000..e00625c0 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift @@ -0,0 +1,168 @@ +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassJoinFeature { + @Dependency(\.classUseCase) private var classUseCase + + public init() {} + + @ObservableState + public struct State: Equatable { + public var joinCode = "" + public var nickname = "" + public var preview: ClassRoom? + public var isLoading = false + public var errorMessage: String? + + public init() {} + + public var canFindClass: Bool { + !joinCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading + } + + public var canJoin: Bool { + preview != nil && !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading + } + } + + public enum Action: ViewAction, BindableAction { + case binding(BindingAction) + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case delegate(DelegateAction) + } + + @CasePathable + public enum View { + case backTapped + case findTapped + case joinTapped + case previewDismissed + } + + public enum AsyncAction: Equatable { + case find(String) + case join(joinCode: String, nickname: String) + } + + public enum InnerAction: Equatable { + case found(Result) + case joined(Result) + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case joined(ClassRoom) + } + + nonisolated enum CancelID: Hashable { + case find + case join + } + + public var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: + return .none + + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + + case let .async(asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case let .inner(innerAction): + return handleInnerAction(state: &state, action: innerAction) + + case .delegate: + return .none + } + } + } +} + +private extension ClassJoinFeature { + func handleViewAction(state: inout State, action: View) -> Effect { + switch action { + case .backTapped: + return .send(.delegate(.dismiss)) + + case .findTapped: + guard state.canFindClass else { return .none } + let code = state.joinCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + return .send(.async(.find(code))) + + case .joinTapped: + guard state.canJoin, let room = state.preview else { return .none } + let nickname = state.nickname.trimmingCharacters(in: .whitespacesAndNewlines) + return .send(.async(.join(joinCode: room.joinCode, nickname: nickname))) + + case .previewDismissed: + state.preview = nil + state.nickname = "" + return .none + } + } + + func handleAsyncAction(state: inout State, action: AsyncAction) -> Effect { + switch action { + case let .find(code): + state.isLoading = true + state.errorMessage = nil + return .run { [classUseCase] send in + let result = await Result { + try await classUseCase.fetchClass(joinCode: code) + } + .mapError(ClassError.from) + await send(.inner(.found(result))) + } + .cancellable(id: CancelID.find, cancelInFlight: true) + + case let .join(code, nickname): + state.isLoading = true + state.errorMessage = nil + return .run { [classUseCase] send in + let result = await Result { + try await classUseCase.joinClass(joinCode: code, nickname: nickname) + } + .mapError(ClassError.from) + await send(.inner(.joined(result))) + } + .cancellable(id: CancelID.join, cancelInFlight: true) + } + } + + func handleInnerAction(state: inout State, action: InnerAction) -> Effect { + state.isLoading = false + switch action { + case let .found(.success(room)): + state.preview = room + return .none + + case let .joined(.success(room)): + state.preview = nil + return .send(.delegate(.joined(room))) + + case let .found(.failure(error)), let .joined(.failure(error)): + state.errorMessage = error.message + return .none + } + } +} + +private extension ClassError { + var message: String { + switch self { + case .invalidCode: return "참여 코드를 다시 확인해 주세요." + case .closed: return "종료된 클래스입니다." + case .alreadyJoined: return "이미 참여한 클래스입니다." + case let .network(message), let .unknown(message): return message + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift b/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift new file mode 100644 index 00000000..31eca21b --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift @@ -0,0 +1,150 @@ +// +// ClassJoinView.swift +// Class +// + +import ClassDomainInterface +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassJoinFeature.self) +public struct ClassJoinView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar( + onBack: { send(.backTapped) }, + centerTitle: "클래스 참여하기" + ) + .foregroundStyle(.gray500) + + ScrollView { + contentSection() + .padding(16) + } + .scrollIndicators(.hidden) + + primaryButton + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .screenBackground() + .hidesSystemBars() + } +} + +private extension ClassJoinView { + @ViewBuilder + func contentSection() -> some View { + VStack(alignment: .leading, spacing: 28) { + titleSection() + + if store.preview == nil { + codeSection() + } else { + previewSection() + nicknameSection() + } + + if let errorMessage = store.errorMessage { + Text(errorMessage) + .pretendardFont(.bodySmall) + .foregroundStyle(.errorDefault) + } + } + } + + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 8) { + Text(store.preview == nil ? "참여 코드를 입력해주세요" : "이름을 입력해주세요") + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + + Text( + store.preview == nil + ? "선생님에게 받은 코드를 입력하면 클래스에 참여할 수 있어요." + : "이 클래스에서만 보이는 이름이에요." + ) + .pretendardFont(.medium15) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func codeSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text("참여 코드") + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + + TextField( + "예) PK9T3S", + text: $store.joinCode + ) + .textInputAutocapitalization(.characters) + .autocorrectionDisabled() + .pickeTextField() + } + } + + @ViewBuilder + func previewSection() -> some View { + if let preview = store.preview { + VStack(alignment: .leading, spacing: 12) { + Text("참여할 클래스") + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + + VStack(alignment: .leading, spacing: 8) { + Text(preview.name) + .pretendardFont(family: .SemiBold, size: 18) + .foregroundStyle(.gray800) + + Text(preview.battle.title) + .pretendardFont(.medium15) + .foregroundStyle(.gray500) + + Text("현재 (preview.memberCount)명이 참여 중") + .pretendardFont(.medium13) + .foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + .pickeCard(.beige50, border: .beige600) + } + } + } + + @ViewBuilder + func nicknameSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text("이름") + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + + TextField( + "입력해주세요", + text: $store.nickname + ) + .textInputAutocapitalization(.never) + .pickeTextField() + } + } + + var primaryButton: some View { + Button { + send(store.preview == nil ? .findTapped : .joinTapped) + } label: { + Text(store.preview == nil ? "클래스 찾기" : "참여하기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(store.preview == nil ? !store.canFindClass : !store.canJoin) + } +} diff --git a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift new file mode 100644 index 00000000..5825f552 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift @@ -0,0 +1,84 @@ +import ClassDomainInterface +import ComposableArchitecture + +@Reducer +public struct ClassMemberFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public struct Member: Equatable, Identifiable { + public let id: Int + public let name: String + public let isOwner: Bool + + public init(id: Int, name: String, isOwner: Bool = false) { + self.id = id + self.name = name + self.isOwner = isOwner + } + } + + public let room: ClassRoom + public var members: [Member] + public var selectedMember: Member? + + public init(room: ClassRoom) { + self.room = room + members = [Member(id: 1, name: "선생님", isOwner: true)] + + (0 ..< max(0, room.memberCount - 1)).map { index in + Member(id: index + 2, name: "학생 \(index + 1)") + } + } + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + public enum View { + case backTapped + case removeTapped(Int) + case removeConfirmed + case removeCancelled + } + + public enum DelegateAction: Equatable { + case dismiss + } + + public var body: some Reducer { + Reduce { state, action in + switch action { + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + case .delegate: + return .none + } + } + } +} + +extension ClassMemberFeature { + private func handleViewAction(state: inout State, action: View) -> Effect { + switch action { + case .backTapped: + return .send(.delegate(.dismiss)) + case let .removeTapped(id): + guard state.room.role == .owner, + let member = state.members.first(where: { $0.id == id && !$0.isOwner }) + else { return .none } + state.selectedMember = member + return .none + case .removeConfirmed: + guard let member = state.selectedMember else { return .none } + state.members.removeAll(where: { $0.id == member.id }) + state.selectedMember = nil + return .none + case .removeCancelled: + state.selectedMember = nil + return .none + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift new file mode 100644 index 00000000..55201c76 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -0,0 +1,73 @@ +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassMemberFeature.self) +public struct ClassMemberView: View { + public let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "멤버 \(store.members.count)") + .foregroundStyle(.gray500) + + ScrollView { + LazyVStack(spacing: 0) { + ForEach(store.members) { member in + memberRow(member) + } + } + .padding(.horizontal, 16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .hidesSystemBars() + .alert( + "멤버를 삭제할까요?", + isPresented: Binding( + get: { store.selectedMember != nil }, + set: { + if !$0 { + send(.removeCancelled) + } + } + ) + ) { + Button("취소", role: .cancel) { send(.removeCancelled) } + Button("삭제", role: .destructive) { send(.removeConfirmed) } + } message: { + Text("삭제한 멤버는 참여 코드를 통해 다시 입장할 수 있어요.") + } + } +} + +private extension ClassMemberView { + @ViewBuilder + func memberRow(_ member: ClassMemberFeature.State.Member) -> some View { + HStack(spacing: 12) { + Text(member.name) + .pretendardFont(.medium15) + .foregroundStyle(.gray800) + + if member.isOwner { + Text("선생님") + .pretendardFont(.medium13) + .foregroundStyle(.gray300) + } + + Spacer(minLength: 0) + + if store.room.role == .owner, !member.isOwner { + Button("삭제") { send(.removeTapped(member.id)) } + .pretendardFont(.medium13) + .foregroundStyle(.textError) + } + } + .padding(.vertical, 16) + } +} diff --git a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift new file mode 100644 index 00000000..c29aa7cc --- /dev/null +++ b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift @@ -0,0 +1,137 @@ +import ClassDomainInterface +import ComposableArchitecture + +@Reducer +public struct MyClassFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public enum Ownership: Equatable, CaseIterable { + case all + case created + case joined + } + + public enum Progress: Equatable, CaseIterable { + case all + case open + case closed + } + + public var rooms: [ClassRoom] = [] + public var ownership: Ownership = .all + public var progress: Progress = .all + public var isLoading = false + public var errorMessage: String? + + public init() {} + + public var visibleRooms: [ClassRoom] { + rooms.filter { room in + let matchesOwnership: Bool = switch ownership { + case .all: true + case .created: room.role == .owner + case .joined: room.role == .member + } + let matchesProgress: Bool = switch progress { + case .all: true + case .open: room.status == .open + case .closed: room.status == .closed + } + return matchesOwnership && matchesProgress + } + } + } + + public enum Action: ViewAction { + case view(View) + case async(AsyncAction) + case inner(InnerAction) + case delegate(DelegateAction) + } + + public enum View { + case onAppear + case backTapped + case ownershipTapped(State.Ownership) + case progressTapped(State.Progress) + case roomTapped(Int) + } + + public enum AsyncAction: Equatable { + case fetchRooms + } + + public enum InnerAction: Equatable { + case roomsResponse(Result<[ClassRoom], ClassError>) + } + + public enum DelegateAction: Equatable { + case dismiss + case openRoom(ClassRoom) + } + + @Dependency(\.classUseCase) private var classUseCase + + public var body: some Reducer { + Reduce { state, action in + switch action { + case let .view(viewAction): + return handleViewAction(state: &state, action: viewAction) + case let .async(asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + case let .inner(innerAction): + return handleInnerAction(state: &state, action: innerAction) + case .delegate: + return .none + } + } + } +} + +extension MyClassFeature { + private func handleViewAction(state: inout State, action: View) -> Effect { + switch action { + case .onAppear: + return .send(.async(.fetchRooms)) + case .backTapped: + return .send(.delegate(.dismiss)) + case let .ownershipTapped(ownership): + state.ownership = ownership + return .none + case let .progressTapped(progress): + state.progress = progress + return .none + case let .roomTapped(id): + guard let room = state.rooms.first(where: { $0.id == id }) else { return .none } + return .send(.delegate(.openRoom(room))) + } + } + + private func handleAsyncAction(state: inout State, action: AsyncAction) -> Effect { + switch action { + case .fetchRooms: + state.isLoading = true + state.errorMessage = nil + return .run { [classUseCase] send in + let result = await Result { + try await classUseCase.fetchMyClasses() + } + .mapError(ClassError.from) + await send(.inner(.roomsResponse(result))) + } + } + } + + private func handleInnerAction(state: inout State, action: InnerAction) -> Effect { + state.isLoading = false + switch action { + case let .roomsResponse(.success(rooms)): + state.rooms = rooms + case let .roomsResponse(.failure(error)): + state.errorMessage = String(describing: error) + } + return .none + } +} diff --git a/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift b/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift new file mode 100644 index 00000000..ab7c709c --- /dev/null +++ b/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift @@ -0,0 +1,150 @@ +import ClassDomainInterface +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: MyClassFeature.self) +public struct MyClassView: View { + public let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "내 클래스") + .foregroundStyle(.gray500) + + ownershipTabs() + + ScrollView { + VStack(alignment: .leading, spacing: 16) { + progressChips() + roomList() + } + .padding(16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .hidesSystemBars() + .onAppear { send(.onAppear) } + } +} + +private extension MyClassView { + @ViewBuilder + func ownershipTabs() -> some View { + HStack(spacing: 0) { + ForEach(MyClassFeature.State.Ownership.allCases, id: \.self) { ownership in + Button { send(.ownershipTapped(ownership)) } label: { + Text(ownership.title) + .pretendardFont(.semiBold15) + .foregroundStyle(store.ownership == ownership ? .gray800 : .gray300) + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .overlay(alignment: .bottom) { + if store.ownership == ownership { + Rectangle() + .fill(.gray800) + .frame(height: 2) + } + } + } + .buttonStyle(.plain) + } + } + } + + @ViewBuilder + func progressChips() -> some View { + HStack(spacing: 8) { + ForEach(MyClassFeature.State.Progress.allCases, id: \.self) { progress in + Button { send(.progressTapped(progress)) } label: { + Text(progress.title) + .pickeChoiceChip(isSelected: store.progress == progress) + } + .buttonStyle(.plain) + } + } + } + + @ViewBuilder + func roomList() -> some View { + if store.isLoading { + ProgressView() + .frame(maxWidth: .infinity, minHeight: 160) + } else if let message = store.errorMessage { + Text(message) + .pretendardFont(.medium15) + .foregroundStyle(.textError) + .frame(maxWidth: .infinity, minHeight: 160) + } else if store.visibleRooms.isEmpty { + Text("아직 클래스가 없어요") + .pretendardFont(.medium15) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, minHeight: 160) + } else { + LazyVStack(spacing: 12) { + ForEach(store.visibleRooms) { room in + Button { send(.roomTapped(room.id)) } label: { + roomCard(room) + } + .buttonStyle(.plain) + } + } + } + } + + @ViewBuilder + func roomCard(_ room: ClassRoom) -> some View { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + Text(room.role == .owner ? "내가 만든 클래스" : "참여한 클래스") + .pretendardFont(.medium13) + .foregroundStyle(.gray300) + Spacer(minLength: 0) + Text(room.status == .open ? "진행 중" : "종료") + .pretendardFont(.medium13) + .foregroundStyle(.gray500) + } + + Text(room.name) + .pretendardFont(.bold18) + .foregroundStyle(.gray800) + + Text(room.battle.title) + .pretendardFont(.medium15) + .foregroundStyle(.gray500) + .lineLimit(2) + + Text("멤버 \(room.memberCount)명") + .pretendardFont(.medium13) + .foregroundStyle(.gray300) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.white, in: RoundedRectangle(cornerRadius: 12)) + } +} + +private extension MyClassFeature.State.Ownership { + var title: String { + switch self { + case .all: "전체" + case .created: "내가 만든 클래스" + case .joined: "참여한 클래스" + } + } +} + +private extension MyClassFeature.State.Progress { + var title: String { + switch self { + case .all: "전체" + case .open: "진행 중" + case .closed: "종료" + } + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift new file mode 100644 index 00000000..1675c68f --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift @@ -0,0 +1,49 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Foundation +import Testing + +@MainActor +struct ClassDetailTests { + @Test + func 선생님은_마감일을_수정할_수_있다() async { + let room = ClassRoom.mocks[0] + let store = TestStore(initialState: ClassDetailFeature.State(room: room)) { + ClassDetailFeature() + } + let updated = room.deadline.addingTimeInterval(86400) + + await store.send(.view(.deadlineTapped)) { + $0.isDeadlineSheetPresented = true + } + await store.send(.binding(.set(\.draftDeadline, updated))) { + $0.draftDeadline = updated + } + await store.send(.view(.deadlineSaved)) { + $0.deadline = updated + $0.isDeadlineSheetPresented = false + } + } + + @Test + func 학생은_마감일과_삭제_동작을_사용할_수_없다() async { + let store = TestStore(initialState: ClassDetailFeature.State(room: ClassRoom.mocks[1])) { + ClassDetailFeature() + } + + await store.send(.view(.deadlineTapped)) + await store.send(.view(.deleteTapped)) + } + + @Test + func 배틀을_누르면_현재_클래스의_배틀을_전달한다() async { + let room = ClassRoom.mocks[0] + let store = TestStore(initialState: ClassDetailFeature.State(room: room)) { + ClassDetailFeature() + } + + await store.send(.view(.battleTapped)) + await store.receive(.delegate(.openBattle(room.battle))) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift new file mode 100644 index 00000000..5b16a332 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift @@ -0,0 +1,116 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct ClassJoinTests { + @Test + func 뒤로가기는_닫기_델리게이트를_보낸다() async { + let store = TestStore(initialState: ClassJoinFeature.State()) { + ClassJoinFeature() + } + + await store.send(.view(.backTapped)) + await store.receive(\.delegate.dismiss) + } + + @Test + func 참여코드를_조회하면_클래스_미리보기를_표시한다() async { + let room = ClassRoom.mockJoinable + let store = TestStore(initialState: ClassJoinFeature.State()) { + ClassJoinFeature() + } withDependencies: { + $0.classUseCase = StubClassUseCase(fetchClass: { _ in room }) + } + + await store.send(.binding(.set(\.joinCode, " pk9t3s "))) { + $0.joinCode = " pk9t3s " + } + await store.send(.view(.findTapped)) + await store.receive(.async(.find("PK9T3S"))) { + $0.isLoading = true + } + await store.receive(\.inner.found) { + $0.preview = room + $0.isLoading = false + } + } + + @Test + func 이름을_입력하고_참여하면_클래스_참여_델리게이트를_보낸다() async { + let room = ClassRoom.mockJoinable + var state = ClassJoinFeature.State() + state.preview = room + state.nickname = " 민지 " + let store = TestStore(initialState: state) { + ClassJoinFeature() + } withDependencies: { + $0.classUseCase = StubClassUseCase(joinClass: { code, nickname in + #expect(code == room.joinCode) + #expect(nickname == "민지") + return room + }) + } + + await store.send(.view(.joinTapped)) + await store.receive(.async(.join(joinCode: room.joinCode, nickname: "민지"))) { + $0.isLoading = true + } + await store.receive(\.inner.joined) { + $0.preview = nil + $0.isLoading = false + } + await store.receive(\.delegate.joined) + } + + @Test + func 잘못된_코드_조회는_오류를_상태에_저장한다() async { + let store = TestStore(initialState: ClassJoinFeature.State()) { + ClassJoinFeature() + } withDependencies: { + $0.classUseCase = StubClassUseCase(fetchClass: { _ in + throw ClassError.invalidCode + }) + } + + await store.send(.binding(.set(\.joinCode, "INVALID"))) { + $0.joinCode = "INVALID" + } + await store.send(.view(.findTapped)) + await store.receive(.async(.find("INVALID"))) { + $0.isLoading = true + } + await store.receive(\.inner.found) { + $0.isLoading = false + $0.errorMessage = "참여 코드를 다시 확인해 주세요." + } + } +} + +private struct StubClassUseCase: ClassInterface { + var fetchClass: @Sendable (String) async throws -> ClassRoom = { _ in .mockJoinable } + var joinClass: @Sendable (String, String) async throws -> ClassRoom = { _, _ in .mockJoinable } + + func fetchMyClasses() async throws -> [ClassRoom] { + [] + } + + func fetchRecommendedBattles( + filter _: ClassTopicFilter + ) async throws -> [ClassBattleSummary] { + [] + } + + func createClass(_: ClassCreation) async throws -> ClassRoom { + .mockJoinable + } + + func fetchClass(joinCode: String) async throws -> ClassRoom { + try await fetchClass(joinCode) + } + + func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { + try await joinClass(joinCode, nickname) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift new file mode 100644 index 00000000..518d0b28 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift @@ -0,0 +1,34 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct ClassMemberTests { + @Test + func 선생님은_멤버를_선택하고_삭제할_수_있다() async { + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + ClassMemberFeature() + } + let member = store.state.members[1] + + await store.send(.view(.removeTapped(member.id))) { + $0.selectedMember = member + } + await store.send(.view(.removeConfirmed)) { + $0.members.remove(at: 1) + $0.selectedMember = nil + } + #expect(store.state.members.count == ClassRoom.mocks[0].memberCount - 1) + } + + @Test + func 학생은_다른_멤버를_삭제할_수_없다() async { + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[1])) { + ClassMemberFeature() + } + + await store.send(.view(.removeTapped(2))) + #expect(store.state.selectedMember == nil) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift new file mode 100644 index 00000000..8dc686c4 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift @@ -0,0 +1,36 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct MyClassTests { + @Test + func 소유_역할과_진행_상태를_함께_필터링한다() async { + var state = MyClassFeature.State() + state.rooms = ClassRoom.mocks + let store = TestStore(initialState: state) { + MyClassFeature() + } + + await store.send(.view(.ownershipTapped(.joined))) { + $0.ownership = .joined + } + await store.send(.view(.progressTapped(.open))) { + $0.progress = .open + } + #expect(store.state.visibleRooms.map(\.id) == [2]) + } + + @Test + func 클래스를_누르면_해당_방을_델리게이트로_전달한다() async { + var state = MyClassFeature.State() + state.rooms = ClassRoom.mocks + let store = TestStore(initialState: state) { + MyClassFeature() + } + + await store.send(.view(.roomTapped(1))) + await store.receive(.delegate(.openRoom(ClassRoom.mocks[0]))) + } +} From 7cd812bc78d04a6ef174b20a3850923903de1b23 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:47:40 +0900 Subject: [PATCH 18/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B3=B5?= =?UTF-8?q?=EC=9C=A0=20=ED=99=94=EB=A9=B4=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 클래스 생성 후 참여 코드와 선택한 배틀을 보여주고 ShareLink 로 코드를 공유한다 - 선택한 배틀 카드를 설정 / 공유 화면 공용 컴포넌트로 분리한다 --- .../Navigation/Class/ClassCoordinator.swift | 8 +- .../Class/ClassCoordinatorView.swift | 3 + .../ClassSetting/View/ClassSettingView.swift | 60 +-------- .../Reducer/ClassShareFeature.swift | 71 ++++++++++ .../ClassShare/View/ClassShareView.swift | 124 ++++++++++++++++++ .../Components/ClassSelectedBattleCard.swift | 74 +++++++++++ .../Class/Tests/Sources/ClassShareTests.swift | 45 +++++++ 7 files changed, 325 insertions(+), 60 deletions(-) create mode 100644 Projects/Feature/Class/Sources/ClassShare/Reducer/ClassShareFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassShare/View/ClassShareView.swift create mode 100644 Projects/Feature/Class/Sources/Common/Components/ClassSelectedBattleCard.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassShareTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index 9aa0d4e4..0db97911 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -84,7 +84,12 @@ private extension ClassCoordinator { case .routeAction(_, action: .setting(.delegate(.dismiss))): return .send(.view(.backAction)) - case .routeAction(_, action: .setting(.delegate(.created))): + case let .routeAction(_, action: .setting(.delegate(.created(room)))): + state.routes.push(.share(.init(room: room))) + return .none + + case .routeAction(_, action: .share(.delegate(.dismiss))), + .routeAction(_, action: .share(.delegate(.enterClass))): return .send(.view(.backToRootAction)) default: @@ -116,6 +121,7 @@ extension ClassCoordinator { case topic(ClassTopicFeature) case recommend(ClassRecommendFeature) case setting(ClassSettingFeature) + case share(ClassShareFeature) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index 92fcc85a..36da33fd 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -29,6 +29,9 @@ public struct ClassCoordinatorView: View { case let .setting(settingStore): ClassSettingView(store: settingStore) + + case let .share(shareStore): + ClassShareView(store: shareStore) } } } diff --git a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift index 38546ad7..07940e35 100644 --- a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift +++ b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift @@ -117,68 +117,10 @@ private extension ClassSettingView { func battleSection() -> some View { VStack(alignment: .leading, spacing: 12) { sectionLabel("선택한 배틀") - selectedBattleCard() + ClassSelectedBattleCard(battle: store.battle) } } - @ViewBuilder - func selectedBattleCard() -> some View { - HStack(alignment: .top, spacing: 8) { - PickeRemoteImage(url: store.battle.thumbnailURL) { Color.beige600 } - .frame(width: 76, height: 106) - .clipShape(RoundedRectangle(cornerRadius: .radiusDefault)) - - VStack(alignment: .leading, spacing: 24) { - VStack(alignment: .leading, spacing: 8) { - HStack(spacing: 6) { - Text("#\(store.battle.category.title)") - .pickeBadge(.filled, size: .tag) - Text(store.battle.title) - .pretendardFont(.headingSmall) - .foregroundStyle(.gray500) - .lineLimit(1) - } - - Text(store.battle.summary) - .pretendardFont(.regular13) - .foregroundStyle(.gray400) - .lineLimit(2) - .padding(.horizontal, 2) - } - - HStack(spacing: 6) { - Spacer(minLength: 0) - metaLabel( - systemImage: "clock", - text: "\(store.battle.durationMinutes)분" - ) - metaLabel( - systemImage: "eye", - text: store.battle.viewCount.decimalFormatted - ) - } - } - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - .frame(maxWidth: .infinity, alignment: .leading) - .pickeCard(.beige50, border: .beige600, radius: 0) - } - - @ViewBuilder - func metaLabel( - systemImage: String, - text: String - ) -> some View { - HStack(spacing: 2) { - Image(systemName: systemImage) - .pretendardFont(.labelXSmall) - Text(text) - .pretendardFont(.labelSmall) - } - .foregroundStyle(.gray300) - } - var commentToggle: some View { Toggle(isOn: $store.requiresComment) { VStack(alignment: .leading, spacing: 2) { diff --git a/Projects/Feature/Class/Sources/ClassShare/Reducer/ClassShareFeature.swift b/Projects/Feature/Class/Sources/ClassShare/Reducer/ClassShareFeature.swift new file mode 100644 index 00000000..11e35222 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassShare/Reducer/ClassShareFeature.swift @@ -0,0 +1,71 @@ +// +// ClassShareFeature.swift +// Class +// + +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassShareFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public var room: ClassRoom + + public init(room: ClassRoom) { + self.room = room + } + + /// 마감일을 끈 클래스는 `.distantFuture` 로 만들어지므로 표시하지 않는다. + public var deadlineText: String? { + guard room.deadline != .distantFuture else { return nil } + return "참여 마감 \(Self.deadlineFormatter.string(from: room.deadline))" + } + + public var shareMessage: String { + "[Picke] '\(room.name)' 클래스에 참여해 주세요!\n참여 코드: \(room.joinCode)" + } + + private static let deadlineFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.dateFormat = "yyyy년 MM월 dd일 HH:mm" + return formatter + }() + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View { + case closeTapped + case enterClassTapped + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case enterClass(ClassRoom) + } + + public var body: some Reducer { + Reduce { state, action in + switch action { + case .view(.closeTapped): + return .send(.delegate(.dismiss)) + + case .view(.enterClassTapped): + return .send(.delegate(.enterClass(state.room))) + + case .delegate: + return .none + } + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassShare/View/ClassShareView.swift b/Projects/Feature/Class/Sources/ClassShare/View/ClassShareView.swift new file mode 100644 index 00000000..e429132b --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassShare/View/ClassShareView.swift @@ -0,0 +1,124 @@ +// +// ClassShareView.swift +// Class +// + +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassShareFeature.self) +public struct ClassShareView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar( + onBack: { send(.closeTapped) }, + centerTitle: "클래스 공유하기" + ) + .foregroundStyle(.gray500) + + ScrollView { + VStack(alignment: .leading, spacing: 32) { + intro() + codeCard() + battleSection() + } + .padding(16) + } + .scrollIndicators(.hidden) + .scrollBounceBehavior(.basedOnSize) + + enterButton + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .screenBackground() + .hidesSystemBars() + } +} + +private extension ClassShareView { + @ViewBuilder + func intro() -> some View { + VStack(spacing: 12) { + Text("클래스가 준비됐어요") + .pretendardFont(.semiBold24) + .foregroundStyle(.gray500) + + Text("참여 코드를 학생들에게 공유해주세요") + .pretendardFont(.headingMedium) + .foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity) + } + + @ViewBuilder + func codeCard() -> some View { + VStack(spacing: 12) { + VStack(spacing: 4) { + Text(store.room.name) + .pretendardFont(.bold18) + .foregroundStyle(.gray800) + + if let deadlineText = store.deadlineText { + Text(deadlineText) + .pretendardFont(.labelMedium) + .foregroundStyle(.gray300) + } + } + + VStack(spacing: 12) { + Text("참여 코드") + .pretendardFont(.labelMedium) + .foregroundStyle(.gray300) + + Text(store.room.joinCode) + .pretendardFont(.headingXXLarge) + .foregroundStyle(.primary500) + .textSelection(.enabled) + } + + shareButton + } + .padding(24) + .frame(maxWidth: .infinity) + .pickeCard(.beige50, border: .beige600) + } + + var shareButton: some View { + ShareLink(item: store.shareMessage) { + Text("코드 공유하기") + .pretendardFont(.labelLarge) + .foregroundStyle(.primary500) + .frame(maxWidth: .infinity, minHeight: 52) + .background(.primary50, in: RoundedRectangle(cornerRadius: .radiusDefault)) + } + .buttonStyle(.plain) + } + + @ViewBuilder + func battleSection() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text("선택한 배틀") + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + + ClassSelectedBattleCard(battle: store.room.battle) + } + } + + var enterButton: some View { + Button { + send(.enterClassTapped) + } label: { + Text("클래스로 이동하기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + } +} diff --git a/Projects/Feature/Class/Sources/Common/Components/ClassSelectedBattleCard.swift b/Projects/Feature/Class/Sources/Common/Components/ClassSelectedBattleCard.swift new file mode 100644 index 00000000..27c14cb4 --- /dev/null +++ b/Projects/Feature/Class/Sources/Common/Components/ClassSelectedBattleCard.swift @@ -0,0 +1,74 @@ +// +// ClassSelectedBattleCard.swift +// Class +// + +import ClassDomainInterface +import PickeCoreUtility +import PickeDesignKit +import PickeSharedUI +import SwiftUI + +/// 클래스 설정 / 공유 화면의 "선택한 배틀" 카드. +struct ClassSelectedBattleCard: View { + let battle: ClassBattleSummary + + var body: some View { + HStack(alignment: .top, spacing: 8) { + PickeRemoteImage(url: battle.thumbnailURL) { Color.beige600 } + .frame(width: 76, height: 106) + .clipShape(RoundedRectangle(cornerRadius: .radiusDefault)) + + VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 6) { + Text("#\(battle.category.title)") + .pickeBadge(.filled, size: .tag) + Text(battle.title) + .pretendardFont(.headingSmall) + .foregroundStyle(.gray500) + .lineLimit(1) + } + + Text(battle.summary) + .pretendardFont(.regular13) + .foregroundStyle(.gray400) + .lineLimit(2) + .padding(.horizontal, 2) + } + + HStack(spacing: 6) { + Spacer(minLength: 0) + metaLabel( + systemImage: "clock", + text: "\(battle.durationMinutes)분" + ) + metaLabel( + systemImage: "eye", + text: battle.viewCount.decimalFormatted + ) + } + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + .pickeCard(.beige50, border: .beige600, radius: 0) + } +} + +private extension ClassSelectedBattleCard { + @ViewBuilder + func metaLabel( + systemImage: String, + text: String + ) -> some View { + HStack(spacing: 2) { + Image(systemName: systemImage) + .pretendardFont(.labelXSmall) + Text(text) + .pretendardFont(.labelSmall) + } + .foregroundStyle(.gray300) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassShareTests.swift b/Projects/Feature/Class/Tests/Sources/ClassShareTests.swift new file mode 100644 index 00000000..b70b7749 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassShareTests.swift @@ -0,0 +1,45 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Foundation +import Testing + +@MainActor +struct ClassShareTests { + private let room = ClassRoom.mocks[0] + + @Test + func 공유_문구에_클래스명과_참여코드를_담는다() { + let state = ClassShareFeature.State(room: room) + #expect(state.shareMessage.contains(room.name)) + #expect(state.shareMessage.contains(room.joinCode)) + } + + @Test + func 마감일이_없는_클래스는_마감_문구를_숨긴다() { + let noDeadline = ClassRoom( + id: room.id, + name: room.name, + joinCode: room.joinCode, + battle: room.battle, + deadline: .distantFuture, + memberCount: room.memberCount, + role: room.role, + status: room.status, + allowsAnonymousOpinion: room.allowsAnonymousOpinion, + requiresComment: room.requiresComment + ) + #expect(ClassShareFeature.State(room: noDeadline).deadlineText == nil) + #expect(ClassShareFeature.State(room: room).deadlineText != nil) + } + + @Test + func 클래스로_이동하면_이동_델리게이트를_보낸다() async { + let store = TestStore(initialState: ClassShareFeature.State(room: room)) { + ClassShareFeature() + } + + await store.send(.view(.enterClassTapped)) + await store.receive(\.delegate.enterClass) + } +} From 84230c79085e81bfd20ee5f0453356d1c29beacf Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 00:48:02 +0900 Subject: [PATCH 19/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9D=98=20TCA=20?= =?UTF-8?q?=EC=95=A1=EC=85=98=20=EC=88=98=EC=8B=A0=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 Class 테스트와 같은 케이스 경로 수신 형식으로 바꾸고 목록 필터 열거형을 Hashable로 선언한다. --- .../Sources/MyClass/Reducer/MyClassFeature.swift | 4 ++-- .../Class/Tests/Sources/ClassDetailTests.swift | 2 +- .../Feature/Class/Tests/Sources/ClassJoinTests.swift | 12 ++++++------ .../Feature/Class/Tests/Sources/MyClassTests.swift | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift index c29aa7cc..ce73f4e5 100644 --- a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift +++ b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift @@ -7,13 +7,13 @@ public struct MyClassFeature { @ObservableState public struct State: Equatable { - public enum Ownership: Equatable, CaseIterable { + public enum Ownership: Hashable, CaseIterable { case all case created case joined } - public enum Progress: Equatable, CaseIterable { + public enum Progress: Hashable, CaseIterable { case all case open case closed diff --git a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift index 1675c68f..70343d73 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift @@ -44,6 +44,6 @@ struct ClassDetailTests { } await store.send(.view(.battleTapped)) - await store.receive(.delegate(.openBattle(room.battle))) + await store.receive(\.delegate, .openBattle(room.battle)) } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift index 5b16a332..75cd2bc8 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift @@ -28,10 +28,10 @@ struct ClassJoinTests { $0.joinCode = " pk9t3s " } await store.send(.view(.findTapped)) - await store.receive(.async(.find("PK9T3S"))) { + await store.receive(\.async, .find("PK9T3S")) { $0.isLoading = true } - await store.receive(\.inner.found) { + await store.receive(\.inner, .found(.success(room))) { $0.preview = room $0.isLoading = false } @@ -54,10 +54,10 @@ struct ClassJoinTests { } await store.send(.view(.joinTapped)) - await store.receive(.async(.join(joinCode: room.joinCode, nickname: "민지"))) { + await store.receive(\.async, .join(joinCode: room.joinCode, nickname: "민지")) { $0.isLoading = true } - await store.receive(\.inner.joined) { + await store.receive(\.inner, .joined(.success(room))) { $0.preview = nil $0.isLoading = false } @@ -78,10 +78,10 @@ struct ClassJoinTests { $0.joinCode = "INVALID" } await store.send(.view(.findTapped)) - await store.receive(.async(.find("INVALID"))) { + await store.receive(\.async, .find("INVALID")) { $0.isLoading = true } - await store.receive(\.inner.found) { + await store.receive(\.inner, .found(.failure(.invalidCode))) { $0.isLoading = false $0.errorMessage = "참여 코드를 다시 확인해 주세요." } diff --git a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift index 8dc686c4..54772886 100644 --- a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift +++ b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift @@ -31,6 +31,6 @@ struct MyClassTests { } await store.send(.view(.roomTapped(1))) - await store.receive(.delegate(.openRoom(ClassRoom.mocks[0]))) + await store.receive(\.delegate, .openRoom(ClassRoom.mocks[0])) } } From e48dd46bc741e0fb059c20861cdd7d73a7c8aef0 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 01:00:36 +0900 Subject: [PATCH 20/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=EC=99=80=20=EC=83=81=EC=84=B8=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=84=20=EC=95=B1=20=EA=B2=BD=EB=A1=9C=EC=97=90=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 참여 코드, 내 클래스, 상세, 멤버 목록을 ClassCoordinator에 연결하고 배틀 참여는 기존 Chat 흐름으로 이동한다. 상세의 코드 공유는 시스템 공유 기능을 사용한다. --- .../Navigation/Class/ClassCoordinator.swift | 46 +++++++++++++++++-- .../Class/ClassCoordinatorView.swift | 15 ++++++ .../Reducer/ClassDetailFeature.swift | 4 -- .../ClassDetail/View/ClassDetailView.swift | 6 ++- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index 0db97911..6b4db586 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -3,11 +3,10 @@ // Picke // -import Foundation - import ComposableArchitecture import DomainAssembly import FeatureAssembly +import Foundation import TCAFlow @FlowCoordinator(screen: "ClassScreen", navigation: true) @@ -63,6 +62,14 @@ private extension ClassCoordinator { action: IndexedRouterActionOf ) -> Effect { switch action { + case .routeAction(_, action: .intro(.delegate(.join))): + state.routes.push(.join(.init())) + return .none + + case .routeAction(_, action: .intro(.delegate(.myClasses))): + state.routes.push(.myClasses(.init())) + return .none + case .routeAction(_, action: .intro(.delegate(.create))): state.routes.push(.topic(.init())) return .none @@ -88,8 +95,34 @@ private extension ClassCoordinator { state.routes.push(.share(.init(room: room))) return .none - case .routeAction(_, action: .share(.delegate(.dismiss))), - .routeAction(_, action: .share(.delegate(.enterClass))): + case .routeAction(_, action: .share(.delegate(.dismiss))): + return .send(.view(.backToRootAction)) + + case let .routeAction(_, action: .share(.delegate(.enterClass(room)))): + state.routes.goBackToRoot() + state.routes.push(.detail(.init(room: room))) + return .none + + case .routeAction(_, action: .join(.delegate(.dismiss))), + .routeAction(_, action: .myClasses(.delegate(.dismiss))), + .routeAction(_, action: .detail(.delegate(.dismiss))), + .routeAction(_, action: .members(.delegate(.dismiss))): + return .send(.view(.backAction)) + + case let .routeAction(_, action: .join(.delegate(.joined(room)))), + let .routeAction(_, action: .myClasses(.delegate(.openRoom(room)))): + state.routes.push(.detail(.init(room: room))) + return .none + + case let .routeAction(_, action: .detail(.delegate(.openMembers(room)))): + state.routes.push(.members(.init(room: room))) + return .none + + case let .routeAction(_, action: .detail(.delegate(.openBattle(battle)))): + state.routes.push(.chat(.init(route: .preVote(battleId: battle.id)))) + return .none + + case .routeAction(_, action: .detail(.delegate(.deleted))): return .send(.view(.backToRootAction)) default: @@ -122,6 +155,11 @@ extension ClassCoordinator { case recommend(ClassRecommendFeature) case setting(ClassSettingFeature) case share(ClassShareFeature) + case join(ClassJoinFeature) + case myClasses(MyClassFeature) + case detail(ClassDetailFeature) + case members(ClassMemberFeature) + case chat(ChatCoordinator) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index 36da33fd..fbf865d7 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -32,6 +32,21 @@ public struct ClassCoordinatorView: View { case let .share(shareStore): ClassShareView(store: shareStore) + + case let .join(joinStore): + ClassJoinView(store: joinStore) + + case let .myClasses(myClassesStore): + MyClassView(store: myClassesStore) + + case let .detail(detailStore): + ClassDetailView(store: detailStore) + + case let .members(membersStore): + ClassMemberView(store: membersStore) + + case let .chat(chatStore): + ChatCoordinatorView(store: chatStore) } } } diff --git a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift index 8efca48c..291f78c5 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift @@ -29,7 +29,6 @@ public struct ClassDetailFeature { public enum View { case backTapped - case codeShareTapped case membersTapped case battleTapped case deadlineTapped @@ -40,7 +39,6 @@ public struct ClassDetailFeature { public enum DelegateAction: Equatable { case dismiss - case shareCode(String) case openMembers(ClassRoom) case openBattle(ClassBattleSummary) case deleted(Int) @@ -66,8 +64,6 @@ extension ClassDetailFeature { switch action { case .backTapped: return .send(.delegate(.dismiss)) - case .codeShareTapped: - return .send(.delegate(.shareCode(state.room.joinCode))) case .membersTapped: return .send(.delegate(.openMembers(state.room))) case .battleTapped: diff --git a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift index c01de3f8..566b714e 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift @@ -76,8 +76,10 @@ private extension ClassDetailView { informationRow("참여 코드", value: store.room.joinCode) if store.room.role == .owner { - Button("코드 공유하기") { send(.codeShareTapped) } - .pretendardFont(.semiBold15) + ShareLink(item: "[Picke] '\(store.room.name)' 클래스 참여 코드: \(store.room.joinCode)") { + Text("코드 공유하기") + } + .pretendardFont(.semiBold15) } informationRow( From c79b9fb79db73941cf0aab8c3e1bb4870e89aa6c Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 01:08:33 +0900 Subject: [PATCH 21/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B9=84?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=20Reducer=20=EA=B5=AC=EC=A1=B0=EB=A5=BC=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EB=AA=A8=EB=93=88=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추천 배틀 조회와 클래스 생성의 사용자 입력, 비동기 실행, 응답 반영을 각각 분리하고 관련 테스트를 갱신한다. --- .../Reducer/ClassRecommendFeature.swift | 55 ++++++++++++------- .../Reducer/ClassSettingFeature.swift | 40 ++++++++++---- .../Tests/Sources/ClassRecommendTests.swift | 7 ++- .../Tests/Sources/ClassSettingTests.swift | 5 +- 4 files changed, 73 insertions(+), 34 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift index 5d6eaa1e..37900787 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift @@ -43,7 +43,8 @@ public struct ClassRecommendFeature { public enum Action: ViewAction { case view(View) - case response(Response) + case async(AsyncAction) + case inner(InnerAction) case delegate(DelegateAction) } @@ -57,8 +58,11 @@ public struct ClassRecommendFeature { case selectTapped } - @CasePathable - public enum Response: Equatable { + public enum AsyncAction: Equatable { + case fetch(ClassTopicFilter) + } + + public enum InnerAction: Equatable { case battles(Result<[ClassBattleSummary], ClassError>) } @@ -78,8 +82,11 @@ public struct ClassRecommendFeature { case let .view(viewAction): return handleViewAction(state: &state, action: viewAction) - case let .response(response): - return handleResponse(state: &state, response: response) + case let .async(asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case let .inner(innerAction): + return handleInnerAction(state: &state, action: innerAction) case .delegate: return .none @@ -88,23 +95,15 @@ public struct ClassRecommendFeature { } } -private extension ClassRecommendFeature { - func handleViewAction( +extension ClassRecommendFeature { + private func handleViewAction( state: inout State, action: View ) -> Effect { switch action { case .onAppear: guard state.battles.isEmpty else { return .none } - state.isLoading = true - return .run { [classUseCase, filter = state.filter] send in - let result = await Result { - try await classUseCase.fetchRecommendedBattles(filter: filter) - } - .mapError(ClassError.from) - await send(.response(.battles(result))) - } - .cancellable(id: CancelID.fetch, cancelInFlight: true) + return .send(.async(.fetch(state.filter))) case .backTapped, .editConditionTapped: return .send(.delegate(.dismiss)) @@ -122,11 +121,29 @@ private extension ClassRecommendFeature { } } - func handleResponse( + private func handleAsyncAction( state: inout State, - response: Response + action: AsyncAction ) -> Effect { - switch response { + switch action { + case let .fetch(filter): + state.isLoading = true + return .run { [classUseCase] send in + let result = await Result { + try await classUseCase.fetchRecommendedBattles(filter: filter) + } + .mapError(ClassError.from) + await send(.inner(.battles(result))) + } + .cancellable(id: CancelID.fetch, cancelInFlight: true) + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { case let .battles(result): state.isLoading = false if case let .success(battles) = result { diff --git a/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift b/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift index 648a6afc..0c600271 100644 --- a/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift +++ b/Projects/Feature/Class/Sources/ClassSetting/Reducer/ClassSettingFeature.swift @@ -64,7 +64,8 @@ public struct ClassSettingFeature { public enum Action: ViewAction, BindableAction { case binding(BindingAction) case view(View) - case response(Response) + case async(AsyncAction) + case inner(InnerAction) case delegate(DelegateAction) } @@ -75,8 +76,12 @@ public struct ClassSettingFeature { case createTapped } + public enum AsyncAction: Equatable { + case create(ClassCreation) + } + @CasePathable - public enum Response: Equatable { + public enum InnerAction: Equatable { case created(Result) } @@ -100,8 +105,11 @@ public struct ClassSettingFeature { case let .view(viewAction): return handleViewAction(state: &state, action: viewAction) - case let .response(response): - return handleResponse(state: &state, response: response) + case let .async(asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + + case let .inner(innerAction): + return handleInnerAction(state: &state, action: innerAction) case .delegate: return .none @@ -110,8 +118,8 @@ public struct ClassSettingFeature { } } -private extension ClassSettingFeature { - func handleViewAction( +extension ClassSettingFeature { + private func handleViewAction( state: inout State, action: View ) -> Effect { @@ -125,23 +133,33 @@ private extension ClassSettingFeature { case .createTapped: guard state.canCreate else { return .none } + return .send(.async(.create(state.creation))) + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + switch action { + case let .create(creation): state.isLoading = true - return .run { [classUseCase, creation = state.creation] send in + return .run { [classUseCase] send in let result = await Result { try await classUseCase.createClass(creation) } .mapError(ClassError.from) - await send(.response(.created(result))) + await send(.inner(.created(result))) } .cancellable(id: CancelID.create, cancelInFlight: true) } } - func handleResponse( + private func handleInnerAction( state: inout State, - response: Response + action: InnerAction ) -> Effect { - switch response { + switch action { case let .created(result): state.isLoading = false guard case let .success(room) = result else { return .none } diff --git a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift index 7b926760..3ec651d4 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift @@ -11,10 +11,13 @@ struct ClassRecommendTests { ClassRecommendFeature() } - await store.send(.view(.onAppear)) { + await store.send(.view(.onAppear)) + await store.receive(\.async, .fetch(.init(level: .middle))) { $0.isLoading = true } - await store.receive(\.response.battles) { + await store.receive(\.inner, .battles(.success( + ClassBattleSummary.mocks.filter { $0.level == .middle } + ))) { $0.isLoading = false $0.battles = ClassBattleSummary.mocks.filter { $0.level == .middle } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift b/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift index d0916a47..6a0b70f0 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassSettingTests.swift @@ -39,10 +39,11 @@ struct ClassSettingTests { } store.exhaustivity = .off - await store.send(.view(.createTapped)) { + await store.send(.view(.createTapped)) + await store.receive(\.async, .create(state.creation)) { $0.isLoading = true } - await store.receive(\.response.created) { + await store.receive(\.inner.created) { $0.isLoading = false } await store.receive(\.delegate.created) From 31fe74676225e12a46ba60796b6bb30c0e25dd1e Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 06:36:14 +0900 Subject: [PATCH 22/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=A7=88?= =?UTF-8?q?=EA=B0=90=EC=9D=BC=20=EB=B3=80=EA=B2=BD=EA=B3=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EB=A5=BC=20=EB=AA=A9=20=EC=A0=80=EC=9E=A5=EC=86=8C?= =?UTF-8?q?=EC=97=90=20=EB=B0=98=EC=98=81=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클래스 상세 화면에서 저장소 응답을 확인한 뒤 마감일과 삭제 경로를 갱신한다. 목 저장소의 변경 결과를 도메인과 화면 테스트로 검증한다. --- .../Sources/Repository/ClassInterface.swift | 2 + .../Default/MockClassRepository.swift | 34 +++++++++ .../Repository/ClassRepositoryImpl.swift | 8 ++ .../Sources/UseCase/ClassUseCase.swift | 8 ++ .../Tests/Sources/ClassDomainTests.swift | 27 +++++++ .../Reducer/ClassDetailFeature.swift | 75 +++++++++++++++++-- .../ClassDetail/View/ClassDetailView.swift | 16 ++++ .../Tests/Sources/ClassDetailTests.swift | 42 ++++++++++- .../Class/Tests/Sources/ClassJoinTests.swift | 7 ++ 9 files changed, 212 insertions(+), 7 deletions(-) diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift index 75f82ca4..973d5559 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift @@ -7,6 +7,8 @@ public protocol ClassInterface: Sendable { func createClass(_ creation: ClassCreation) async throws -> ClassRoom func fetchClass(joinCode: String) async throws -> ClassRoom func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom + func updateDeadline(id: Int, deadline: Date) async throws -> ClassRoom + func deleteClass(id: Int) async throws } public enum ClassRepositoryDependency: TestDependencyKey { diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift index 86b76003..1b1e925a 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/Default/MockClassRepository.swift @@ -89,4 +89,38 @@ public actor MockClassRepository: ClassInterface { availableRooms.remove(at: index) return joined } + + public func updateDeadline(id: Int, deadline: Date) async throws -> ClassRoom { + guard let index = rooms.firstIndex(where: { $0.id == id }) else { + throw ClassError.invalidCode + } + let room = rooms[index] + guard room.role == .owner else { + throw ClassError.unknown("클래스 관리자만 마감일을 변경할 수 있습니다.") + } + let updated = ClassRoom( + id: room.id, + name: room.name, + joinCode: room.joinCode, + battle: room.battle, + deadline: deadline, + memberCount: room.memberCount, + role: room.role, + status: room.status, + allowsAnonymousOpinion: room.allowsAnonymousOpinion, + requiresComment: room.requiresComment + ) + rooms[index] = updated + return updated + } + + public func deleteClass(id: Int) async throws { + guard let index = rooms.firstIndex(where: { $0.id == id }) else { + throw ClassError.invalidCode + } + guard rooms[index].role == .owner else { + throw ClassError.unknown("클래스 관리자만 삭제할 수 있습니다.") + } + rooms.remove(at: index) + } } diff --git a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift index ae20c2ca..f0dd3512 100644 --- a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift +++ b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift @@ -25,4 +25,12 @@ public struct ClassRepositoryImpl: ClassInterface { public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { try await Self.mock.joinClass(joinCode: joinCode, nickname: nickname) } + + public func updateDeadline(id: Int, deadline: Date) async throws -> ClassRoom { + try await Self.mock.updateDeadline(id: id, deadline: deadline) + } + + public func deleteClass(id: Int) async throws { + try await Self.mock.deleteClass(id: id) + } } diff --git a/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift b/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift index 77e93f11..6fc9fbb3 100644 --- a/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift +++ b/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift @@ -26,4 +26,12 @@ public struct ClassUseCaseImpl: ClassInterface { public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { try await repository.joinClass(joinCode: joinCode, nickname: nickname) } + + public func updateDeadline(id: Int, deadline: Date) async throws -> ClassRoom { + try await repository.updateDeadline(id: id, deadline: deadline) + } + + public func deleteClass(id: Int) async throws { + try await repository.deleteClass(id: id) + } } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift index cef93e1e..51d8640c 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift @@ -79,4 +79,31 @@ struct ClassDomainTests { #expect(middle.map(\.id) == [101]) #expect(high.isEmpty) } + + @Test + func 마감일_변경은_목록과_참여코드_조회에_반영된다() async throws { + let repository = MockClassRepository(rooms: [ClassRoom.mocks[0]], availableRooms: []) + let deadline = Date(timeIntervalSince1970: 1_801_000_000) + + let updated = try await repository.updateDeadline(id: 1, deadline: deadline) + + #expect(updated.deadline == deadline) + #expect(try await repository.fetchMyClasses()[0].deadline == deadline) + #expect(try await repository.fetchClass(joinCode: updated.joinCode).deadline == deadline) + } + + @Test + func 클래스_삭제는_목록과_참여코드_조회에_반영된다() async throws { + let repository = MockClassRepository(rooms: [ClassRoom.mocks[0]], availableRooms: []) + + try await repository.deleteClass(id: 1) + + #expect(try await repository.fetchMyClasses().isEmpty) + do { + _ = try await repository.fetchClass(joinCode: "PK7M2Q") + Issue.record("삭제한 클래스를 조회할 수 있음") + } catch { + #expect(ClassError.from(error) == .invalidCode) + } + } } diff --git a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift index 291f78c5..c2d00af4 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift @@ -4,15 +4,19 @@ import Foundation @Reducer public struct ClassDetailFeature { + @Dependency(\.classUseCase) private var classUseCase + public init() {} @ObservableState public struct State: Equatable { - public let room: ClassRoom + public var room: ClassRoom public var deadline: Date public var draftDeadline: Date public var isDeadlineSheetPresented = false public var isDeleteAlertPresented = false + public var isLoading = false + public var errorMessage: String? public init(room: ClassRoom) { self.room = room @@ -24,6 +28,8 @@ public struct ClassDetailFeature { public enum Action: ViewAction, BindableAction { case binding(BindingAction) case view(View) + case async(AsyncAction) + case inner(InnerAction) case delegate(DelegateAction) } @@ -44,6 +50,16 @@ public struct ClassDetailFeature { case deleted(Int) } + public enum AsyncAction: Equatable { + case updateDeadline(Int, Date) + case delete(Int) + } + + public enum InnerAction: Equatable { + case deadlineUpdated(Result) + case deleted(Int, ClassError?) + } + public var body: some Reducer { BindingReducer() Reduce { state, action in @@ -52,6 +68,10 @@ public struct ClassDetailFeature { return .none case let .view(viewAction): return handleViewAction(state: &state, action: viewAction) + case let .async(asyncAction): + return handleAsyncAction(state: &state, action: asyncAction) + case let .inner(innerAction): + return handleInnerAction(state: &state, action: innerAction) case .delegate: return .none } @@ -74,17 +94,60 @@ extension ClassDetailFeature { state.isDeadlineSheetPresented = true return .none case .deadlineSaved: - state.deadline = state.draftDeadline - state.isDeadlineSheetPresented = false - return .none + guard !state.isLoading else { return .none } + return .send(.async(.updateDeadline(state.room.id, state.draftDeadline))) case .deleteTapped: guard state.room.role == .owner else { return .none } state.isDeleteAlertPresented = true return .none case .deleteConfirmed: - guard state.room.role == .owner else { return .none } + guard state.room.role == .owner, !state.isLoading else { return .none } state.isDeleteAlertPresented = false - return .send(.delegate(.deleted(state.room.id))) + return .send(.async(.delete(state.room.id))) + } + } + + private func handleAsyncAction( + state: inout State, + action: AsyncAction + ) -> Effect { + state.isLoading = true + state.errorMessage = nil + switch action { + case let .updateDeadline(id, deadline): + return .run { [classUseCase] send in + let result = await Result { try await classUseCase.updateDeadline(id: id, deadline: deadline) } + .mapError(ClassError.from) + await send(.inner(.deadlineUpdated(result))) + } + case let .delete(id): + return .run { [classUseCase] send in + do { + try await classUseCase.deleteClass(id: id) + await send(.inner(.deleted(id, nil))) + } catch { + await send(.inner(.deleted(id, ClassError.from(error)))) + } + } + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + state.isLoading = false + switch action { + case let .deadlineUpdated(.success(room)): + state.room = room + state.deadline = room.deadline + state.isDeadlineSheetPresented = false + return .none + case let .deadlineUpdated(.failure(error)), let .deleted(_, .some(error)): + state.errorMessage = error.localizedDescription + return .none + case let .deleted(id, .none): + return .send(.delegate(.deleted(id))) } } } diff --git a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift index 566b714e..cc0c402b 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift @@ -42,6 +42,21 @@ public struct ClassDetailView: View { } message: { Text("삭제한 클래스는 다시 복구할 수 없어요.") } + .alert( + "요청을 완료하지 못했어요", + isPresented: Binding( + get: { store.errorMessage != nil }, + set: { + if !$0 { + store.errorMessage = nil + } + } + ) + ) { + Button("확인", role: .cancel) {} + } message: { + Text(store.errorMessage ?? "다시 시도해 주세요.") + } } } @@ -161,6 +176,7 @@ private extension ClassDetailView { Button("수정하기") { send(.deadlineSaved) } .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(store.isLoading) } .padding(20) .screenBackground() diff --git a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift index 70343d73..5324da0b 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift @@ -20,7 +20,25 @@ struct ClassDetailTests { await store.send(.binding(.set(\.draftDeadline, updated))) { $0.draftDeadline = updated } - await store.send(.view(.deadlineSaved)) { + await store.send(.view(.deadlineSaved)) + await store.receive(\.async, .updateDeadline(room.id, updated)) { + $0.isLoading = true + } + let updatedRoom = ClassRoom( + id: room.id, + name: room.name, + joinCode: room.joinCode, + battle: room.battle, + deadline: updated, + memberCount: room.memberCount, + role: room.role, + status: room.status, + allowsAnonymousOpinion: room.allowsAnonymousOpinion, + requiresComment: room.requiresComment + ) + await store.receive(\.inner, .deadlineUpdated(.success(updatedRoom))) { + $0.isLoading = false + $0.room = updatedRoom $0.deadline = updated $0.isDeadlineSheetPresented = false } @@ -46,4 +64,26 @@ struct ClassDetailTests { await store.send(.view(.battleTapped)) await store.receive(\.delegate, .openBattle(room.battle)) } + + @Test + func 삭제가_완료된_뒤에만_화면을_닫는다() async { + let room = ClassRoom.mocks[0] + let store = TestStore(initialState: ClassDetailFeature.State(room: room)) { + ClassDetailFeature() + } + + await store.send(.view(.deleteTapped)) { + $0.isDeleteAlertPresented = true + } + await store.send(.view(.deleteConfirmed)) { + $0.isDeleteAlertPresented = false + } + await store.receive(\.async, .delete(room.id)) { + $0.isLoading = true + } + await store.receive(\.inner, .deleted(room.id, nil)) { + $0.isLoading = false + } + await store.receive(\.delegate, .deleted(room.id)) + } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift index 75cd2bc8..59333744 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift @@ -1,6 +1,7 @@ @testable import Class import ClassDomainInterface import ComposableArchitecture +import Foundation import Testing @MainActor @@ -113,4 +114,10 @@ private struct StubClassUseCase: ClassInterface { func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { try await joinClass(joinCode, nickname) } + + func updateDeadline(id _: Int, deadline _: Date) async throws -> ClassRoom { + .mockJoinable + } + + func deleteClass(id _: Int) async throws {} } From 8c8414189289bea46de870d38f0aac6c2dbd4ba9 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 16:33:31 +0900 Subject: [PATCH 23/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EC=BD=94=EB=93=9C=20=ED=99=95=EC=9D=B8=20=ED=9B=84?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=9E=85=EB=A0=A5=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=EC=9D=84=20=EC=88=9C=EC=B0=A8=EB=A1=9C=20=EB=9D=84=EC=9A=B4?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Navigation/Class/ClassCoordinator.swift | 28 +- .../Class/ClassCoordinatorView.swift | 3 + .../Reducer/ClassDetailFeature.swift | 63 +++- .../ClassDetail/View/ClassDetailView.swift | 338 +++++++++++++----- .../Reducer/ClassIntroFeature.swift | 92 ++++- .../ClassIntro/View/ClassIntroView.swift | 38 +- .../View/Components/ClassMenuCard.swift | 35 +- .../Reducer/ClassMemberFeature.swift | 41 ++- .../ClassMember/View/ClassMemberView.swift | 86 +++-- .../Reducer/ClassReportFeature.swift | 54 +++ .../ClassReport/View/ClassReportView.swift | 331 +++++++++++++++++ .../Tests/Sources/ClassDetailTests.swift | 26 +- .../Tests/Sources/ClassMemberTests.swift | 23 +- .../Tests/Sources/ClassReportTests.swift | 28 ++ .../Class/Tests/Sources/ClassTests.swift | 48 ++- 15 files changed, 1061 insertions(+), 173 deletions(-) create mode 100644 Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassReportTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index 6b4db586..ba971b58 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -20,6 +20,16 @@ public struct ClassCoordinator { public init() { routes = [.root(.intro(.init()), embedInNavigationView: true)] } + + /// 내 클래스 · 클래스 상세 · 멤버 목록 화면에서만 탭바를 보여준다. + public var showsTabBar: Bool { + switch routes.last?.screen { + case .myClasses?, .detail?, .members?, .report?: + return true + default: + return false + } + } } @CasePathable @@ -62,8 +72,8 @@ private extension ClassCoordinator { action: IndexedRouterActionOf ) -> Effect { switch action { - case .routeAction(_, action: .intro(.delegate(.join))): - state.routes.push(.join(.init())) + case let .routeAction(_, action: .intro(.delegate(.joined(room)))): + state.routes.push(.detail(.init(room: room))) return .none case .routeAction(_, action: .intro(.delegate(.myClasses))): @@ -122,6 +132,19 @@ private extension ClassCoordinator { state.routes.push(.chat(.init(route: .preVote(battleId: battle.id)))) return .none + case let .routeAction(_, action: .detail(.delegate(.openReport(room)))): + state.routes.push(.report(.init(room: room))) + return .none + + case .routeAction(_, action: .report(.delegate(.dismiss))): + return .send(.view(.backAction)) + + case .routeAction(_, action: .chat(.delegate(.dismiss))): + return .send(.view(.backAction)) + + case .routeAction(_, action: .chat(.delegate(.popToRoot))): + return .send(.view(.backToRootAction)) + case .routeAction(_, action: .detail(.delegate(.deleted))): return .send(.view(.backToRootAction)) @@ -159,6 +182,7 @@ extension ClassCoordinator { case myClasses(MyClassFeature) case detail(ClassDetailFeature) case members(ClassMemberFeature) + case report(ClassReportFeature) case chat(ChatCoordinator) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index fbf865d7..ea0bb91e 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -45,6 +45,9 @@ public struct ClassCoordinatorView: View { case let .members(membersStore): ClassMemberView(store: membersStore) + case let .report(reportStore): + ClassReportView(store: reportStore) + case let .chat(chatStore): ChatCoordinatorView(store: chatStore) } diff --git a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift index c2d00af4..b1102d6e 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift @@ -1,6 +1,7 @@ import ClassDomainInterface import ComposableArchitecture import Foundation +import PickeSharedUI @Reducer public struct ClassDetailFeature { @@ -13,8 +14,8 @@ public struct ClassDetailFeature { public var room: ClassRoom public var deadline: Date public var draftDeadline: Date - public var isDeadlineSheetPresented = false - public var isDeleteAlertPresented = false + @Presents public var modal: ClassModalFeature.State? + @Presents public var customAlert: CustomAlertState? public var isLoading = false public var errorMessage: String? @@ -30,6 +31,8 @@ public struct ClassDetailFeature { case view(View) case async(AsyncAction) case inner(InnerAction) + case modal(PresentationAction) + case customAlert(PresentationAction) case delegate(DelegateAction) } @@ -37,6 +40,11 @@ public struct ClassDetailFeature { case backTapped case membersTapped case battleTapped + case reportTapped + case managementTapped + case managementDismissed + case codeTapped + case codeDismissed case deadlineTapped case deadlineSaved case deleteTapped @@ -47,6 +55,7 @@ public struct ClassDetailFeature { case dismiss case openMembers(ClassRoom) case openBattle(ClassBattleSummary) + case openReport(ClassRoom) case deleted(Int) } @@ -72,10 +81,30 @@ public struct ClassDetailFeature { return handleAsyncAction(state: &state, action: asyncAction) case let .inner(innerAction): return handleInnerAction(state: &state, action: innerAction) + case .modal(.presented(.dismissTapped)), .modal(.dismiss): + state.modal = nil + return .none + case .modal: + return .none + case let .customAlert(alertAction): + switch alertAction { + case .presented(.confirmTapped): + state.customAlert = nil + return handleViewAction(state: &state, action: .deleteConfirmed) + case .presented(.cancelTapped), .dismiss: + state.customAlert = nil + return .none + } case .delegate: return .none } } + .ifLet(\.$modal, action: \.modal) { + ClassModalFeature() + } + .ifLet(\.$customAlert, action: \.customAlert) { + CustomConfirmAlert() + } } } @@ -88,21 +117,43 @@ extension ClassDetailFeature { return .send(.delegate(.openMembers(state.room))) case .battleTapped: return .send(.delegate(.openBattle(state.room.battle))) + case .reportTapped: + return .send(.delegate(.openReport(state.room))) + case .managementTapped: + guard state.room.role == .owner else { return .none } + state.modal = .init(kind: .management) + return .none + case .managementDismissed: + state.modal = nil + return .none + case .codeTapped: + state.modal = .init(kind: .code) + return .none + case .codeDismissed: + state.modal = nil + return .none case .deadlineTapped: guard state.room.role == .owner else { return .none } + state.modal = .init(kind: .deadline) state.draftDeadline = state.deadline - state.isDeadlineSheetPresented = true return .none case .deadlineSaved: guard !state.isLoading else { return .none } return .send(.async(.updateDeadline(state.room.id, state.draftDeadline))) case .deleteTapped: guard state.room.role == .owner else { return .none } - state.isDeleteAlertPresented = true + state.modal = nil + state.customAlert = CustomAlertState( + title: "삭제 후에는 복구할 수 없어요.\n그럼에도 삭제하시겠습니까?", + confirmTitle: "삭제하기", + cancelTitle: "뒤로가기", + isDestructive: true, + style: .deleteConfirm + ) return .none case .deleteConfirmed: guard state.room.role == .owner, !state.isLoading else { return .none } - state.isDeleteAlertPresented = false + state.customAlert = nil return .send(.async(.delete(state.room.id))) } } @@ -141,7 +192,7 @@ extension ClassDetailFeature { case let .deadlineUpdated(.success(room)): state.room = room state.deadline = room.deadline - state.isDeadlineSheetPresented = false + state.modal = nil return .none case let .deadlineUpdated(.failure(error)), let .deleted(_, .some(error)): state.errorMessage = error.localizedDescription diff --git a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift index cc0c402b..11f0048d 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift @@ -1,6 +1,7 @@ import ClassDomainInterface import ComposableArchitecture import PickeDesignKit +import PickeSharedUI import SwiftUI @ViewAction(for: ClassDetailFeature.self) @@ -13,35 +14,47 @@ public struct ClassDetailView: View { public var body: some View { VStack(spacing: 0) { - PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "클래스 상세") - .foregroundStyle(.gray500) + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "클래스 상세") { + if store.room.role == .owner { + Button { send(.managementTapped) } label: { + Image(systemName: "ellipsis") + .font(.system(size: 20)) + .frame(width: 24, height: 24) + } + .accessibilityLabel("클래스 관리") + } + } + .foregroundStyle(.gray800) ScrollView { - VStack(alignment: .leading, spacing: 24) { - headerSection() - classInformation() - battleSection() - if store.room.role == .owner { - deleteButton + VStack(alignment: .leading, spacing: 20) { + VStack(alignment: .leading, spacing: 16) { + headerSection() + classInformation() } + battleSection() + actionButtons() } .padding(16) } .scrollIndicators(.hidden) } .screenBackground() - .hidesSystemBars() - .sheet(isPresented: $store.isDeadlineSheetPresented) { - deadlineSheet() - .presentationDetents([.height(320)]) - .presentationDragIndicator(.visible) - } - .alert("클래스를 삭제할까요?", isPresented: $store.isDeleteAlertPresented) { - Button("취소", role: .cancel) {} - Button("삭제", role: .destructive) { send(.deleteConfirmed) } - } message: { - Text("삭제한 클래스는 다시 복구할 수 없어요.") + .toolbar(.hidden, for: .navigationBar) + .toolbar(store.modal == nil ? .visible : .hidden, for: .tabBar) + .pickeModal($store.scope(state: \.modal, action: \.modal)) { modalStore in + switch modalStore.kind { + case .management: + modalBackdrop({ send(.managementDismissed) }) { managementSheet() } + case .deadline: + modalBackdrop({ modalStore.send(.dismissTapped) }) { deadlineSheet() } + case .code: + modalBackdrop({ send(.codeDismissed) }) { codeSheet() } + case .nickname: + EmptyView() + } } + .customAlert($store.scope(state: \.customAlert, action: \.customAlert)) .alert( "요청을 완료하지 못했어요", isPresented: Binding( @@ -66,105 +79,237 @@ private extension ClassDetailView { VStack(alignment: .leading, spacing: 12) { Text(store.room.status == .open ? "진행 중" : "종료") .pretendardFont(.medium13) - .foregroundStyle(.gray500) + .foregroundStyle(.primary500) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(.beige600, in: RoundedRectangle(cornerRadius: 2)) Text(store.room.name) .pretendardFont(.semiBold24) .foregroundStyle(.gray800) - - HStack(spacing: 16) { - Text("멤버 \(store.room.memberCount)명") - .pretendardFont(.medium15) - .foregroundStyle(.gray500) - - if store.room.role == .owner { - Button("멤버 보기") { send(.membersTapped) } - .pretendardFont(.semiBold15) - } - } } } @ViewBuilder func classInformation() -> some View { - VStack(alignment: .leading, spacing: 16) { - informationRow("참여 코드", value: store.room.joinCode) - - if store.room.role == .owner { - ShareLink(item: "[Picke] '\(store.room.name)' 클래스 참여 코드: \(store.room.joinCode)") { - Text("코드 공유하기") - } - .pretendardFont(.semiBold15) + VStack(spacing: 0) { + Button { send(.membersTapped) } label: { + informationRow( + "클래스 멤버", + detail: "총 \(store.room.memberCount)명이 참여하고 있어요", + actionTitle: "보기" + ) } - - informationRow( - "마감일", - value: store.deadline.formatted(date: .numeric, time: .shortened) - ) - - if store.room.role == .owner { - Button("마감일 수정") { send(.deadlineTapped) } - .pretendardFont(.semiBold15) + .buttonStyle(.plain) + Rectangle().fill(.beige600).frame(height: 1) + Button { send(.deadlineTapped) } label: { + informationRow( + "참여 마감일", + detail: Self.deadlineFormatter.string(from: store.deadline) + "까지", + actionTitle: "수정" + ) } - - informationRow("익명 의견", value: store.room.allowsAnonymousOpinion ? "허용" : "허용 안 함") - informationRow("댓글 필수", value: store.room.requiresComment ? "필수" : "선택") + .buttonStyle(.plain) + .disabled(store.room.role != .owner) } - .padding(16) - .frame(maxWidth: .infinity, alignment: .leading) - .background(.white, in: RoundedRectangle(cornerRadius: 12)) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) } @ViewBuilder - func informationRow(_ title: String, value: String) -> some View { - HStack(alignment: .top, spacing: 16) { - Text(title) - .pretendardFont(.medium15) - .foregroundStyle(.gray300) - .frame(width: 80, alignment: .leading) - - Text(value) - .pretendardFont(.medium15) - .foregroundStyle(.gray800) + func informationRow(_ title: String, detail: String, actionTitle: String) -> some View { + HStack(spacing: 8) { + VStack(alignment: .leading, spacing: 2) { + Text(title) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + Text(detail) + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + Spacer(minLength: 0) + HStack(spacing: 4) { + Text(actionTitle) + .pretendardFont(family: .Bold, size: 12) + Image(systemName: "chevron.right") + .font(.system(size: 10, weight: .semibold)) + } + .foregroundStyle(.primary500) } + .frame(height: 64) } + static let deadlineFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.dateFormat = "yyyy. MM. dd. EEEE HH:mm" + return formatter + }() + @ViewBuilder func battleSection() -> some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 8) { Text("이번 클래스 배틀") - .pretendardFont(.bold18) - .foregroundStyle(.gray800) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray300) Button { send(.battleTapped) } label: { - VStack(alignment: .leading, spacing: 8) { - Text(store.room.battle.title) - .pretendardFont(.bold18) - .foregroundStyle(.gray800) - Text(store.room.battle.summary) - .pretendardFont(.medium15) - .foregroundStyle(.gray500) - .lineLimit(2) + HStack(spacing: 8) { + AsyncImage(url: store.room.battle.thumbnailURL) { image in + image.resizable().scaledToFill() + } placeholder: { + Color.beige600 + } + .frame(width: 76, height: 94) + .clipShape(RoundedRectangle(cornerRadius: 2)) + + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 6) { + Text("#\(store.room.battle.category.title)") + .pickeBadge(.filled, size: .tag) + Text(store.room.battle.title) + .pretendardFont(family: .SemiBold, size: 14) + .foregroundStyle(.gray500) + .lineLimit(1) + } + Text(store.room.battle.summary) + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + .lineLimit(2) + } + HStack(spacing: 2) { + Spacer(minLength: 0) + Image(systemName: "clock") + .font(.system(size: 12)) + Text("\(store.room.battle.durationMinutes)분") + .pretendardFont(family: .Medium, size: 12) + } + .foregroundStyle(.gray300) + } + .frame(height: 94) + Spacer(minLength: 0) } - .padding(16) + .padding(.vertical, 12) + .padding(.horizontal, 14) .frame(maxWidth: .infinity, alignment: .leading) - .background(.white, in: RoundedRectangle(cornerRadius: 12)) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) } .buttonStyle(.plain) } } var deleteButton: some View { - Button("클래스 삭제") { send(.deleteTapped) } - .pretendardFont(.medium15) - .foregroundStyle(.textError) + managementRow("클래스 삭제", detail: "삭제 후에는 복구할 수 없어요.", icon: "trash") { + send(.deleteTapped) + } + } + + @ViewBuilder + func actionButtons() -> some View { + VStack(spacing: 12) { + Button { send(.codeTapped) } label: { + Text("참여 코드 보기") + .pretendardFont(.headingMedium) + .foregroundStyle(.primary500) + .frame(maxWidth: .infinity) + .frame(height: 52) + .background(.primary50, in: RoundedRectangle(cornerRadius: 2)) + } + .buttonStyle(.plain) + Button("결과 보기") { send(.reportTapped) } + .ctaButtonStyle(.primary, size: .large, height: 52) + } + } + + @ViewBuilder + func modalBackdrop( + _ onDismiss: @escaping () -> Void, + @ViewBuilder content: () -> some View + ) -> some View { + ZStack(alignment: .bottom) { + Color.black.opacity(0.28) + .ignoresSafeArea() + .onTapGesture(perform: onDismiss) + content() + .frame(maxWidth: .infinity) + .background( + UnevenRoundedRectangle(topLeadingRadius: 26, topTrailingRadius: 26) + .fill(.beige50) + .ignoresSafeArea(edges: .bottom) + ) + } + } + + var sheetHandle: some View { + Capsule() + .fill(.gray50) + .frame(width: 40, height: 4) .frame(maxWidth: .infinity) - .padding(.vertical, 12) + .padding(.bottom, 10) + } + + @ViewBuilder + func managementSheet() -> some View { + VStack(alignment: .leading, spacing: 20) { + sheetHandle + Text("클래스 관리") + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + VStack(spacing: 0) { + managementRow("클래스 정보 수정", detail: "클래스 기본 정보를 수정해요.", icon: "pencil") { + send(.deadlineTapped) + } + Rectangle().fill(.beige600).frame(height: 1) + ShareLink(item: "[Picke] '\(store.room.name)' 클래스 참여 코드: \(store.room.joinCode)") { + managementRowContent("참여 코드 공유", detail: "클래스 참여 코드를 공유해요.", icon: "square.and.arrow.up") + } + .buttonStyle(.plain) + Rectangle().fill(.beige600).frame(height: 1) + deleteButton + } + } + .padding(.top, 12) + .padding(.horizontal, 16) + .padding(.bottom, 32) + } + + @ViewBuilder + func managementRow(_ title: String, detail: String, icon: String, action: @escaping () -> Void) -> some View { + Button(action: action) { + managementRowContent(title, detail: detail, icon: icon) + } + .buttonStyle(.plain) + } + + @ViewBuilder + func managementRowContent(_ title: String, detail: String, icon: String) -> some View { + HStack(spacing: 12) { + Image(systemName: icon) + .font(.system(size: 18)) + .foregroundStyle(.primary500) + .frame(width: 40, height: 40) + .background(.primary50, in: Circle()) + VStack(alignment: .leading, spacing: 2) { + Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) + Text(detail).pretendardFont(.medium13).foregroundStyle(.gray300) + } + Spacer() + Image(systemName: "chevron.right") + .font(.system(size: 12)) + .foregroundStyle(.gray800) + } + .frame(height: 72) + .contentShape(Rectangle()) } @ViewBuilder func deadlineSheet() -> some View { VStack(alignment: .leading, spacing: 20) { + sheetHandle Text("마감일 수정") .pretendardFont(.semiBold24) .foregroundStyle(.gray800) @@ -172,13 +317,36 @@ private extension ClassDetailView { DatePicker("마감일", selection: $store.draftDeadline, displayedComponents: [.date, .hourAndMinute]) .datePickerStyle(.compact) - Spacer(minLength: 0) - Button("수정하기") { send(.deadlineSaved) } .ctaButtonStyle(.primary, size: .large, height: 52) .disabled(store.isLoading) } - .padding(20) - .screenBackground() + .padding(.top, 16) + .padding(.horizontal, 16) + .padding(.bottom, 40) + } + + @ViewBuilder + func codeSheet() -> some View { + VStack(alignment: .leading, spacing: 20) { + sheetHandle + Text("참여 코드") + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + Text(store.room.joinCode) + .pretendardFont(.semiBold24) + .foregroundStyle(.primary500) + .frame(maxWidth: .infinity) + .padding(.vertical, 24) + .background(.beige600, in: RoundedRectangle(cornerRadius: 2)) + ShareLink(item: "[Picke] '\(store.room.name)' 클래스 참여 코드: \(store.room.joinCode)") { + Text("코드 공유하기") + .frame(maxWidth: .infinity) + } + .ctaButtonStyle(.primary, size: .large, height: 52) + } + .padding(.top, 16) + .padding(.horizontal, 16) + .padding(.bottom, 40) } } diff --git a/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift b/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift index 0ba8d673..0e6b7ccd 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift @@ -3,9 +3,9 @@ // Class // -import Foundation - +import ClassDomainInterface import ComposableArchitecture +import Foundation @Reducer public struct ClassIntroFeature { @@ -13,54 +13,94 @@ public struct ClassIntroFeature { @ObservableState public struct State: Equatable { + @Presents public var join: ClassJoinFeature.State? + public init() {} } public enum Action: ViewAction { case view(View) + case join(PresentationAction) + case inner(InnerAction) case delegate(DelegateAction) } @CasePathable public enum View { + case backTapped case joinTapped case myClassesTapped case createTapped case ticketTapped } + @CasePathable + public enum InnerAction: Equatable { + case nicknameModalPresented(ClassRoom) + } + @CasePathable public enum DelegateAction: Equatable { - case join + case backToHome + case joined(ClassRoom) case myClasses case create case ticket } + nonisolated enum CancelID: Hashable { + case nicknameModal + } + + @Dependency(\.continuousClock) private var clock + public var body: some Reducer { Reduce { state, action in switch action { case let .view(viewAction): - return handleViewAction( + // 이름 입력 모달 대기 중 다른 탭이 들어오면 예약된 모달을 취소한다. + return .merge( + .cancel(id: CancelID.nicknameModal), + handleViewAction( + state: &state, + action: viewAction + ) + ) + + case let .join(presentationAction): + return handleJoinAction( state: &state, - action: viewAction + action: presentationAction + ) + + case let .inner(innerAction): + return handleInnerAction( + state: &state, + action: innerAction ) case .delegate: return .none } } + .ifLet(\.$join, action: \.join) { + ClassJoinFeature() + } } } extension ClassIntroFeature { private func handleViewAction( - state _: inout State, + state: inout State, action: View ) -> Effect { switch action { + case .backTapped: + return .send(.delegate(.backToHome)) + case .joinTapped: - return .send(.delegate(.join)) + state.join = .init() + return .none case .myClassesTapped: return .send(.delegate(.myClasses)) @@ -72,4 +112,42 @@ extension ClassIntroFeature { return .send(.delegate(.ticket)) } } + + private func handleJoinAction( + state: inout State, + action: PresentationAction + ) -> Effect { + switch action { + case .presented(.delegate(.dismiss)): + state.join = nil + return .none + + case let .presented(.delegate(.found(room))): + state.join = nil + // PickeModal exit 애니메이션(0.3초)이 끝난 뒤 이름 입력 모달을 새로 띄운다. + return .run { [clock] send in + try await clock.sleep(for: .seconds(0.3)) + await send(.inner(.nicknameModalPresented(room))) + } + .cancellable(id: CancelID.nicknameModal, cancelInFlight: true) + + case let .presented(.delegate(.joined(room))): + state.join = nil + return .send(.delegate(.joined(room))) + + case .presented, .dismiss: + return .none + } + } + + private func handleInnerAction( + state: inout State, + action: InnerAction + ) -> Effect { + switch action { + case let .nicknameModalPresented(room): + state.join = .init(mode: .nickname, preview: room) + return .none + } + } } diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift index 1a5e96e6..8013b565 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -3,11 +3,10 @@ // Class // -import SwiftUI - import ComposableArchitecture import PickeDesignKit import PickeSharedUI +import SwiftUI @ViewAction(for: ClassIntroFeature.self) public struct ClassIntroView: View { @@ -19,14 +18,17 @@ public struct ClassIntroView: View { public var body: some View { VStack(spacing: 0) { - PickeNavigationBar(centerTitle: "클래스") - .foregroundStyle(.white) + PickeNavigationBar( + onBack: { send(.backTapped) }, + centerTitle: "클래스" + ) + .foregroundStyle(.beige50) VStack(alignment: .leading, spacing: 0) { titleSection() Spacer(minLength: 24) menuCards() - .padding(.bottom, 16) + .padding(.bottom, 6) createButton ticketButton } @@ -35,7 +37,15 @@ public struct ClassIntroView: View { .padding(.bottom, 16) } .background(backgroundImage()) - .toolbar(.hidden, for: .navigationBar) + .hidesSystemBars() + .pickeModal( + $store.scope( + state: \.join, + action: \.join + ) + ) { joinStore in + ClassJoinView(store: joinStore) + } } } @@ -53,16 +63,21 @@ private extension ClassIntroView { @ViewBuilder func titleSection() -> some View { - VStack(alignment: .leading, spacing: 8) { + VStack(spacing: 12) { Text("함께 생각하는\n수업을 시작해 보세요") .pretendardFont(family: .SemiBold, size: 24) - .lineSpacing(7) - .foregroundStyle(.white) + .kerning(-0.6) + .lineSpacing(2.4) + .multilineTextAlignment(.center) + .foregroundStyle(.beige50) Text("콘텐츠를 고르고, 클래스로 함께 나눠요.") .pretendardFont(family: .SemiBold, size: 16) + .kerning(-0.4) + .multilineTextAlignment(.center) .foregroundStyle(.gray300) } + .frame(maxWidth: .infinity) } @ViewBuilder @@ -98,11 +113,10 @@ private extension ClassIntroView { send(.ticketTapped) } label: { Text("이용권 등록하기") - .pretendardFont(family: .Medium, size: 14) + .pretendardFont(.labelSmall) .foregroundStyle(.gray300) - .underline() .frame(maxWidth: .infinity) - .padding(.vertical, 12) + .padding(.vertical, 10) } .buttonStyle(.plain) } diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift b/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift index f3e03109..f8393860 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/Components/ClassMenuCard.swift @@ -3,9 +3,8 @@ // Class // -import SwiftUI - import PickeDesignKit +import SwiftUI /// 클래스 인트로의 진입 카드 (참여하기 / 내 클래스). struct ClassMenuCard: View { @@ -16,27 +15,25 @@ struct ClassMenuCard: View { var body: some View { Button(action: action) { - ZStack(alignment: .bottomTrailing) { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .pretendardFont(.headingMedium) + .foregroundStyle(.beige50) + + Text(description) + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .topLeading) + .frame(height: 118, alignment: .top) + .background(alignment: .topLeading) { Image(asset: image) .resizable() .scaledToFit() - .frame(width: 96, height: 96) - .offset(x: 12, y: 12) - - VStack(alignment: .leading, spacing: 4) { - Text(title) - .pretendardFont(family: .SemiBold, size: 16) - .foregroundStyle(.white) - - Text(description) - .pretendardFont(family: .Regular, size: 13) - .foregroundStyle(.gray300) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .frame(width: 130, height: 130) + .offset(x: 59, y: 38) } - .padding(16) - .frame(maxWidth: .infinity) - .frame(height: 118) .background(.gray700, in: RoundedRectangle(cornerRadius: 2)) .overlay( RoundedRectangle(cornerRadius: 2) diff --git a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift index 5825f552..2f33f772 100644 --- a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift +++ b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift @@ -1,5 +1,7 @@ import ClassDomainInterface import ComposableArchitecture +import Foundation +import PickeSharedUI @Reducer public struct ClassMemberFeature { @@ -21,7 +23,9 @@ public struct ClassMemberFeature { public let room: ClassRoom public var members: [Member] + public var searchText = "" public var selectedMember: Member? + @Presents public var customAlert: CustomAlertState? public init(room: ClassRoom) { self.room = room @@ -30,10 +34,17 @@ public struct ClassMemberFeature { Member(id: index + 2, name: "학생 \(index + 1)") } } + + public var visibleMembers: [Member] { + let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) + return query.isEmpty ? members : members.filter { $0.name.localizedCaseInsensitiveContains(query) } + } } - public enum Action: ViewAction { + public enum Action: ViewAction, BindableAction { + case binding(BindingAction) case view(View) + case customAlert(PresentationAction) case delegate(DelegateAction) } @@ -49,14 +60,33 @@ public struct ClassMemberFeature { } public var body: some Reducer { + BindingReducer() Reduce { state, action in switch action { + case .binding: + return .none case let .view(viewAction): return handleViewAction(state: &state, action: viewAction) + case let .customAlert(alertAction): + switch alertAction { + case .presented(.confirmTapped): + guard let member = state.selectedMember else { return .none } + state.members.removeAll(where: { $0.id == member.id }) + state.selectedMember = nil + state.customAlert = nil + return .none + case .presented(.cancelTapped), .dismiss: + state.selectedMember = nil + state.customAlert = nil + return .none + } case .delegate: return .none } } + .ifLet(\.$customAlert, action: \.customAlert) { + CustomConfirmAlert() + } } } @@ -70,14 +100,23 @@ extension ClassMemberFeature { let member = state.members.first(where: { $0.id == id && !$0.isOwner }) else { return .none } state.selectedMember = member + state.customAlert = CustomAlertState( + title: "\(member.name)님을\n클래스에서 내보낼까요?", + confirmTitle: "내보내기", + cancelTitle: "뒤로가기", + isDestructive: true, + style: .deleteConfirm + ) return .none case .removeConfirmed: guard let member = state.selectedMember else { return .none } state.members.removeAll(where: { $0.id == member.id }) state.selectedMember = nil + state.customAlert = nil return .none case .removeCancelled: state.selectedMember = nil + state.customAlert = nil return .none } } diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift index 55201c76..e0f2e2f1 100644 --- a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -1,10 +1,11 @@ import ComposableArchitecture import PickeDesignKit +import PickeSharedUI import SwiftUI @ViewAction(for: ClassMemberFeature.self) public struct ClassMemberView: View { - public let store: StoreOf + @Bindable public var store: StoreOf public init(store: StoreOf) { self.store = store @@ -12,62 +13,81 @@ public struct ClassMemberView: View { public var body: some View { VStack(spacing: 0) { - PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "멤버 \(store.members.count)") + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "클래스 멤버") .foregroundStyle(.gray500) + searchField + .padding(.horizontal, 16) + .padding(.top, 12) + ScrollView { - LazyVStack(spacing: 0) { - ForEach(store.members) { member in + LazyVStack(alignment: .leading, spacing: 0) { + Text("멤버 \(store.members.count)") + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(.gray300) + .padding(.bottom, 4) + + ForEach(store.visibleMembers) { member in memberRow(member) } } .padding(.horizontal, 16) + .padding(.top, 20) } .scrollIndicators(.hidden) } .screenBackground() - .hidesSystemBars() - .alert( - "멤버를 삭제할까요?", - isPresented: Binding( - get: { store.selectedMember != nil }, - set: { - if !$0 { - send(.removeCancelled) - } - } - ) - ) { - Button("취소", role: .cancel) { send(.removeCancelled) } - Button("삭제", role: .destructive) { send(.removeConfirmed) } - } message: { - Text("삭제한 멤버는 참여 코드를 통해 다시 입장할 수 있어요.") - } + .toolbar(.hidden, for: .navigationBar) + .toolbar(.visible, for: .tabBar) + .customAlert($store.scope(state: \.customAlert, action: \.customAlert)) } } private extension ClassMemberView { + var searchField: some View { + HStack(spacing: 8) { + Image(systemName: "magnifyingglass") + .font(.system(size: 18)) + .foregroundStyle(.gray300) + TextField("멤버 검색", text: $store.searchText) + .pretendardFont(.medium15) + .foregroundStyle(.gray800) + } + .padding(.horizontal, 12) + .frame(height: 44) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + @ViewBuilder func memberRow(_ member: ClassMemberFeature.State.Member) -> some View { HStack(spacing: 12) { + PickeAvatarView(imageURL: nil, fallback: member.name, size: 36) + Text(member.name) - .pretendardFont(.medium15) + .pretendardFont(family: .Medium, size: 16) .foregroundStyle(.gray800) - if member.isOwner { - Text("선생님") - .pretendardFont(.medium13) - .foregroundStyle(.gray300) - } - Spacer(minLength: 0) - if store.room.role == .owner, !member.isOwner { - Button("삭제") { send(.removeTapped(member.id)) } - .pretendardFont(.medium13) - .foregroundStyle(.textError) + if member.isOwner { + Text("운영자") + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(.primary500) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.beige600, in: RoundedRectangle(cornerRadius: 2)) + } else if store.room.role == .owner { + Button { send(.removeTapped(member.id)) } label: { + Image(systemName: "minus.circle") + .font(.system(size: 20)) + .foregroundStyle(.gray500) + .frame(width: 32, height: 32) + } + .accessibilityLabel("\(member.name) 내보내기") } } - .padding(.vertical, 16) + .frame(height: 68) + .overlay(alignment: .bottom) { Rectangle().fill(.beige600).frame(height: 1) } } } diff --git a/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift b/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift new file mode 100644 index 00000000..199f3db2 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift @@ -0,0 +1,54 @@ +import ClassDomainInterface +import ComposableArchitecture + +@Reducer +public struct ClassReportFeature { + public init() {} + + public enum Tab: String, CaseIterable, Equatable { + case summary = "요약" + case participation = "내 참여" + case classResult = "클래스 결과" + case feedback = "피드백" + } + + @ObservableState + public struct State: Equatable { + public var room: ClassRoom + public var selectedTab: Tab = .summary + + public init(room: ClassRoom) { + self.room = room + } + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View: Equatable { + case backTapped + case tabSelected(Tab) + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + } + + public var body: some Reducer { + Reduce { state, action in + switch action { + case .view(.backTapped): + return .send(.delegate(.dismiss)) + case let .view(.tabSelected(tab)): + state.selectedTab = tab + return .none + case .delegate: + return .none + } + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift new file mode 100644 index 00000000..f0d80583 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -0,0 +1,331 @@ +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassReportFeature.self) +public struct ClassReportView: View { + public let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "내 배틀 리포트") { + Image(systemName: "ellipsis") + .font(.system(size: 20)) + .frame(width: 24, height: 24) + } + .foregroundStyle(.gray800) + + ScrollView { + VStack(spacing: 16) { + reportTitle() + reportTabs() + reportContent() + .padding(.horizontal, 16) + } + .padding(.top, 16) + .padding(.bottom, 16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .toolbar(.hidden, for: .navigationBar) + .toolbar(.visible, for: .tabBar) + } +} + +private extension ClassReportView { + @ViewBuilder + func reportTitle() -> some View { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + Text("참여완료").pickeBadge(.filled, size: .tag) + Text("#\(store.room.battle.category.title)").pickeBadge(.filled, size: .tag) + } + Text(store.room.battle.title) + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + .lineLimit(1) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + } + + @ViewBuilder + func reportTabs() -> some View { + HStack(spacing: 0) { + ForEach(ClassReportFeature.Tab.allCases, id: \.self) { tab in + Button { send(.tabSelected(tab)) } label: { + Text(tab.rawValue) + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(store.selectedTab == tab ? .primary500 : .gray300) + .frame(maxWidth: .infinity) + .frame(height: 44) + .overlay(alignment: .bottom) { + Rectangle() + .fill(store.selectedTab == tab ? .primary500 : .gray100) + .frame(height: store.selectedTab == tab ? 2 : 1) + } + } + .buttonStyle(.plain) + } + } + } + + @ViewBuilder + func reportContent() -> some View { + VStack(spacing: 16) { + switch store.selectedTab { + case .summary: + summaryContent() + case .participation: + participationContent() + case .classResult: + classResultContent() + case .feedback: + feedbackContent() + } + } + } + + @ViewBuilder + func summaryContent() -> some View { + reportCard { + cardHeading("내 참여 요약") + voteChange() + participationCounts() + } + reportCard { + cardHeading("AI 리포트") + Text("다른 해결방법까지 생각을 확장했어요") + .pretendardFont(family: .SemiBold, size: 18) + .foregroundStyle(.gray800) + Text("“교육과 보호도 함께 필요하다”는 대댓글에서 처벌 외의 대안을 제시했어요") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + reportCard { + cardHeading("선생님 피드백") + Text("“왜 생각이 달라졌는지 자신의 말로 설명한 점이 좋았어요”") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func participationContent() -> some View { + reportCard { + cardHeading("내 투표 결과") + voteChange() + participationCounts() + } + reportCard(padding: 16, spacing: 8) { + cardHeading("내 댓글") + HStack { + Text("사전 투표").pickeBadge(.filled, size: .tag) + Spacer() + Text("26.09.23. 14:02") + .pretendardFont(family: .SemiBold, size: 10) + .foregroundStyle(.gray300) + } + Text("청소년 범죄에는 책임을 물어야 한다고 생각해요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Text("댓글 1 · 좋아요 8") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + reportCard(padding: 16, spacing: 8) { + cardHeading("내 대댓글") + Text("처벌만으로는 해결되지 않아요. 교육과 보호도 함께 필요하다고 생각해요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Text("대댓글 12 · 좋아요 8") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func classResultContent() -> some View { + reportCard { + cardHeading("우리 클래스 참여 현황") + HStack { + Text("참여한 학생") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + Spacer() + Text("28 / 32") + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + } + voteBar("사전 투표 결과", primary: "낮춰야 한다", secondary: "교정이 우선이다", fraction: 0.67, color: .secondary500) + voteBar("사후 투표 결과", primary: "낮춰야 한다", secondary: "교정이 우선이다", fraction: 0.49, color: .primary500) + } + reportCard(padding: 16, spacing: 8) { + cardHeading("클래스 결과 요약") + Text("토론 후 교정과 보호가 우선이라는 의견이 늘었어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + } + reportCard(padding: 16, spacing: 8) { + cardHeading("BEST 댓글") + Text("김민지") + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(.gray500) + Text("서로 다른 의견을 듣고 다시 생각해 볼 수 있었어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Text("좋아요 8") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func feedbackContent() -> some View { + reportCard { + cardHeading("AI 피드백") + feedbackRow( + "생각의 변화", + description: "“처벌만으로는 해결되지 않는 점”을 언급하며, 처음과 입장이 달라진 이유를 설명했어요.", + icon: "arrow.left.arrow.right" + ) + Divider().overlay(.beige500) + feedbackRow("대안 제시", description: "반대의견에 그치지 않고 “교육과 보호”라는 대안을 덧붙였어요.", icon: "lightbulb") + Divider().overlay(.beige500) + feedbackRow("다음 단계", description: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지 사례나 자료를 찾아보세요.", icon: "sparkles") + Divider().overlay(.beige500) + Text("분석에 사용한 내 대댓글 보기") + .pretendardFont(family: .Bold, size: 12) + .foregroundStyle(.primary500) + .frame(maxWidth: .infinity, alignment: .leading) + } + reportCard(padding: 16, spacing: 8) { + cardHeading("다음 생각 해보기") + Text("교육과 보호가 재범을 줄이는 데 어떤 도움이 될까요?") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Text("내 의견과 반대되는 입장도 함께 떠올려 보세요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + reportCard(padding: 16, spacing: 8) { + cardHeading("선생님 피드백") + Text("처음과 나중의 생각이 어떻게 달라졌는지 스스로 정리한 점이 좋았어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Text("김민지 선생님 26.09.23. 14:02") + .pretendardFont(family: .SemiBold, size: 10) + .foregroundStyle(.gray300) + } + } + + @ViewBuilder + func reportCard( + padding: CGFloat = 12, + spacing: CGFloat = 12, + @ViewBuilder content: () -> some View + ) -> some View { + VStack(alignment: .leading, spacing: spacing, content: content) + .padding(.vertical, padding) + .padding(.horizontal, 16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + + @ViewBuilder + func cardHeading(_ title: String) -> some View { + Text(title) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + .frame(height: 39, alignment: .center) + } + + @ViewBuilder + func voteChange() -> some View { + HStack(spacing: 0) { + voteChoice("사전 선택", value: "낮춰야 한다") + Text("변화") + .pretendardFont(family: .Medium, size: 11) + .foregroundStyle(.beige50) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(.primary500, in: Capsule()) + voteChoice("사후 선택", value: "교정이 우선이다") + } + .frame(height: 60) + .background(.beige300, in: RoundedRectangle(cornerRadius: 2)) + } + + @ViewBuilder + func voteChoice(_ label: String, value: String) -> some View { + VStack(spacing: 4) { + Text(label) + .pretendardFont(family: .Medium, size: 11) + .foregroundStyle(.gray300) + Text(value) + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(.gray800) + } + .frame(maxWidth: .infinity) + } + + @ViewBuilder + func participationCounts() -> some View { + HStack(spacing: 0) { + count("1", label: "댓글") + count("12", label: "대댓글") + count("8", label: "받은 좋아요") + } + .frame(height: 55) + } + + @ViewBuilder + func count(_ value: String, label: String) -> some View { + VStack(spacing: 2) { + Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) + Text(label).pretendardFont(.regular13).foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity) + } + + @ViewBuilder + func voteBar(_ title: String, primary: String, secondary: String, fraction: CGFloat, color: Color) -> some View { + VStack(alignment: .leading, spacing: 6) { + Text(title).pretendardFont(.regular13).foregroundStyle(.gray500) + GeometryReader { proxy in + HStack(spacing: 12) { + Text(primary) + .pretendardFont(.regular13) + .foregroundStyle(.gray800) + .frame(width: proxy.size.width * fraction, height: 34) + .background(color, in: RoundedRectangle(cornerRadius: 2)) + Text(secondary) + .pretendardFont(.regular13) + .foregroundStyle(.gray500) + } + } + .frame(height: 34) + .background(.beige300, in: RoundedRectangle(cornerRadius: 2)) + } + } + + @ViewBuilder + func feedbackRow(_ title: String, description: String, icon: String) -> some View { + HStack(alignment: .top, spacing: 6) { + Image(systemName: icon) + .font(.system(size: 20)) + .foregroundStyle(.primary500) + .frame(width: 24, height: 24) + VStack(alignment: .leading, spacing: 2) { + Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) + Text(description).pretendardFont(.regular13).foregroundStyle(.gray500) + } + } + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift index 5324da0b..b67391bd 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift @@ -2,6 +2,7 @@ import ClassDomainInterface import ComposableArchitecture import Foundation +import PickeSharedUI import Testing @MainActor @@ -15,7 +16,7 @@ struct ClassDetailTests { let updated = room.deadline.addingTimeInterval(86400) await store.send(.view(.deadlineTapped)) { - $0.isDeadlineSheetPresented = true + $0.modal = .init(kind: .deadline) } await store.send(.binding(.set(\.draftDeadline, updated))) { $0.draftDeadline = updated @@ -40,7 +41,7 @@ struct ClassDetailTests { $0.isLoading = false $0.room = updatedRoom $0.deadline = updated - $0.isDeadlineSheetPresented = false + $0.modal = nil } } @@ -65,6 +66,17 @@ struct ClassDetailTests { await store.receive(\.delegate, .openBattle(room.battle)) } + @Test + func 결과보기를_누르면_현재_클래스를_리포트로_전달한다() async { + let room = ClassRoom.mocks[0] + let store = TestStore(initialState: ClassDetailFeature.State(room: room)) { + ClassDetailFeature() + } + + await store.send(.view(.reportTapped)) + await store.receive(\.delegate, .openReport(room)) + } + @Test func 삭제가_완료된_뒤에만_화면을_닫는다() async { let room = ClassRoom.mocks[0] @@ -73,10 +85,16 @@ struct ClassDetailTests { } await store.send(.view(.deleteTapped)) { - $0.isDeleteAlertPresented = true + $0.customAlert = CustomAlertState( + title: "삭제 후에는 복구할 수 없어요.\n그럼에도 삭제하시겠습니까?", + confirmTitle: "삭제하기", + cancelTitle: "뒤로가기", + isDestructive: true, + style: .deleteConfirm + ) } await store.send(.view(.deleteConfirmed)) { - $0.isDeleteAlertPresented = false + $0.customAlert = nil } await store.receive(\.async, .delete(room.id)) { $0.isLoading = true diff --git a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift index 518d0b28..8c75f1a6 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift @@ -1,6 +1,7 @@ @testable import Class import ClassDomainInterface import ComposableArchitecture +import PickeSharedUI import Testing @MainActor @@ -14,10 +15,18 @@ struct ClassMemberTests { await store.send(.view(.removeTapped(member.id))) { $0.selectedMember = member + $0.customAlert = CustomAlertState( + title: "\(member.name)님을\n클래스에서 내보낼까요?", + confirmTitle: "내보내기", + cancelTitle: "뒤로가기", + isDestructive: true, + style: .deleteConfirm + ) } - await store.send(.view(.removeConfirmed)) { + await store.send(.customAlert(.presented(.confirmTapped))) { $0.members.remove(at: 1) $0.selectedMember = nil + $0.customAlert = nil } #expect(store.state.members.count == ClassRoom.mocks[0].memberCount - 1) } @@ -31,4 +40,16 @@ struct ClassMemberTests { await store.send(.view(.removeTapped(2))) #expect(store.state.selectedMember == nil) } + + @Test + func 검색어에_맞는_멤버만_표시한다() async { + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + ClassMemberFeature() + } + + await store.send(.binding(.set(\.searchText, "학생 1"))) { + $0.searchText = "학생 1" + } + #expect(store.state.visibleMembers.allSatisfy { $0.name.contains("학생 1") }) + } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift new file mode 100644 index 00000000..45e1cb35 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift @@ -0,0 +1,28 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct ClassReportTests { + @Test + func 결과_탭을_선택하면_해당_내용으로_바뀐다() async { + let store = TestStore(initialState: ClassReportFeature.State(room: ClassRoom.mocks[0])) { + ClassReportFeature() + } + + await store.send(.view(.tabSelected(.classResult))) { + $0.selectedTab = .classResult + } + } + + @Test + func 결과_뒤로가기는_코디네이터에_닫기를_알린다() async { + let store = TestStore(initialState: ClassReportFeature.State(room: ClassRoom.mocks[0])) { + ClassReportFeature() + } + + await store.send(.view(.backTapped)) + await store.receive(\.delegate.dismiss) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassTests.swift b/Projects/Feature/Class/Tests/Sources/ClassTests.swift index 5bf545a0..26a8d315 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassTests.swift @@ -11,13 +11,55 @@ import Testing @MainActor struct ClassTests { @Test - func 참여하기_탭하면_참여_델리게이트를_보낸다() async { + func 참여하기_탭하면_코드_입력_모달을_띄운다() async { let store = TestStore(initialState: ClassIntroFeature.State()) { ClassIntroFeature() } - await store.send(.view(.joinTapped)) - await store.receive(\.delegate.join) + await store.send(.view(.joinTapped)) { + $0.join = ClassJoinFeature.State() + } + } + + @Test + func 코드_확인에_성공하면_코드_모달을_닫고_이름_입력_모달을_띄운다() async { + let clock = TestClock() + let room = ClassRoom.mockJoinable + var state = ClassIntroFeature.State() + state.join = ClassJoinFeature.State() + let store = TestStore(initialState: state) { + ClassIntroFeature() + } withDependencies: { + $0.continuousClock = clock + } + + await store.send(.join(.presented(.delegate(.found(room))))) { + $0.join = nil + } + await clock.advance(by: .seconds(0.3)) + await store.receive(\.inner.nicknameModalPresented, room) { + $0.join = ClassJoinFeature.State(mode: .nickname, preview: room) + } + } + + @Test + func 이름_입력_모달_대기_중_뒤로가면_예약된_모달을_취소한다() async { + let clock = TestClock() + let room = ClassRoom.mockJoinable + var state = ClassIntroFeature.State() + state.join = ClassJoinFeature.State() + let store = TestStore(initialState: state) { + ClassIntroFeature() + } withDependencies: { + $0.continuousClock = clock + } + + await store.send(.join(.presented(.delegate(.found(room))))) { + $0.join = nil + } + await store.send(.view(.backTapped)) + await store.receive(\.delegate.backToHome) + await clock.advance(by: .seconds(0.3)) } @Test From 9844a977cbf13b4d0905f7f2ac679e4ee4d292b6 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 16:34:32 +0900 Subject: [PATCH 24/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EC=BD=94=EB=93=9C=EC=99=80=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EB=AA=A8=EB=8B=AC=EC=9D=84=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClassJoin/Reducer/ClassJoinFeature.swift | 26 +-- .../ClassJoin/View/ClassJoinView.swift | 149 ++++++++++-------- .../Class/Tests/Sources/ClassJoinTests.swift | 7 +- 3 files changed, 98 insertions(+), 84 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift b/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift index e00625c0..28e1de04 100644 --- a/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift +++ b/Projects/Feature/Class/Sources/ClassJoin/Reducer/ClassJoinFeature.swift @@ -8,22 +8,32 @@ public struct ClassJoinFeature { public init() {} + public enum Mode: Equatable { + case code + case nickname + } + @ObservableState public struct State: Equatable { + public var mode: Mode public var joinCode = "" public var nickname = "" public var preview: ClassRoom? public var isLoading = false public var errorMessage: String? - public init() {} + public init(mode: Mode = .code, preview: ClassRoom? = nil) { + self.mode = mode + self.preview = preview + } public var canFindClass: Bool { - !joinCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading + mode == .code && !joinCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading } public var canJoin: Bool { - preview != nil && !nickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading + mode == .nickname && preview != nil && !nickname.trimmingCharacters(in: .whitespacesAndNewlines) + .isEmpty && !isLoading } } @@ -40,7 +50,6 @@ public struct ClassJoinFeature { case backTapped case findTapped case joinTapped - case previewDismissed } public enum AsyncAction: Equatable { @@ -56,6 +65,7 @@ public struct ClassJoinFeature { @CasePathable public enum DelegateAction: Equatable { case dismiss + case found(ClassRoom) case joined(ClassRoom) } @@ -102,11 +112,6 @@ private extension ClassJoinFeature { guard state.canJoin, let room = state.preview else { return .none } let nickname = state.nickname.trimmingCharacters(in: .whitespacesAndNewlines) return .send(.async(.join(joinCode: room.joinCode, nickname: nickname))) - - case .previewDismissed: - state.preview = nil - state.nickname = "" - return .none } } @@ -142,8 +147,7 @@ private extension ClassJoinFeature { state.isLoading = false switch action { case let .found(.success(room)): - state.preview = room - return .none + return .send(.delegate(.found(room))) case let .joined(.success(room)): state.preview = nil diff --git a/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift b/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift index 31eca21b..30dca4b0 100644 --- a/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift +++ b/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift @@ -6,6 +6,7 @@ import ClassDomainInterface import ComposableArchitecture import PickeDesignKit +import PickeSharedUI import SwiftUI @ViewAction(for: ClassJoinFeature.self) @@ -17,40 +18,45 @@ public struct ClassJoinView: View { } public var body: some View { - VStack(spacing: 0) { - PickeNavigationBar( - onBack: { send(.backTapped) }, - centerTitle: "클래스 참여하기" - ) - .foregroundStyle(.gray500) - - ScrollView { - contentSection() - .padding(16) + ZStack(alignment: .bottom) { + Color.black.opacity(0.28) + .ignoresSafeArea() + .onTapGesture { send(.backTapped) } + switch store.mode { + case .code: + codeSheet() + case .nickname: + nicknameSheet() } - .scrollIndicators(.hidden) - - primaryButton - .padding(.horizontal, 16) - .padding(.bottom, 16) } - .screenBackground() - .hidesSystemBars() } } private extension ClassJoinView { + @ViewBuilder + func codeSheet() -> some View { + VStack(alignment: .leading, spacing: 20) { + sheetHandle + contentSection() + primaryButton + } + .padding(.top, 12) + .padding(.horizontal, 16) + .padding(.bottom, 32) + .frame(maxWidth: .infinity) + .background( + UnevenRoundedRectangle(topLeadingRadius: 26, topTrailingRadius: 26) + .fill(.beige50) + .ignoresSafeArea(edges: .bottom) + ) + } + @ViewBuilder func contentSection() -> some View { - VStack(alignment: .leading, spacing: 28) { + VStack(alignment: .leading, spacing: 20) { titleSection() - if store.preview == nil { - codeSection() - } else { - previewSection() - nicknameSection() - } + codeSection() if let errorMessage = store.errorMessage { Text(errorMessage) @@ -63,17 +69,13 @@ private extension ClassJoinView { @ViewBuilder func titleSection() -> some View { VStack(alignment: .leading, spacing: 8) { - Text(store.preview == nil ? "참여 코드를 입력해주세요" : "이름을 입력해주세요") + Text("참여 코드를 입력해주세요") .pretendardFont(.semiBold24) .foregroundStyle(.gray800) - Text( - store.preview == nil - ? "선생님에게 받은 코드를 입력하면 클래스에 참여할 수 있어요." - : "이 클래스에서만 보이는 이름이에요." - ) - .pretendardFont(.medium15) - .foregroundStyle(.gray300) + Text("선생님에게 받은 코드를 입력하면 클래스에 참여할 수 있어요.") + .pretendardFont(.medium15) + .foregroundStyle(.gray300) } } @@ -95,56 +97,65 @@ private extension ClassJoinView { } @ViewBuilder - func previewSection() -> some View { - if let preview = store.preview { - VStack(alignment: .leading, spacing: 12) { - Text("참여할 클래스") - .pretendardFont(.semiBold15) + func nicknameSheet() -> some View { + VStack(alignment: .leading, spacing: 20) { + sheetHandle + + VStack(alignment: .leading, spacing: 6) { + Text("이름을 입력해주세요") + .pretendardFont(.semiBold24) .foregroundStyle(.gray800) + Text("이 클래스에서만 보이는 이름이에요.\n입장 후에도 수정할 수 있어요.") + .pretendardFont(family: .Regular, size: 14) + .foregroundStyle(.gray300) + } - VStack(alignment: .leading, spacing: 8) { - Text(preview.name) - .pretendardFont(family: .SemiBold, size: 18) - .foregroundStyle(.gray800) - - Text(preview.battle.title) - .pretendardFont(.medium15) - .foregroundStyle(.gray500) - - Text("현재 (preview.memberCount)명이 참여 중") - .pretendardFont(.medium13) - .foregroundStyle(.gray300) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(16) - .pickeCard(.beige50, border: .beige600) + nicknameInput + if let errorMessage = store.errorMessage { + Text(errorMessage) + .pretendardFont(.bodySmall) + .foregroundStyle(.errorDefault) } + joinButton } + .padding(.top, 12) + .padding(.horizontal, 16) + .padding(.bottom, 32) + .frame(maxWidth: .infinity) + .background( + UnevenRoundedRectangle(topLeadingRadius: 26, topTrailingRadius: 26) + .fill(.beige50) + .ignoresSafeArea(edges: .bottom) + ) } - @ViewBuilder - func nicknameSection() -> some View { - VStack(alignment: .leading, spacing: 12) { - Text("이름") - .pretendardFont(.semiBold15) - .foregroundStyle(.gray800) + var sheetHandle: some View { + Capsule() + .fill(.gray50) + .frame(width: 40, height: 4) + .frame(maxWidth: .infinity) + .padding(.bottom, 10) + } - TextField( - "입력해주세요", - text: $store.nickname - ) + var nicknameInput: some View { + TextField("이름을 입력해주세요", text: $store.nickname) .textInputAutocapitalization(.never) - .pickeTextField() + .pickeTextField(height: 52) + } + + var joinButton: some View { + Button { send(.joinTapped) } label: { + Text("입장하기") } + .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(!store.canJoin) } var primaryButton: some View { - Button { - send(store.preview == nil ? .findTapped : .joinTapped) - } label: { - Text(store.preview == nil ? "클래스 찾기" : "참여하기") + Button { send(.findTapped) } label: { + Text("클래스 찾기") } .ctaButtonStyle(.primary, size: .large, height: 52) - .disabled(store.preview == nil ? !store.canFindClass : !store.canJoin) + .disabled(!store.canFindClass) } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift index 59333744..a49edf93 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift @@ -17,7 +17,7 @@ struct ClassJoinTests { } @Test - func 참여코드를_조회하면_클래스_미리보기를_표시한다() async { + func 참여코드를_조회하면_코드_모달을_닫기_위해_클래스를_전달한다() async { let room = ClassRoom.mockJoinable let store = TestStore(initialState: ClassJoinFeature.State()) { ClassJoinFeature() @@ -33,16 +33,15 @@ struct ClassJoinTests { $0.isLoading = true } await store.receive(\.inner, .found(.success(room))) { - $0.preview = room $0.isLoading = false } + await store.receive(\.delegate.found, room) } @Test func 이름을_입력하고_참여하면_클래스_참여_델리게이트를_보낸다() async { let room = ClassRoom.mockJoinable - var state = ClassJoinFeature.State() - state.preview = room + var state = ClassJoinFeature.State(mode: .nickname, preview: room) state.nickname = " 민지 " let store = TestStore(initialState: state) { ClassJoinFeature() From f438565c735ee04268c9465d78b793bcf85af8da Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 16:34:36 +0900 Subject: [PATCH 25/40] =?UTF-8?q?=EB=82=B4=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=97=AD=ED=95=A0=20=ED=83=AD=EC=9D=84=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=ED=95=84=ED=84=B0=20=EC=B9=A9=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BE=BC=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyClass/Reducer/MyClassFeature.swift | 31 ++-- .../View/Components/MyClassRoomCard.swift | 146 ++++++++++++++++++ .../View/Components/MyClassSkeletonView.swift | 76 +++++++++ .../Sources/MyClass/View/MyClassView.swift | 92 ++--------- .../Class/Tests/Sources/MyClassTests.swift | 25 ++- 5 files changed, 268 insertions(+), 102 deletions(-) create mode 100644 Projects/Feature/Class/Sources/MyClass/View/Components/MyClassRoomCard.swift create mode 100644 Projects/Feature/Class/Sources/MyClass/View/Components/MyClassSkeletonView.swift diff --git a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift index ce73f4e5..52a18b06 100644 --- a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift +++ b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift @@ -7,12 +7,6 @@ public struct MyClassFeature { @ObservableState public struct State: Equatable { - public enum Ownership: Hashable, CaseIterable { - case all - case created - case joined - } - public enum Progress: Hashable, CaseIterable { case all case open @@ -20,26 +14,28 @@ public struct MyClassFeature { } public var rooms: [ClassRoom] = [] - public var ownership: Ownership = .all public var progress: Progress = .all public var isLoading = false public var errorMessage: String? public init() {} + public var shouldShowSkeleton: Bool { + isLoading && rooms.isEmpty + } + + /// 로드 실패 & 표시할 클래스가 없을 때 스켈레톤 대신 오류를 노출한다. + public var shouldShowLoadError: Bool { + errorMessage != nil && rooms.isEmpty + } + public var visibleRooms: [ClassRoom] { rooms.filter { room in - let matchesOwnership: Bool = switch ownership { - case .all: true - case .created: room.role == .owner - case .joined: room.role == .member - } - let matchesProgress: Bool = switch progress { + switch progress { case .all: true case .open: room.status == .open case .closed: room.status == .closed } - return matchesOwnership && matchesProgress } } } @@ -53,8 +49,8 @@ public struct MyClassFeature { public enum View { case onAppear + case retryTapped case backTapped - case ownershipTapped(State.Ownership) case progressTapped(State.Progress) case roomTapped(Int) } @@ -93,13 +89,10 @@ public struct MyClassFeature { extension MyClassFeature { private func handleViewAction(state: inout State, action: View) -> Effect { switch action { - case .onAppear: + case .onAppear, .retryTapped: return .send(.async(.fetchRooms)) case .backTapped: return .send(.delegate(.dismiss)) - case let .ownershipTapped(ownership): - state.ownership = ownership - return .none case let .progressTapped(progress): state.progress = progress return .none diff --git a/Projects/Feature/Class/Sources/MyClass/View/Components/MyClassRoomCard.swift b/Projects/Feature/Class/Sources/MyClass/View/Components/MyClassRoomCard.swift new file mode 100644 index 00000000..e857a25e --- /dev/null +++ b/Projects/Feature/Class/Sources/MyClass/View/Components/MyClassRoomCard.swift @@ -0,0 +1,146 @@ +// +// MyClassRoomCard.swift +// Class +// + +import ClassDomainInterface +import PickeDesignKit +import SwiftUI + +/// 내 클래스 목록 카드 (Figma 12178:8263 `Card/BattleListCard`). +struct MyClassRoomCard: View { + let room: ClassRoom + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 12) { + badgeRow() + titleSection() + } + + footer() + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .pickeCard(.beige50, border: .beige600) + .contentShape(Rectangle()) + } +} + +private extension MyClassRoomCard { + @ViewBuilder + func badgeRow() -> some View { + HStack(alignment: .top, spacing: 10) { + statusBadge + + Spacer(minLength: 0) + + Image(systemName: "ellipsis") + .font(.pretendardFontFamily(family: .Bold, size: 14)) + .foregroundStyle(.gray900) + .frame(width: 24, height: 24) + } + } + + @ViewBuilder + var statusBadge: some View { + switch room.status { + case .open: + Text("진행 중") + .pickeBadge(.filled, size: .tag) + case .closed: + Text("종료") + .pretendardFont(.semiBold12) + .foregroundStyle(.gray300) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .roundedBackground(.gray50) + } + } + + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(room.name) + .pretendardFont(.headingMedium) + .foregroundStyle(.gray500) + .lineLimit(1) + + Text(room.battle.title) + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + .lineLimit(1) + } + } + + @ViewBuilder + func footer() -> some View { + HStack(spacing: 6) { + metaItem( + systemName: "person", + text: "멤버 \(room.memberCount)명" + ) + + if let deadlineText { + Circle() + .fill(.gray300) + .frame(width: 2, height: 2) + + metaItem( + systemName: "calendar", + text: deadlineText + ) + } + + Spacer(minLength: 0) + + viewLabel + } + .padding(.top, 12) + .overlay(alignment: .top) { + Rectangle() + .fill(.beige600) + .frame(height: 1) + } + } + + @ViewBuilder + func metaItem( + systemName: String, + text: String + ) -> some View { + HStack(spacing: 2) { + Image(systemName: systemName) + .font(.pretendardFontFamily(family: .Regular, size: 10)) + .frame(width: 12, height: 12) + Text(text) + .pretendardFont(.bodySmall) + .lineLimit(1) + } + .foregroundStyle(.gray300) + } + + var viewLabel: some View { + HStack(spacing: 4) { + Text("보기") + .pretendardFont(family: .Bold, size: 12) + Image(systemName: "chevron.right") + .font(.pretendardFontFamily(family: .Bold, size: 9)) + .frame(width: 12, height: 12) + } + .foregroundStyle(.primary500) + } + + /// 마감일을 끈 클래스(`.distantFuture`)는 표시하지 않는다. + var deadlineText: String? { + guard room.deadline != .distantFuture else { return nil } + return "\(Self.deadlineFormatter.string(from: room.deadline))까지" + } + + static let deadlineFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.dateFormat = "yyyy. MM. dd. EEEE HH:mm" + return formatter + }() +} diff --git a/Projects/Feature/Class/Sources/MyClass/View/Components/MyClassSkeletonView.swift b/Projects/Feature/Class/Sources/MyClass/View/Components/MyClassSkeletonView.swift new file mode 100644 index 00000000..85d339b5 --- /dev/null +++ b/Projects/Feature/Class/Sources/MyClass/View/Components/MyClassSkeletonView.swift @@ -0,0 +1,76 @@ +// +// MyClassSkeletonView.swift +// Class +// + +import PickeDesignKit +import SwiftUI + +/// 내 클래스 목록 로딩 자리표시자. `MyClassRoomCard` 와 같은 자리에 블록을 둔다. +struct MyClassSkeletonView: View { + var body: some View { + VStack(spacing: 12) { + ForEach(0 ..< 3, id: \.self) { _ in + card() + } + } + .allowsHitTesting(false) + } +} + +private extension MyClassSkeletonView { + @ViewBuilder + func card() -> some View { + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 12) { + badgeRow() + titleSection() + } + + footer() + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .pickeCard(.beige50, border: .beige600) + } + + @ViewBuilder + func badgeRow() -> some View { + HStack(spacing: 10) { + bar(width: 44, height: 21) + Spacer(minLength: 0) + bar(width: 24, height: 24) + } + } + + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 4) { + bar(width: 160, height: 21) + bar(width: 220, height: 17) + } + } + + @ViewBuilder + func footer() -> some View { + HStack(spacing: 6) { + bar(width: 180, height: 17) + Spacer(minLength: 0) + bar(width: 32, height: 17) + } + .padding(.top, 12) + .overlay(alignment: .top) { + Rectangle() + .fill(.beige600) + .frame(height: 1) + } + } + + func bar( + width: CGFloat, + height: CGFloat + ) -> some View { + SkeletonView(.round(cornerRadius: 2)) + .frame(width: width, height: height) + } +} diff --git a/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift b/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift index ab7c709c..40ba8a9d 100644 --- a/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift +++ b/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift @@ -1,11 +1,12 @@ import ClassDomainInterface import ComposableArchitecture import PickeDesignKit +import PickeSharedUI import SwiftUI @ViewAction(for: MyClassFeature.self) public struct MyClassView: View { - public let store: StoreOf + @Bindable public var store: StoreOf public init(store: StoreOf) { self.store = store @@ -16,54 +17,31 @@ public struct MyClassView: View { PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: "내 클래스") .foregroundStyle(.gray500) - ownershipTabs() - ScrollView { - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 20) { progressChips() roomList() } - .padding(16) + .padding(.horizontal, 16) + .padding(.vertical, 20) } .scrollIndicators(.hidden) } .screenBackground() - .hidesSystemBars() + .toolbar(.hidden, for: .navigationBar) + .toolbar(.visible, for: .tabBar) .onAppear { send(.onAppear) } } } private extension MyClassView { - @ViewBuilder - func ownershipTabs() -> some View { - HStack(spacing: 0) { - ForEach(MyClassFeature.State.Ownership.allCases, id: \.self) { ownership in - Button { send(.ownershipTapped(ownership)) } label: { - Text(ownership.title) - .pretendardFont(.semiBold15) - .foregroundStyle(store.ownership == ownership ? .gray800 : .gray300) - .frame(maxWidth: .infinity) - .padding(.vertical, 16) - .overlay(alignment: .bottom) { - if store.ownership == ownership { - Rectangle() - .fill(.gray800) - .frame(height: 2) - } - } - } - .buttonStyle(.plain) - } - } - } - @ViewBuilder func progressChips() -> some View { HStack(spacing: 8) { ForEach(MyClassFeature.State.Progress.allCases, id: \.self) { progress in Button { send(.progressTapped(progress)) } label: { Text(progress.title) - .pickeChoiceChip(isSelected: store.progress == progress) + .pickeSortChip(isSelected: store.progress == progress) } .buttonStyle(.plain) } @@ -72,14 +50,11 @@ private extension MyClassView { @ViewBuilder func roomList() -> some View { - if store.isLoading { - ProgressView() - .frame(maxWidth: .infinity, minHeight: 160) - } else if let message = store.errorMessage { - Text(message) - .pretendardFont(.medium15) - .foregroundStyle(.textError) + if store.shouldShowLoadError { + PickeRetryErrorView(message: "클래스를 불러오지 못했어요") { send(.retryTapped) } .frame(maxWidth: .infinity, minHeight: 160) + } else if store.shouldShowSkeleton { + MyClassSkeletonView() } else if store.visibleRooms.isEmpty { Text("아직 클래스가 없어요") .pretendardFont(.medium15) @@ -89,54 +64,13 @@ private extension MyClassView { LazyVStack(spacing: 12) { ForEach(store.visibleRooms) { room in Button { send(.roomTapped(room.id)) } label: { - roomCard(room) + MyClassRoomCard(room: room) } .buttonStyle(.plain) } } } } - - @ViewBuilder - func roomCard(_ room: ClassRoom) -> some View { - VStack(alignment: .leading, spacing: 12) { - HStack(spacing: 8) { - Text(room.role == .owner ? "내가 만든 클래스" : "참여한 클래스") - .pretendardFont(.medium13) - .foregroundStyle(.gray300) - Spacer(minLength: 0) - Text(room.status == .open ? "진행 중" : "종료") - .pretendardFont(.medium13) - .foregroundStyle(.gray500) - } - - Text(room.name) - .pretendardFont(.bold18) - .foregroundStyle(.gray800) - - Text(room.battle.title) - .pretendardFont(.medium15) - .foregroundStyle(.gray500) - .lineLimit(2) - - Text("멤버 \(room.memberCount)명") - .pretendardFont(.medium13) - .foregroundStyle(.gray300) - } - .padding(16) - .frame(maxWidth: .infinity, alignment: .leading) - .background(.white, in: RoundedRectangle(cornerRadius: 12)) - } -} - -private extension MyClassFeature.State.Ownership { - var title: String { - switch self { - case .all: "전체" - case .created: "내가 만든 클래스" - case .joined: "참여한 클래스" - } - } } private extension MyClassFeature.State.Progress { diff --git a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift index 54772886..98df831a 100644 --- a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift +++ b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift @@ -6,20 +6,22 @@ import Testing @MainActor struct MyClassTests { @Test - func 소유_역할과_진행_상태를_함께_필터링한다() async { + func 진행_상태로_필터링한다() async { var state = MyClassFeature.State() state.rooms = ClassRoom.mocks let store = TestStore(initialState: state) { MyClassFeature() } - await store.send(.view(.ownershipTapped(.joined))) { - $0.ownership = .joined + await store.send(.view(.progressTapped(.closed))) { + $0.progress = .closed } + #expect(store.state.visibleRooms.isEmpty) + await store.send(.view(.progressTapped(.open))) { $0.progress = .open } - #expect(store.state.visibleRooms.map(\.id) == [2]) + #expect(store.state.visibleRooms.map(\.id) == [1, 2]) } @Test @@ -33,4 +35,19 @@ struct MyClassTests { await store.send(.view(.roomTapped(1))) await store.receive(\.delegate, .openRoom(ClassRoom.mocks[0])) } + + @Test + func 로딩_중이고_클래스가_없으면_스켈레톤을_보여주고_실패하면_오류로_바꾼다() { + var state = MyClassFeature.State() + state.isLoading = true + #expect(state.shouldShowSkeleton) + + state.isLoading = false + state.errorMessage = "error" + #expect(state.shouldShowSkeleton == false) + #expect(state.shouldShowLoadError) + + state.rooms = ClassRoom.mocks + #expect(state.shouldShowLoadError == false) + } } From 6ca5383ea32473e4381c7617c657a6443f707e5b Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 16:35:23 +0900 Subject: [PATCH 26/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=EA=B3=BC=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=9E=AC=EC=8B=9C=EB=8F=84=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reducer/ClassRecommendFeature.swift | 20 ++++- .../View/ClassRecommendView.swift | 19 ++++- .../ClassRecommendSkeletonView.swift | 77 +++++++++++++++++++ .../Tests/Sources/ClassRecommendTests.swift | 15 ++++ 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassRecommendSkeletonView.swift diff --git a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift index 37900787..0321bec9 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift @@ -19,6 +19,7 @@ public struct ClassRecommendFeature { public var battles: [ClassBattleSummary] = [] public var selectedBattleId: Int? public var isLoading = false + public var loadFailed = false public init(filter: ClassTopicFilter) { self.filter = filter @@ -39,6 +40,15 @@ public struct ClassRecommendFeature { public var canSelect: Bool { selectedBattle != nil } + + public var shouldShowSkeleton: Bool { + isLoading && battles.isEmpty + } + + /// 로드 실패 & 표시할 배틀이 없을 때 스켈레톤 대신 오류를 노출한다. + public var shouldShowLoadError: Bool { + loadFailed && battles.isEmpty + } } public enum Action: ViewAction { @@ -51,6 +61,7 @@ public struct ClassRecommendFeature { @CasePathable public enum View { case onAppear + case retryTapped case backTapped case editConditionTapped case battleTapped(Int) @@ -105,6 +116,9 @@ extension ClassRecommendFeature { guard state.battles.isEmpty else { return .none } return .send(.async(.fetch(state.filter))) + case .retryTapped: + return .send(.async(.fetch(state.filter))) + case .backTapped, .editConditionTapped: return .send(.delegate(.dismiss)) @@ -128,6 +142,7 @@ extension ClassRecommendFeature { switch action { case let .fetch(filter): state.isLoading = true + state.loadFailed = false return .run { [classUseCase] send in let result = await Result { try await classUseCase.fetchRecommendedBattles(filter: filter) @@ -146,8 +161,11 @@ extension ClassRecommendFeature { switch action { case let .battles(result): state.isLoading = false - if case let .success(battles) = result { + switch result { + case let .success(battles): state.battles = battles + case .failure: + state.loadFailed = true } return .none } diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift index 9d39e503..37532344 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift @@ -6,6 +6,7 @@ import ClassDomainInterface import ComposableArchitecture import PickeDesignKit +import PickeSharedUI import SwiftUI @ViewAction(for: ClassRecommendFeature.self) @@ -49,7 +50,7 @@ private extension ClassRecommendView { VStack(alignment: .leading, spacing: 6) { Text("수업에 어울리는\n배틀을 찾았어요") .pretendardFont(.semiBold24) - .lineSpacing(7) + .lineSpacing(2.4) .foregroundStyle(.gray800) Text("미리 듣고 클래스에 사용할 배틀을 골라보세요.") @@ -90,6 +91,21 @@ private extension ClassRecommendView { @ViewBuilder func resultSection() -> some View { + Group { + if store.shouldShowLoadError { + PickeRetryErrorView(message: "추천 배틀을 불러오지 못했어요") { send(.retryTapped) } + .frame(maxWidth: .infinity, minHeight: 160) + } else if store.shouldShowSkeleton { + ClassRecommendSkeletonView() + } else { + resultList() + } + } + .padding([.horizontal, .top], 16) + } + + @ViewBuilder + func resultList() -> some View { VStack(alignment: .leading, spacing: 16) { Text(store.resultTitle) .pretendardFont(.headingSmall) @@ -104,7 +120,6 @@ private extension ClassRecommendView { .onTapGesture { send(.battleTapped(battle.id)) } } } - .padding([.horizontal, .top], 16) } @ViewBuilder diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassRecommendSkeletonView.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassRecommendSkeletonView.swift new file mode 100644 index 00000000..4f01008b --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassRecommendSkeletonView.swift @@ -0,0 +1,77 @@ +// +// ClassRecommendSkeletonView.swift +// Class +// + +import PickeDesignKit +import SwiftUI + +/// 추천 결과 로딩 자리표시자. 결과 제목 + `ClassBattleListCard` 와 같은 자리에 블록을 둔다. +struct ClassRecommendSkeletonView: View { + var body: some View { + VStack(alignment: .leading, spacing: 16) { + bar(width: 76, height: 20) + + ForEach(0 ..< 3, id: \.self) { _ in + card() + } + } + .allowsHitTesting(false) + } +} + +private extension ClassRecommendSkeletonView { + @ViewBuilder + func card() -> some View { + VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 12) { + metaRow() + titleSection() + } + versusRow() + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .pickeCard(.beige50, border: .beige600) + } + + @ViewBuilder + func metaRow() -> some View { + HStack(spacing: 10) { + bar(width: 41, height: 21) + Spacer(minLength: 0) + bar(width: 36, height: 17) + bar(width: 60, height: 17) + SkeletonView(.round(cornerRadius: 10)) + .frame(width: 20, height: 20) + } + } + + @ViewBuilder + func titleSection() -> some View { + VStack(alignment: .leading, spacing: 4) { + bar(width: 200, height: 18) + bar(width: 280, height: 17) + } + } + + @ViewBuilder + func versusRow() -> some View { + HStack(spacing: 8) { + SkeletonView(.round(cornerRadius: 2)) + .frame(height: 58) + SkeletonView(.round(cornerRadius: 12)) + .frame(width: 24, height: 24) + SkeletonView(.round(cornerRadius: 2)) + .frame(height: 58) + } + } + + func bar( + width: CGFloat, + height: CGFloat + ) -> some View { + SkeletonView(.round(cornerRadius: 2)) + .frame(width: width, height: height) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift index 3ec651d4..d324a16c 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift @@ -53,4 +53,19 @@ struct ClassRecommendTests { } await store.send(.view(.selectTapped)) } + + @Test + func 로딩_중이고_배틀이_없으면_스켈레톤을_보여주고_실패하면_오류로_바꾼다() { + var state = ClassRecommendFeature.State(filter: .init()) + state.isLoading = true + #expect(state.shouldShowSkeleton) + + state.isLoading = false + state.loadFailed = true + #expect(state.shouldShowSkeleton == false) + #expect(state.shouldShowLoadError) + + state.battles = ClassBattleSummary.mocks + #expect(state.shouldShowLoadError == false) + } } From 3cd33143f26274664a66508717815bd347ac2f0f Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 16:35:23 +0900 Subject: [PATCH 27/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A3=BC?= =?UTF-8?q?=EC=A0=9C=C2=B7=EC=84=A4=EC=A0=95=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=ED=96=89=EA=B0=84=EA=B3=BC=20=EB=82=9C?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=20=EC=84=B8=EA=B7=B8=EB=A8=BC=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=EC=9D=84=20=ED=94=BC=EA=B7=B8=EB=A7=88?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=98=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClassSetting/View/ClassSettingView.swift | 2 +- .../Sources/ClassTopic/View/ClassTopicView.swift | 12 +++++++++--- .../Sources/UI/Navigaion/PickeBoxSegment.swift | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift index 07940e35..aca19af6 100644 --- a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift +++ b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift @@ -30,7 +30,7 @@ public struct ClassSettingView: View { VStack(alignment: .leading, spacing: 32) { Text("수업 정보에 맞게\n클래스를 설정해 주세요") .pretendardFont(.semiBold24) - .lineSpacing(7) + .lineSpacing(2.4) .foregroundStyle(.gray500) nameSection() diff --git a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift index d637fb12..e9eeb411 100644 --- a/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift +++ b/Projects/Feature/Class/Sources/ClassTopic/View/ClassTopicView.swift @@ -11,6 +11,7 @@ import SwiftUI @ViewAction(for: ClassTopicFeature.self) public struct ClassTopicView: View { @Bindable public var store: StoreOf + @Namespace private var levelNamespace public init(store: StoreOf) { self.store = store @@ -49,7 +50,7 @@ private extension ClassTopicView { VStack(alignment: .leading, spacing: 6) { Text("어떤 주제로\n이야기 나눌까요?") .pretendardFont(.semiBold24) - .lineSpacing(7) + .lineSpacing(2.4) .foregroundStyle(.gray800) Text("관심 있는 주제나 수업 조건을 선택해 주세요.") @@ -99,10 +100,15 @@ private extension ClassTopicView { HStack(spacing: 0) { ForEach(ClassAudienceLevel.allCases, id: \.self) { level in Button { - store.level = level + withAnimation(.snappy(duration: 0.25)) { + store.level = level + } } label: { Text(level.title) - .pickeBoxSegment(isSelected: store.level == level) + .pickeBoxSegment( + isSelected: store.level == level, + namespace: levelNamespace + ) } .buttonStyle(.plain) } diff --git a/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift b/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift index e2fbfc83..85339d6a 100644 --- a/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift +++ b/Projects/UI/PickeDesignKit/Sources/UI/Navigaion/PickeBoxSegment.swift @@ -7,15 +7,25 @@ import SwiftUI public extension View { /// 박스형 세그먼트의 한 칸. 선택 시 beige50 박스 + primary500 텍스트. - func pickeBoxSegment(isSelected: Bool) -> some View { + /// `namespace` 를 넘기면 선택 박스가 칸 사이를 미끄러지듯 이동한다. + func pickeBoxSegment( + isSelected: Bool, + namespace: Namespace.ID? = nil + ) -> some View { pretendardFont(isSelected ? .headingSmall : .bodyMedium) .foregroundStyle(isSelected ? .primary500 : .gray300) .frame(maxWidth: .infinity) .frame(height: 36) .background { if isSelected { - RoundedRectangle(cornerRadius: .radiusDefault) - .fill(.beige50) + if let namespace { + RoundedRectangle(cornerRadius: .radiusDefault) + .fill(.beige50) + .matchedGeometryEffect(id: "pickeBoxSegment", in: namespace) + } else { + RoundedRectangle(cornerRadius: .radiusDefault) + .fill(.beige50) + } } } .contentShape(Rectangle()) From e24735507425fa560cba8d1ebff9d9b5b6b09050 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:01:46 +0900 Subject: [PATCH 28/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=A6=AC?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=20=ED=99=94=EB=A9=B4=EC=9D=84=20=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=A7=88=20=EA=B5=AC=EC=84=B1=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClassReport/View/ClassReportView.swift | 104 ++++++++++++------ 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift index f0d80583..de58e3d3 100644 --- a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -94,12 +94,12 @@ private extension ClassReportView { @ViewBuilder func summaryContent() -> some View { reportCard { - cardHeading("내 참여 요약") + cardHeading("내 참여 요약", destination: .participation) voteChange() participationCounts() } reportCard { - cardHeading("AI 리포트") + cardHeading("AI 리포트", destination: .feedback) Text("다른 해결방법까지 생각을 확장했어요") .pretendardFont(family: .SemiBold, size: 18) .foregroundStyle(.gray800) @@ -108,7 +108,7 @@ private extension ClassReportView { .foregroundStyle(.gray300) } reportCard { - cardHeading("선생님 피드백") + cardHeading("선생님 피드백", destination: .feedback) Text("“왜 생각이 달라졌는지 자신의 말로 설명한 점이 좋았어요”") .pretendardFont(.regular13) .foregroundStyle(.gray300) @@ -125,27 +125,48 @@ private extension ClassReportView { reportCard(padding: 16, spacing: 8) { cardHeading("내 댓글") HStack { - Text("사전 투표").pickeBadge(.filled, size: .tag) + Text("낮춰야 한다").pickeBadge(.filled, size: .tag) Spacer() Text("26.09.23. 14:02") .pretendardFont(family: .SemiBold, size: 10) .foregroundStyle(.gray300) } - Text("청소년 범죄에는 책임을 물어야 한다고 생각해요.") + Text("청소년 범죄가 날로 잔혹해지는 만큼 처벌 연령을 낮춰야 한다고 생각해요. 피해자 보호를 위해서라도 책임을 물어야 해요.") .pretendardFont(.regular13) .foregroundStyle(.gray700) - Text("댓글 1 · 좋아요 8") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + HStack(spacing: 12) { + Label("1,340", systemImage: "heart") + Label("23", systemImage: "bubble") + } + .pretendardFont(.regular13) + .foregroundStyle(.gray300) } reportCard(padding: 16, spacing: 8) { cardHeading("내 대댓글") + VStack(alignment: .leading, spacing: 4) { + Text("김민지 · 교정이 우선이다") + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(.gray500) + Text("“피해자 보호를 위한 기준도 함께 필요하지 않을까요?”") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige300, in: RoundedRectangle(cornerRadius: 2)) + Text("↳ 내 대댓글") + .pretendardFont(family: .SemiBold, size: 12) + .foregroundStyle(.primary500) Text("처벌만으로는 해결되지 않아요. 교육과 보호도 함께 필요하다고 생각해요.") .pretendardFont(.regular13) .foregroundStyle(.gray700) - Text("대댓글 12 · 좋아요 8") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + HStack { + Text("26.09.23. 14:02") + Spacer() + Label("12", systemImage: "heart") + } + .pretendardFont(family: .SemiBold, size: 10) + .foregroundStyle(.gray300) } } @@ -167,19 +188,19 @@ private extension ClassReportView { } reportCard(padding: 16, spacing: 8) { cardHeading("클래스 결과 요약") - Text("토론 후 교정과 보호가 우선이라는 의견이 늘었어요.") + Text("입장을 바꾼 학생은 6명이에요.\n연령하향 → 교정우선 5명, 교정 우선 → 연령 하향 1명이에요.\n교정 우선 선택은 10명에서 14명으로 늘었어요.") .pretendardFont(.regular13) .foregroundStyle(.gray700) } reportCard(padding: 16, spacing: 8) { cardHeading("BEST 댓글") - Text("김민지") + Text("김민지 · 교정이 우선이다") .pretendardFont(family: .SemiBold, size: 12) .foregroundStyle(.gray500) - Text("서로 다른 의견을 듣고 다시 생각해 볼 수 있었어요.") + Text("“피해자 보호를 위한 기준도 함께 필요하지 않을까요?”") .pretendardFont(.regular13) .foregroundStyle(.gray700) - Text("좋아요 8") + Text("좋아요 1,340 · 대댓글 23") .pretendardFont(.regular13) .foregroundStyle(.gray300) } @@ -190,14 +211,14 @@ private extension ClassReportView { reportCard { cardHeading("AI 피드백") feedbackRow( - "생각의 변화", - description: "“처벌만으로는 해결되지 않는 점”을 언급하며, 처음과 입장이 달라진 이유를 설명했어요.", + "내 생각의 변화를 설명했어요", + description: "“처벌만으로는 해결되지 않는 점”을 언급하며, 처음과 입장이 달리진 이유를 드러냈어요.", icon: "arrow.left.arrow.right" ) Divider().overlay(.beige500) - feedbackRow("대안 제시", description: "반대의견에 그치지 않고 “교육과 보호”라는 대안을 덧붙였어요.", icon: "lightbulb") + feedbackRow("다른 해결방법을 제안했어요", description: "반대의견에 그치지 않고 “교육과 보호”라는 대안을 덧붙였어요.", icon: "lightbulb") Divider().overlay(.beige500) - feedbackRow("다음 단계", description: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지 사례나 자료를 찾아보세요.", icon: "sparkles") + feedbackRow("근거를 한 단계 더 구체적으로", description: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지, 사례나 자료 하나를 연결하면 주장이 더 설득력 있어져요.", icon: "sparkles") Divider().overlay(.beige500) Text("분석에 사용한 내 대댓글 보기") .pretendardFont(family: .Bold, size: 12) @@ -206,7 +227,7 @@ private extension ClassReportView { } reportCard(padding: 16, spacing: 8) { cardHeading("다음 생각 해보기") - Text("교육과 보호가 재범을 줄이는 데 어떤 도움이 될까요?") + Text("처벌과 교화를 함께 한다면,\n무엇을 기준으로 균형을 잡을까요?") .pretendardFont(.regular13) .foregroundStyle(.gray700) Text("내 의견과 반대되는 입장도 함께 떠올려 보세요.") @@ -215,12 +236,16 @@ private extension ClassReportView { } reportCard(padding: 16, spacing: 8) { cardHeading("선생님 피드백") - Text("처음과 나중의 생각이 어떻게 달라졌는지 스스로 정리한 점이 좋았어요.") + Text("처음과 나중의 생각이 어떻게 달라졌는지 스스로 정리한 점이 좋았어요. 단순히 입장을 바꾸는 데서 끝나지 않고, 왜 바뀌었는지 설명하려는 태도가 인상적이었습니다.") .pretendardFont(.regular13) .foregroundStyle(.gray700) - Text("김민지 선생님 26.09.23. 14:02") - .pretendardFont(family: .SemiBold, size: 10) - .foregroundStyle(.gray300) + HStack { + Text("김민지 선생님") + Spacer() + Text("26.09.23. 14:02") + } + .pretendardFont(family: .SemiBold, size: 10) + .foregroundStyle(.gray300) } } @@ -239,19 +264,34 @@ private extension ClassReportView { } @ViewBuilder - func cardHeading(_ title: String) -> some View { - Text(title) - .pretendardFont(.semiBold15) - .foregroundStyle(.gray800) - .frame(height: 39, alignment: .center) + func cardHeading(_ title: String, destination: ClassReportFeature.Tab? = nil) -> some View { + HStack { + Text(title) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + Spacer(minLength: 0) + if let destination { + Button { send(.tabSelected(destination)) } label: { + HStack(spacing: 4) { + Text("보기") + .pretendardFont(family: .Bold, size: 12) + Image(systemName: "chevron.right") + .font(.system(size: 10, weight: .semibold)) + } + .foregroundStyle(.primary500) + } + .buttonStyle(.plain) + } + } + .frame(height: 39) } @ViewBuilder func voteChange() -> some View { HStack(spacing: 0) { voteChoice("사전 선택", value: "낮춰야 한다") - Text("변화") - .pretendardFont(family: .Medium, size: 11) + Text("입장 변경") + .pretendardFont(family: .SemiBold, size: 10) .foregroundStyle(.beige50) .padding(.horizontal, 6) .padding(.vertical, 2) @@ -279,7 +319,9 @@ private extension ClassReportView { func participationCounts() -> some View { HStack(spacing: 0) { count("1", label: "댓글") + Rectangle().fill(.beige600).frame(width: 1, height: 47) count("12", label: "대댓글") + Rectangle().fill(.beige600).frame(width: 1, height: 47) count("8", label: "받은 좋아요") } .frame(height: 55) From 20cc6df5486895a8190e8386cf5fdc379a4dc51e Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:06:41 +0900 Subject: [PATCH 29/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=C2=B7=EC=B6=94=EC=B2=9C=C2=B7=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B0=84=EA=B2=A9=EA=B3=BC=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=95=88=EB=82=B4=20=ED=8F=B0=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=ED=94=BC=EA=B7=B8=EB=A7=88=EC=97=90=20=EB=A7=9E=EC=B6=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Class/Sources/ClassIntro/View/ClassIntroView.swift | 2 +- .../Sources/ClassRecommend/View/ClassRecommendView.swift | 1 - .../Class/Sources/ClassSetting/View/ClassSettingView.swift | 6 ++++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift index 8013b565..78b9d367 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -26,7 +26,7 @@ public struct ClassIntroView: View { VStack(alignment: .leading, spacing: 0) { titleSection() - Spacer(minLength: 24) + Spacer(minLength: 20) menuCards() .padding(.bottom, 6) createButton diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift index 37532344..0e5ad62d 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift @@ -135,7 +135,6 @@ private extension ClassRecommendView { selectButton .padding(.horizontal, 12) - .padding(.bottom, 16) .background(.beige200) } } diff --git a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift index aca19af6..32df92a6 100644 --- a/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift +++ b/Projects/Feature/Class/Sources/ClassSetting/View/ClassSettingView.swift @@ -62,6 +62,12 @@ private extension ClassSettingView { "", text: $store.name, prompt: Text("ex) 2학년 3반 1학기 토론") + .font( + .pretendardFontFamily( + family: .Medium, + size: 13 + ) + ) .foregroundStyle(.gray300) ) .pickeTextField() From b7207cd69896ad7173abf6dc8a90c45cb2155007 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:06:47 +0900 Subject: [PATCH 30/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=EC=99=80=20=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=EB=A5=BC=20=ED=94=BC=EA=B7=B8=EB=A7=88=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ClassMember/Reducer/ClassMemberFeature.swift | 2 +- .../Class/Sources/ClassMember/View/ClassMemberView.swift | 2 +- .../Class/Sources/ClassReport/View/ClassReportView.swift | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift index 2f33f772..12c8d38c 100644 --- a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift +++ b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift @@ -101,7 +101,7 @@ extension ClassMemberFeature { else { return .none } state.selectedMember = member state.customAlert = CustomAlertState( - title: "\(member.name)님을\n클래스에서 내보낼까요?", + title: "\(member.name)님을 클래스에서 내보낼까요?\n한 번 내보내면 되돌릴 수 없어요.", confirmTitle: "내보내기", cancelTitle: "뒤로가기", isDestructive: true, diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift index e0f2e2f1..a5dad328 100644 --- a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -49,7 +49,7 @@ private extension ClassMemberView { Image(systemName: "magnifyingglass") .font(.system(size: 18)) .foregroundStyle(.gray300) - TextField("멤버 검색", text: $store.searchText) + TextField("이름을 입력해 주세요.", text: $store.searchText) .pretendardFont(.medium15) .foregroundStyle(.gray800) } diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift index de58e3d3..4beb77f6 100644 --- a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -125,13 +125,13 @@ private extension ClassReportView { reportCard(padding: 16, spacing: 8) { cardHeading("내 댓글") HStack { - Text("낮춰야 한다").pickeBadge(.filled, size: .tag) + Text("낮춰야한다").pickeBadge(.filled, size: .tag) Spacer() Text("26.09.23. 14:02") .pretendardFont(family: .SemiBold, size: 10) .foregroundStyle(.gray300) } - Text("청소년 범죄가 날로 잔혹해지는 만큼 처벌 연령을 낮춰야 한다고 생각해요. 피해자 보호를 위해서라도 책임을 물어야 해요.") + Text("제도화가 무서운 건, 사회적 압력이 '선택'을 '의무'로 바꿀 수 있다는 거예요. 네덜란드 사례를 보면 우려가 현실이 되고 있죠.") .pretendardFont(.regular13) .foregroundStyle(.gray700) HStack(spacing: 12) { @@ -147,7 +147,7 @@ private extension ClassReportView { Text("김민지 · 교정이 우선이다") .pretendardFont(family: .SemiBold, size: 12) .foregroundStyle(.gray500) - Text("“피해자 보호를 위한 기준도 함께 필요하지 않을까요?”") + Text("제도화가 무서운 건, 사회적 압력이 '선택'을 '의무'로 바꿀 수 있다는 거예요.") .pretendardFont(.regular13) .foregroundStyle(.gray300) } @@ -157,7 +157,7 @@ private extension ClassReportView { Text("↳ 내 대댓글") .pretendardFont(family: .SemiBold, size: 12) .foregroundStyle(.primary500) - Text("처벌만으로는 해결되지 않아요. 교육과 보호도 함께 필요하다고 생각해요.") + Text("토론을 들으면서 처벌만으로는 해결되지 않는 점을 이해하게 됐어요. 교육과 보호도 함께 필요하다고 생각해요.") .pretendardFont(.regular13) .foregroundStyle(.gray700) HStack { From 602d9235224482eb1f5b0087b8934bd6c22d5ffb Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:07:51 +0900 Subject: [PATCH 31/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9D=98=20=ED=83=AD=EB=B0=94=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EC=99=80=20=ED=99=88=20=EB=B3=B5=EA=B7=80=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EB=A5=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/Navigation/MainTab/MainTabCoordinator.swift | 6 +++++- Projects/App/Sources/Navigation/MainTab/MainTabView.swift | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift index a71def14..744b5ad8 100644 --- a/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift @@ -64,7 +64,7 @@ public struct MainTabCoordinator { case .quickBattle: return quickBattleState.routes.count > 1 case .classroom: - return classState.routes.count > 1 + return !classState.showsTabBar case .myPage: return myPageState.routes.count > 1 case .none: @@ -139,6 +139,10 @@ public struct MainTabCoordinator { state.selectedTab = state.previousTab return .none + case .classroom(.router(.routeAction(_, action: .intro(.delegate(.backToHome))))): + state.selectedTab = state.previousTab + return .none + case .myPage(.router(.routeAction(_, action: .settings(.delegate(.sessionEnded))))): return .send(.delegate(.sessionEnded)) diff --git a/Projects/App/Sources/Navigation/MainTab/MainTabView.swift b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift index 5d1dc718..c11e6259 100644 --- a/Projects/App/Sources/Navigation/MainTab/MainTabView.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabView.swift @@ -41,7 +41,7 @@ public struct MainTabView: View { tabContent(for: $0) } .tint(.neutral900) - .toolbar(store.shouldHideTabBar ? .hidden : .automatic, for: .tabBar) + .toolbar(store.shouldHideTabBar ? .hidden : .visible, for: .tabBar) } } From d4ba3c782d18770bc94955dbe8db0b79d226a811 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:08:08 +0900 Subject: [PATCH 32/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=AA=A8=EB=8B=AC=20=EC=83=81=ED=83=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=93=80=EC=84=9C=EB=A5=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Modal/ClassModalFeature.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Projects/Feature/Class/Sources/Modal/ClassModalFeature.swift diff --git a/Projects/Feature/Class/Sources/Modal/ClassModalFeature.swift b/Projects/Feature/Class/Sources/Modal/ClassModalFeature.swift new file mode 100644 index 00000000..2ae5893e --- /dev/null +++ b/Projects/Feature/Class/Sources/Modal/ClassModalFeature.swift @@ -0,0 +1,30 @@ +import ComposableArchitecture + +@Reducer +public struct ClassModalFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public enum Kind: Equatable { + case nickname + case management + case deadline + case code + } + + public let kind: Kind + + public init(kind: Kind) { + self.kind = kind + } + } + + public enum Action { + case dismissTapped + } + + public var body: some Reducer { + EmptyReducer() + } +} From 900cdab981f96a4f598b96fefed1b2fa004e814f Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:45:38 +0900 Subject: [PATCH 33/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=ED=83=AD?= =?UTF-8?q?=EB=B0=94=20=EC=88=A8=EA=B9=80=EC=9D=84=20=EC=BD=94=EB=94=94?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=84=B0=EC=97=90=20=EB=A7=A1=EA=B8=B4?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift index 78b9d367..deb9fcc6 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -37,7 +37,7 @@ public struct ClassIntroView: View { .padding(.bottom, 16) } .background(backgroundImage()) - .hidesSystemBars() + .toolbar(.hidden, for: .navigationBar) .pickeModal( $store.scope( state: \.join, From be364a0d5f0ec26a9e83edd2ae2e64def63a95e1 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 17:46:21 +0900 Subject: [PATCH 34/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=AA=A8=EB=8B=AC=EA=B3=BC=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=ED=83=AD=EB=B0=94?= =?UTF-8?q?=EB=A5=BC=20=EC=88=A8=EA=B8=B4=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클래스 경로의 모달 상태에 맞춰 탭바를 전환하고 닫힌 뒤 다시 표시한다. --- .../App/Sources/Navigation/Class/ClassCoordinator.swift | 7 +++++-- .../Sources/Navigation/Class/ClassCoordinatorView.swift | 1 + .../Class/Sources/ClassMember/View/ClassMemberView.swift | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index ba971b58..73c34257 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -21,10 +21,13 @@ public struct ClassCoordinator { routes = [.root(.intro(.init()), embedInNavigationView: true)] } - /// 내 클래스 · 클래스 상세 · 멤버 목록 화면에서만 탭바를 보여준다. + /// 내 클래스 · 클래스 상세 · 리포트에서 탭바를 보여준다. + /// 클래스 상세의 모달이 열려 있거나 멤버 화면이면 숨긴다. public var showsTabBar: Bool { switch routes.last?.screen { - case .myClasses?, .detail?, .members?, .report?: + case let .detail(detail)?: + return detail.modal == nil + case .myClasses?, .report?: return true default: return false diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index ea0bb91e..b27b30eb 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -20,6 +20,7 @@ public struct ClassCoordinatorView: View { switch screen.case { case let .intro(introStore): ClassIntroView(store: introStore) + .toolbar(store.showsTabBar ? .visible : .hidden, for: .tabBar) case let .topic(topicStore): ClassTopicView(store: topicStore) diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift index a5dad328..23fc8b6c 100644 --- a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -38,7 +38,7 @@ public struct ClassMemberView: View { } .screenBackground() .toolbar(.hidden, for: .navigationBar) - .toolbar(.visible, for: .tabBar) + .toolbar(.hidden, for: .tabBar) .customAlert($store.scope(state: \.customAlert, action: \.customAlert)) } } From 6815262f4d0e98715603b8952b845be645e9f3c5 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 20:48:12 +0900 Subject: [PATCH 35/40] =?UTF-8?q?=EC=95=B1=20=EB=B2=84=EC=A0=84=EA=B3=BC?= =?UTF-8?q?=20=EB=B0=B0=ED=8F=AC=20=EB=85=B8=ED=8A=B8=EB=A5=BC=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 앱 버전과 빌드 번호를 1.1.2와 2610012046으로 올리고 한국어 릴리스 노트의 표시 버전을 1.1.1로 갱신한다. --- .../Project+Templete/Extension+String.swift | 4 ++-- fastlane/metadata/ko/release_notes.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift b/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift index b5676182..d924f153 100644 --- a/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift +++ b/Plugins/ProjectTemplatePlugin/ProjectDescriptionHelpers/Project+Templete/Extension+String.swift @@ -9,7 +9,7 @@ import Foundation import ProjectDescription public extension String { - static func appVersion(version: String = "1.1.0") -> String { + static func appVersion(version: String = "1.1.2") -> String { return version } @@ -17,7 +17,7 @@ public extension String { return Project.Environment.bundlePrefix } - static func appBuildVersion(buildVersion: String = "2609141641") -> String { + static func appBuildVersion(buildVersion: String = "2610012046") -> String { return buildVersion } diff --git a/fastlane/metadata/ko/release_notes.txt b/fastlane/metadata/ko/release_notes.txt index cd697db5..87f7f22c 100644 --- a/fastlane/metadata/ko/release_notes.txt +++ b/fastlane/metadata/ko/release_notes.txt @@ -1,3 +1,3 @@ -[V 1.0.9] +[V 1.1.1] - 버그 수정 - 전반적인 UI 정리 및 안정성 개선 From a942718f9e61e1d9763f58ac1da854f1f77f0f6c Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 21:54:18 +0900 Subject: [PATCH 36/40] =?UTF-8?q?=EB=B0=B0=ED=8B=80=20=EB=A6=AC=ED=8F=AC?= =?UTF-8?q?=ED=8A=B8=20=ED=83=AD=20=EC=A0=84=ED=99=98=EA=B3=BC=20=EC=B0=A8?= =?UTF-8?q?=ED=8A=B8=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EC=9D=84=20=EA=B0=9C=EC=84=A0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리포트 진입 시 탭바를 숨기고 페이지 스와이프를 Reducer의 탭 선택 액션에 연결한다. 클래스 결과 막대의 그리기 상태를 Reducer에서 관리하고 동작 테스트를 보강한다. --- .../Navigation/Class/ClassCoordinator.swift | 6 +- .../Reducer/ClassReportFeature.swift | 11 ++- .../ClassReport/View/ClassReportView.swift | 75 +++++++++++++------ .../Tests/Sources/ClassReportTests.swift | 8 ++ 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index 73c34257..d19cb198 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -21,13 +21,13 @@ public struct ClassCoordinator { routes = [.root(.intro(.init()), embedInNavigationView: true)] } - /// 내 클래스 · 클래스 상세 · 리포트에서 탭바를 보여준다. - /// 클래스 상세의 모달이 열려 있거나 멤버 화면이면 숨긴다. + /// 내 클래스와 클래스 상세에서만 탭바를 보여준다. + /// 클래스 상세의 모달이 열려 있거나 멤버·리포트 화면이면 숨긴다. public var showsTabBar: Bool { switch routes.last?.screen { case let .detail(detail)?: return detail.modal == nil - case .myClasses?, .report?: + case .myClasses?: return true default: return false diff --git a/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift b/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift index 199f3db2..6b3376a7 100644 --- a/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift +++ b/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift @@ -5,7 +5,7 @@ import ComposableArchitecture public struct ClassReportFeature { public init() {} - public enum Tab: String, CaseIterable, Equatable { + public enum Tab: String, CaseIterable, Hashable { case summary = "요약" case participation = "내 참여" case classResult = "클래스 결과" @@ -16,6 +16,7 @@ public struct ClassReportFeature { public struct State: Equatable { public var room: ClassRoom public var selectedTab: Tab = .summary + public var isChartDrawn = false public init(room: ClassRoom) { self.room = room @@ -31,6 +32,7 @@ public struct ClassReportFeature { public enum View: Equatable { case backTapped case tabSelected(Tab) + case chartAnimationStarted } @CasePathable @@ -44,7 +46,14 @@ public struct ClassReportFeature { case .view(.backTapped): return .send(.delegate(.dismiss)) case let .view(.tabSelected(tab)): + guard state.selectedTab != tab else { return .none } state.selectedTab = tab + state.isChartDrawn = false + return .none + case .view(.chartAnimationStarted): + if state.selectedTab == .classResult { + state.isChartDrawn = true + } return .none case .delegate: return .none diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift index 4beb77f6..b88036d2 100644 --- a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -5,6 +5,7 @@ import SwiftUI @ViewAction(for: ClassReportFeature.self) public struct ClassReportView: View { public let store: StoreOf + @Environment(\.accessibilityReduceMotion) private var reduceMotion public init(store: StoreOf) { self.store = store @@ -19,21 +20,20 @@ public struct ClassReportView: View { } .foregroundStyle(.gray800) - ScrollView { - VStack(spacing: 16) { - reportTitle() - reportTabs() - reportContent() - .padding(.horizontal, 16) - } + reportTitle() .padding(.top, 16) - .padding(.bottom, 16) - } - .scrollIndicators(.hidden) + reportTabs() + .padding(.top, 16) + reportPages() } .screenBackground() .toolbar(.hidden, for: .navigationBar) - .toolbar(.visible, for: .tabBar) + .toolbar(.hidden, for: .tabBar) + .onChange(of: store.selectedTab) { _, tab in + if tab == .classResult { + send(.chartAnimationStarted) + } + } } } @@ -76,9 +76,30 @@ private extension ClassReportView { } @ViewBuilder - func reportContent() -> some View { + func reportPages() -> some View { + TabView(selection: Binding( + get: { store.selectedTab }, + set: { send(.tabSelected($0)) } + )) { + ForEach(ClassReportFeature.Tab.allCases, id: \.self) { tab in + ScrollView { + reportContent(for: tab) + .padding(.horizontal, 16) + .padding(.top, 16) + .padding(.bottom, 16) + } + .scrollIndicators(.hidden) + .tag(tab) + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + .animation(.easeInOut(duration: 0.25), value: store.selectedTab) + } + + @ViewBuilder + func reportContent(for tab: ClassReportFeature.Tab) -> some View { VStack(spacing: 16) { - switch store.selectedTab { + switch tab { case .summary: summaryContent() case .participation: @@ -218,7 +239,11 @@ private extension ClassReportView { Divider().overlay(.beige500) feedbackRow("다른 해결방법을 제안했어요", description: "반대의견에 그치지 않고 “교육과 보호”라는 대안을 덧붙였어요.", icon: "lightbulb") Divider().overlay(.beige500) - feedbackRow("근거를 한 단계 더 구체적으로", description: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지, 사례나 자료 하나를 연결하면 주장이 더 설득력 있어져요.", icon: "sparkles") + feedbackRow( + "근거를 한 단계 더 구체적으로", + description: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지, 사례나 자료 하나를 연결하면 주장이 더 설득력 있어져요.", + icon: "sparkles" + ) Divider().overlay(.beige500) Text("분석에 사용한 내 대댓글 보기") .pretendardFont(family: .Bold, size: 12) @@ -341,15 +366,21 @@ private extension ClassReportView { VStack(alignment: .leading, spacing: 6) { Text(title).pretendardFont(.regular13).foregroundStyle(.gray500) GeometryReader { proxy in - HStack(spacing: 12) { - Text(primary) - .pretendardFont(.regular13) - .foregroundStyle(.gray800) + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 2) + .fill(color) .frame(width: proxy.size.width * fraction, height: 34) - .background(color, in: RoundedRectangle(cornerRadius: 2)) - Text(secondary) - .pretendardFont(.regular13) - .foregroundStyle(.gray500) + .scaleEffect(x: store.isChartDrawn ? 1 : 0, anchor: .leading) + .animation(reduceMotion ? nil : .easeOut(duration: 0.7), value: store.isChartDrawn) + HStack(spacing: 12) { + Text(primary) + .pretendardFont(.regular13) + .foregroundStyle(.gray800) + .frame(width: proxy.size.width * fraction, height: 34) + Text(secondary) + .pretendardFont(.regular13) + .foregroundStyle(.gray500) + } } } .frame(height: 34) diff --git a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift index 45e1cb35..524b9e7e 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift @@ -14,6 +14,14 @@ struct ClassReportTests { await store.send(.view(.tabSelected(.classResult))) { $0.selectedTab = .classResult } + await store.send(.view(.chartAnimationStarted)) { + $0.isChartDrawn = true + } + await store.send(.view(.tabSelected(.classResult))) + await store.send(.view(.tabSelected(.feedback))) { + $0.selectedTab = .feedback + $0.isChartDrawn = false + } } @Test From 8b4b57832ebc30622c3be0b229abd0297173f0e9 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 22:16:34 +0900 Subject: [PATCH 37/40] =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=EC=9D=84=20=EC=9C=A0=EC=8A=A4=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메시지 노출 시점과 화자 매핑을 순수 계산으로 분리하고 기존 재생 상태 처리는 유지한다. 시나리오 로드 전, 문장 노출 시점, 대사 시작 시각에 대한 회귀 테스트를 추가한다. --- .../ChatRoom/Reducer/ChatRoomFeature.swift | 120 +-------------- .../UseCase/ChatRoomTimelineUseCase.swift | 144 ++++++++++++++++++ .../Chat/Tests/Sources/ChatTests.swift | 69 +++++++++ 3 files changed, 219 insertions(+), 114 deletions(-) create mode 100644 Projects/Feature/Chat/Sources/ChatRoom/UseCase/ChatRoomTimelineUseCase.swift diff --git a/Projects/Feature/Chat/Sources/ChatRoom/Reducer/ChatRoomFeature.swift b/Projects/Feature/Chat/Sources/ChatRoom/Reducer/ChatRoomFeature.swift index ba80ffcd..ecdf1552 100644 --- a/Projects/Feature/Chat/Sources/ChatRoom/Reducer/ChatRoomFeature.swift +++ b/Projects/Feature/Chat/Sources/ChatRoom/Reducer/ChatRoomFeature.swift @@ -73,59 +73,12 @@ public struct ChatRoomFeature { // scenario 가 nil(미로드/로드 실패)이면 목업을 노출하지 않는다. // 과거엔 bundle.messages(목업 전체)를 반환해 "재생 안 됨 + 전체 내용 노출" 버그가 있었다. guard let scenario else { return [] } - let nodes = visibleNodes(in: scenario) - let currentMs = Int(currentTime * 1000) - return nodes.flatMap { node -> [ChatMessage] in - let scripts = node.scripts - // 대사별 startTimeMs 가 구분되면 그 값을, 모두 같거나(0) 평평하면 노드 오디오 구간에 균등 분배. - let distinctStarts = Set(scripts.map(\.startTimeMs)).count - let nodeStartMs = scripts.map(\.startTimeMs).min() ?? 0 - let nodeDurationMs = node.audioDuration * 1000 - let count = scripts.count - - func revealStart(_ index: Int) -> Int { - if distinctStarts > 1 { return scripts[index].startTimeMs } - if count > 1 { return nodeStartMs + nodeDurationMs * index / count } - return nodeStartMs - } - - return scripts.enumerated().flatMap { index, script -> [ChatMessage] in - let scriptStart = revealStart(index) - guard currentMs >= scriptStart else { return [] } - let scriptEnd = index + 1 < count ? revealStart(index + 1) : nodeStartMs + nodeDurationMs - let windowMs = max(1, scriptEnd - scriptStart) - let messageSpeaker = speaker(for: script, in: scenario) - - // 나레이션/클로징(center)·발언자(좌/우) 모두 문장마다 개별 말풍선. - // 문장 시작 시점은 글자수 비례로 분배(긴 문장=더 긴 시간) → 싱크. - let sentences = script.text.splitIntoSentences() - .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { !$0.isEmpty } - guard sentences.count > 1 else { - return [ChatMessage( - messageId: UUID.deterministic(script.scriptId, 0), - speaker: messageSpeaker, - text: script.text, - startTimeMs: scriptStart - )] - } - let totalChars = max(1, sentences.reduce(0) { $0 + $1.count }) - var charsBefore = 0 - var result: [ChatMessage] = [] - for (sentenceIndex, sentence) in sentences.enumerated() { - let sentenceStart = scriptStart + windowMs * charsBefore / totalChars - charsBefore += sentence.count - guard currentMs >= sentenceStart else { break } - result.append(ChatMessage( - messageId: UUID.deterministic(script.scriptId, sentenceIndex), - speaker: messageSpeaker, - text: sentence, - startTimeMs: sentenceStart - )) - } - return result - } - } + return ChatRoomTimelineUseCase().messages( + scenario: scenario, + visibleNodeIds: visibleNodeIds, + currentNodeId: currentNodeId, + currentTime: currentTime + ) } /// 현재 재생 중(가장 최근 노출된) 메시지 id. 문장 단위 노출이라 마지막 노출 버블이 활성. @@ -272,67 +225,6 @@ public struct ChatRoomFeature { } } -private extension ChatRoomFeature.State { - func visibleNodes(in scenario: BattleScenario) -> [ScenarioNode] { - let ids = visibleNodeIds.isEmpty ? [currentNodeId ?? scenario.startNodeId] : visibleNodeIds - return ids.compactMap { id in - scenario.nodes.first { $0.nodeId == id } - } - } - - func speaker( - for script: ScenarioScript, - in scenario: BattleScenario - ) -> ChatSpeaker { - switch script.speakerType { - case .a: - return speaker( - label: "A", - side: .left, - fallbackName: script.speakerName, - in: scenario - ) - case .b: - return speaker( - label: "B", - side: .right, - fallbackName: script.speakerName, - in: scenario - ) - case .narrator: - return ChatSpeaker(name: script.speakerName, side: .center) - case .philosopher, .unknown: - if let philosopher = scenario.philosophers.first(where: { $0.name == script.speakerName }) { - let side: ChatSpeakerSide = philosopher.label == "B" ? .right : .left - return ChatSpeaker( - label: philosopher.label, - name: philosopher.name, - imageURL: philosopher.imageUrl, - side: side - ) - } - return ChatSpeaker(name: script.speakerName, side: .center) - } - } - - func speaker( - label: String, - side: ChatSpeakerSide, - fallbackName: String, - in scenario: BattleScenario - ) -> ChatSpeaker { - guard let philosopher = scenario.philosophers.first(where: { $0.label == label }) else { - return ChatSpeaker(label: label, name: fallbackName, side: side) - } - return ChatSpeaker( - label: philosopher.label, - name: philosopher.name, - imageURL: philosopher.imageUrl, - side: side - ) - } -} - extension ChatRoomFeature { private func handleViewAction( state: inout State, diff --git a/Projects/Feature/Chat/Sources/ChatRoom/UseCase/ChatRoomTimelineUseCase.swift b/Projects/Feature/Chat/Sources/ChatRoom/UseCase/ChatRoomTimelineUseCase.swift new file mode 100644 index 00000000..a286bcbf --- /dev/null +++ b/Projects/Feature/Chat/Sources/ChatRoom/UseCase/ChatRoomTimelineUseCase.swift @@ -0,0 +1,144 @@ +import Foundation +import HomeDomainInterface +import PickeCoreUtility + +/// 시나리오와 재생 시각에서 화면에 표시할 메시지를 계산한다. +struct ChatRoomTimelineUseCase { + func messages( + scenario: BattleScenario, + visibleNodeIds: [Int], + currentNodeId: Int?, + currentTime: TimeInterval + ) -> [ChatMessage] { + let nodes = visibleNodes( + in: scenario, + visibleNodeIds: visibleNodeIds, + currentNodeId: currentNodeId + ) + let currentMs = Int(currentTime * 1000) + return nodes.flatMap { messages(for: $0, in: scenario, currentMs: currentMs) } + } + + private func messages( + for node: ScenarioNode, + in scenario: BattleScenario, + currentMs: Int + ) -> [ChatMessage] { + let scripts = node.scripts + // 대사별 startTimeMs 가 구분되면 그 값을, 모두 같거나(0) 평평하면 노드 오디오 구간에 균등 분배. + let distinctStarts = Set(scripts.map(\.startTimeMs)).count + let nodeStartMs = scripts.map(\.startTimeMs).min() ?? 0 + let nodeDurationMs = node.audioDuration * 1000 + let count = scripts.count + + func revealStart(_ index: Int) -> Int { + if distinctStarts > 1 { + return scripts[index].startTimeMs + } + if count > 1 { + return nodeStartMs + nodeDurationMs * index / count + } + return nodeStartMs + } + + return scripts.enumerated().flatMap { index, script -> [ChatMessage] in + let scriptStart = revealStart(index) + guard currentMs >= scriptStart else { return [] } + let scriptEnd = index + 1 < count ? revealStart(index + 1) : nodeStartMs + nodeDurationMs + let windowMs = max(1, scriptEnd - scriptStart) + let messageSpeaker = speaker(for: script, in: scenario) + + // 나레이션/클로징(center)·발언자(좌/우) 모두 문장마다 개별 말풍선. + // 문장 시작 시점은 글자수 비례로 분배(긴 문장=더 긴 시간) → 싱크. + let sentences = script.text.splitIntoSentences() + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + guard sentences.count > 1 else { + return [ChatMessage( + messageId: UUID.deterministic(script.scriptId, 0), + speaker: messageSpeaker, + text: script.text, + startTimeMs: scriptStart + )] + } + let totalChars = max(1, sentences.reduce(0) { $0 + $1.count }) + var charsBefore = 0 + var result: [ChatMessage] = [] + for (sentenceIndex, sentence) in sentences.enumerated() { + let sentenceStart = scriptStart + windowMs * charsBefore / totalChars + charsBefore += sentence.count + guard currentMs >= sentenceStart else { break } + result.append(ChatMessage( + messageId: UUID.deterministic(script.scriptId, sentenceIndex), + speaker: messageSpeaker, + text: sentence, + startTimeMs: sentenceStart + )) + } + return result + } + } + + private func visibleNodes( + in scenario: BattleScenario, + visibleNodeIds: [Int], + currentNodeId: Int? + ) -> [ScenarioNode] { + let ids = visibleNodeIds.isEmpty ? [currentNodeId ?? scenario.startNodeId] : visibleNodeIds + return ids.compactMap { id in + scenario.nodes.first { $0.nodeId == id } + } + } + + private func speaker( + for script: ScenarioScript, + in scenario: BattleScenario + ) -> ChatSpeaker { + switch script.speakerType { + case .a: + return speaker( + label: "A", + side: .left, + fallbackName: script.speakerName, + in: scenario + ) + case .b: + return speaker( + label: "B", + side: .right, + fallbackName: script.speakerName, + in: scenario + ) + case .narrator: + return ChatSpeaker(name: script.speakerName, side: .center) + case .philosopher, .unknown: + if let philosopher = scenario.philosophers.first(where: { $0.name == script.speakerName }) { + let side: ChatSpeakerSide = philosopher.label == "B" ? .right : .left + return ChatSpeaker( + label: philosopher.label, + name: philosopher.name, + imageURL: philosopher.imageUrl, + side: side + ) + } + return ChatSpeaker(name: script.speakerName, side: .center) + } + } + + private func speaker( + label: String, + side: ChatSpeakerSide, + fallbackName: String, + in scenario: BattleScenario + ) -> ChatSpeaker { + guard let philosopher = scenario.philosophers.first(where: { $0.label == label }) else { + return ChatSpeaker(label: label, name: fallbackName, side: side) + } + return ChatSpeaker( + label: philosopher.label, + name: philosopher.name, + imageURL: philosopher.imageUrl, + side: side + ) + } +} diff --git a/Projects/Feature/Chat/Tests/Sources/ChatTests.swift b/Projects/Feature/Chat/Tests/Sources/ChatTests.swift index a505aae6..fde5d4ec 100644 --- a/Projects/Feature/Chat/Tests/Sources/ChatTests.swift +++ b/Projects/Feature/Chat/Tests/Sources/ChatTests.swift @@ -6,6 +6,7 @@ // @testable import Chat +import HomeDomainInterface import Testing struct ChatTests { @@ -20,4 +21,72 @@ struct ChatTests { let result = true #expect(result == true) } + + @Test + func chatRoomMessagesRemainHiddenUntilTheirTimelinePosition() { + var state = ChatRoomFeature.State(battleId: 901) + state.scenario = makeScenario() + state.currentNodeId = 1 + state.visibleNodeIds = [1] + + state.currentTime = 1 + #expect(state.messages.map(\.text) == ["첫 번째 문장."]) + + state.currentTime = 6 + #expect(state.messages.map(\.text) == ["첫 번째 문장.", "두 번째 문장."]) + } + + @Test + func chatRoomMessagesDoNotFallbackToMockContentBeforeScenarioLoads() { + let state = ChatRoomFeature.State(battleId: 902) + + #expect(state.messages.isEmpty) + #expect(state.audioUrl == nil) + } + + @Test + func chatRoomMessagesFollowDistinctScriptStartTimes() { + var state = ChatRoomFeature.State(battleId: 903) + state.scenario = makeScenario(scripts: [ + ScenarioScript(scriptId: 11, startTimeMs: 0, speakerType: .narrator, speakerName: "해설", text: "시작"), + ScenarioScript(scriptId: 12, startTimeMs: 7000, speakerType: .narrator, speakerName: "해설", text: "나중"), + ]) + state.visibleNodeIds = [1] + + state.currentTime = 6 + #expect(state.messages.map(\.text) == ["시작"]) + state.currentTime = 7 + #expect(state.messages.map(\.text) == ["시작", "나중"]) + #expect(state.messages.last?.speaker.side == .center) + } + + private func makeScenario(scripts: [ScenarioScript]? = nil) -> BattleScenario { + BattleScenario( + battleId: 901, + title: "테스트 배틀", + philosophers: [], + isInteractive: false, + startNodeId: 1, + recommendedPathKey: .common, + audios: ["COMMON": "https://example.com/audio.mp3"], + nodes: [ + ScenarioNode( + nodeId: 1, + nodeName: "START", + audioDuration: 10, + autoNextNodeId: nil, + scripts: scripts ?? [ + ScenarioScript( + scriptId: 1, + startTimeMs: 0, + speakerType: .narrator, + speakerName: "나레이터", + text: "첫 번째 문장. 두 번째 문장." + ), + ], + interactiveOptions: [] + ), + ] + ) + } } From 9e72ae1a9c491da35fb5cb69916b8f89ab3bceda Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 22:40:00 +0900 Subject: [PATCH 38/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9A=B4?= =?UTF-8?q?=EC=98=81=EC=9E=90=20=ED=99=94=EB=A9=B4=EA=B3=BC=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 운영자 대시보드와 상세 화면을 연결하고 Figma 기반 클래스 UI를 보완한다. 서버 계약이 없는 예시 데이터는 표시하고 피드백 전송은 안내로 제한한다. 클래스 회귀 테스트 38개와 Picke-Stage 빌드를 확인한다. --- .../Navigation/Class/ClassCoordinator.swift | 47 +++- .../Class/ClassCoordinatorView.swift | 12 + .../View/Components/TermsAgreementView.swift | 6 +- .../Reducer/ClassDetailFeature.swift | 14 + .../ClassDetail/View/ClassDetailView.swift | 41 ++- .../ClassIntro/View/ClassIntroView.swift | 3 +- .../ClassJoin/View/ClassJoinView.swift | 3 +- .../Reducer/ClassMemberFeature.swift | 91 ++++++ .../ClassMember/View/ClassMemberView.swift | 120 +++++++- .../Reducer/ClassOwnerDashboardFeature.swift | 185 +++++++++++++ .../View/ClassOwnerDashboardView.swift | 262 ++++++++++++++++++ .../Reducer/ClassFeedbackComposeFeature.swift | 59 ++++ .../Reducer/ClassMemberDetailFeature.swift | 74 +++++ .../Reducer/ClassReplyDetailFeature.swift | 66 +++++ .../View/ClassFeedbackComposeView.swift | 100 +++++++ .../View/ClassMemberDetailView.swift | 188 +++++++++++++ .../View/ClassReplyDetailView.swift | 70 +++++ .../ClassReport/View/ClassReportView.swift | 11 +- .../Tests/Sources/ClassDetailTests.swift | 19 ++ .../Tests/Sources/ClassMemberTests.swift | 25 +- .../Sources/ClassOwnerDashboardTests.swift | 52 ++++ .../Attendance/View/AttendanceModalView.swift | 5 +- .../Sources/Modal/PickeModal.swift | 31 ++- 23 files changed, 1457 insertions(+), 27 deletions(-) create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassFeedbackComposeFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift create mode 100644 Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index d19cb198..7e0b4237 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -29,6 +29,8 @@ public struct ClassCoordinator { return detail.modal == nil case .myClasses?: return true + case .ownerDashboard?: + return true default: return false } @@ -136,12 +138,51 @@ private extension ClassCoordinator { return .none case let .routeAction(_, action: .detail(.delegate(.openReport(room)))): - state.routes.push(.report(.init(room: room))) + if room.role == .owner { + state.routes.push(.ownerDashboard(.init(room: room))) + } else { + state.routes.push(.report(.init(room: room))) + } return .none case .routeAction(_, action: .report(.delegate(.dismiss))): return .send(.view(.backAction)) + case .routeAction(_, action: .ownerDashboard(.delegate(.dismiss))), + .routeAction(_, action: .ownerMemberDetail(.delegate(.dismiss))), + .routeAction(_, action: .ownerReplyDetail(.delegate(.dismiss))), + .routeAction(_, action: .ownerFeedback(.delegate(.dismiss))): + return .send(.view(.backAction)) + + case let .routeAction(_, action: .ownerDashboard(.delegate(.openMemberDetail(member)))): + guard case let .ownerDashboard(dashboard)? = state.routes.last?.screen else { return .none } + state.routes.push(.ownerMemberDetail(.init(room: dashboard.room, memberName: member.name))) + return .none + + case .routeAction(_, action: .ownerDashboard(.delegate(.openReplyDetail))): + guard case let .ownerDashboard(dashboard)? = state.routes.last?.screen else { return .none } + state.routes.push(.ownerReplyDetail(.init(room: dashboard.room))) + return .none + + case let .routeAction(_, action: .ownerDashboard(.delegate(.openFeedback(member)))): + guard case let .ownerDashboard(dashboard)? = state.routes.last?.screen else { return .none } + state.routes.push(.ownerFeedback(.init(room: dashboard.room, memberName: member.name))) + return .none + + case .routeAction(_, action: .ownerMemberDetail(.delegate(.replySelected))), + .routeAction(_, action: .ownerMemberDetail(.delegate(.commentSelected))): + guard case let .ownerMemberDetail(member)? = state.routes.last?.screen else { return .none } + state.routes.push(.ownerReplyDetail(.init(room: member.room))) + return .none + + case .routeAction(_, action: .ownerMemberDetail(.delegate(.feedbackSelected))): + guard case let .ownerMemberDetail(member)? = state.routes.last?.screen else { return .none } + state.routes.push(.ownerFeedback(.init(room: member.room, memberName: member.memberName))) + return .none + + case .routeAction(_, action: .ownerFeedback(.delegate(.submitted))): + return .send(.view(.backAction)) + case .routeAction(_, action: .chat(.delegate(.dismiss))): return .send(.view(.backAction)) @@ -186,6 +227,10 @@ extension ClassCoordinator { case detail(ClassDetailFeature) case members(ClassMemberFeature) case report(ClassReportFeature) + case ownerDashboard(ClassOwnerDashboardFeature) + case ownerMemberDetail(ClassMemberDetailFeature) + case ownerReplyDetail(ClassReplyDetailFeature) + case ownerFeedback(ClassFeedbackComposeFeature) case chat(ChatCoordinator) } } diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift index b27b30eb..78f6c185 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinatorView.swift @@ -49,6 +49,18 @@ public struct ClassCoordinatorView: View { case let .report(reportStore): ClassReportView(store: reportStore) + case let .ownerDashboard(dashboardStore): + ClassOwnerDashboardView(store: dashboardStore) + + case let .ownerMemberDetail(memberStore): + ClassMemberDetailView(store: memberStore) + + case let .ownerReplyDetail(replyStore): + ClassReplyDetailView(store: replyStore) + + case let .ownerFeedback(feedbackStore): + ClassFeedbackComposeView(store: feedbackStore) + case let .chat(chatStore): ChatCoordinatorView(store: chatStore) } diff --git a/Projects/Feature/Auth/Sources/Main/View/Components/TermsAgreementView.swift b/Projects/Feature/Auth/Sources/Main/View/Components/TermsAgreementView.swift index d57dc51b..ea799af7 100644 --- a/Projects/Feature/Auth/Sources/Main/View/Components/TermsAgreementView.swift +++ b/Projects/Feature/Auth/Sources/Main/View/Components/TermsAgreementView.swift @@ -3,19 +3,19 @@ // Auth // -import SwiftUI - import AuthDomainInterface import ComposableArchitecture import PickeDesignKit +import SwiftUI struct TermsAgreementView: View { @Bindable var store: StoreOf var body: some View { ZStack(alignment: .bottom) { - Color.black.opacity(0.4) + Color.clear .ignoresSafeArea() + .contentShape(Rectangle()) .onTapGesture { store.send(.view(.dismissTapped)) } sheet() diff --git a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift index b1102d6e..dbbc7840 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/Reducer/ClassDetailFeature.swift @@ -14,6 +14,8 @@ public struct ClassDetailFeature { public var room: ClassRoom public var deadline: Date public var draftDeadline: Date + /// The nickname draft is kept locally until the class API exposes an update contract. + public var draftNickname = "" @Presents public var modal: ClassModalFeature.State? @Presents public var customAlert: CustomAlertState? public var isLoading = false @@ -47,6 +49,8 @@ public struct ClassDetailFeature { case codeDismissed case deadlineTapped case deadlineSaved + case nicknameTapped + case nicknameSaved case deleteTapped case deleteConfirmed } @@ -140,6 +144,16 @@ extension ClassDetailFeature { case .deadlineSaved: guard !state.isLoading else { return .none } return .send(.async(.updateDeadline(state.room.id, state.draftDeadline))) + case .nicknameTapped: + state.modal = .init(kind: .nickname) + return .none + case .nicknameSaved: + guard !state.draftNickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return .none } + // There is currently no server contract for updating a class nickname. + // Keep the entered value in the feature and close the modal until one exists. + state.draftNickname = state.draftNickname.trimmingCharacters(in: .whitespacesAndNewlines) + state.modal = nil + return .none case .deleteTapped: guard state.room.role == .owner else { return .none } state.modal = nil diff --git a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift index 11f0048d..0cc56c1f 100644 --- a/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift +++ b/Projects/Feature/Class/Sources/ClassDetail/View/ClassDetailView.swift @@ -42,7 +42,10 @@ public struct ClassDetailView: View { .screenBackground() .toolbar(.hidden, for: .navigationBar) .toolbar(store.modal == nil ? .visible : .hidden, for: .tabBar) - .pickeModal($store.scope(state: \.modal, action: \.modal)) { modalStore in + .pickeModal( + $store.scope(state: \.modal, action: \.modal), + dimOpacity: 0.28 + ) { modalStore in switch modalStore.kind { case .management: modalBackdrop({ send(.managementDismissed) }) { managementSheet() } @@ -51,7 +54,7 @@ public struct ClassDetailView: View { case .code: modalBackdrop({ send(.codeDismissed) }) { codeSheet() } case .nickname: - EmptyView() + modalBackdrop({ modalStore.send(.dismissTapped) }) { nicknameSheet() } } } .customAlert($store.scope(state: \.customAlert, action: \.customAlert)) @@ -87,6 +90,11 @@ private extension ClassDetailView { Text(store.room.name) .pretendardFont(.semiBold24) .foregroundStyle(.gray800) + .contextMenu { + if store.room.role == .member { + Button("내 이름 수정") { send(.nicknameTapped) } + } + } } } @@ -231,8 +239,9 @@ private extension ClassDetailView { @ViewBuilder content: () -> some View ) -> some View { ZStack(alignment: .bottom) { - Color.black.opacity(0.28) + Color.clear .ignoresSafeArea() + .contentShape(Rectangle()) .onTapGesture(perform: onDismiss) content() .frame(maxWidth: .infinity) @@ -349,4 +358,30 @@ private extension ClassDetailView { .padding(.horizontal, 16) .padding(.bottom, 40) } + + @ViewBuilder + func nicknameSheet() -> some View { + VStack(alignment: .leading, spacing: 20) { + sheetHandle + VStack(alignment: .leading, spacing: 6) { + Text("이름을 입력해주세요") + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + Text("이 클래스에서만 보이는 이름이에요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + + TextField("이름을 입력해주세요", text: $store.draftNickname) + .textInputAutocapitalization(.never) + .pickeTextField(height: 52) + + Button("수정하기") { send(.nicknameSaved) } + .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(store.draftNickname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + .padding(.top, 12) + .padding(.horizontal, 16) + .padding(.bottom, 32) + } } diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift index deb9fcc6..1327426b 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -42,7 +42,8 @@ public struct ClassIntroView: View { $store.scope( state: \.join, action: \.join - ) + ), + dimOpacity: 0.28 ) { joinStore in ClassJoinView(store: joinStore) } diff --git a/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift b/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift index 30dca4b0..f7f37083 100644 --- a/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift +++ b/Projects/Feature/Class/Sources/ClassJoin/View/ClassJoinView.swift @@ -19,8 +19,9 @@ public struct ClassJoinView: View { public var body: some View { ZStack(alignment: .bottom) { - Color.black.opacity(0.28) + Color.clear .ignoresSafeArea() + .contentShape(Rectangle()) .onTapGesture { send(.backTapped) } switch store.mode { case .code: diff --git a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift index 12c8d38c..8d0443f1 100644 --- a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift +++ b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift @@ -25,6 +25,8 @@ public struct ClassMemberFeature { public var members: [Member] public var searchText = "" public var selectedMember: Member? + public var appliedFilter = ClassMemberFilterFeature.State() + @Presents public var filter: ClassMemberFilterFeature.State? @Presents public var customAlert: CustomAlertState? public init(room: ClassRoom) { @@ -44,12 +46,14 @@ public struct ClassMemberFeature { public enum Action: ViewAction, BindableAction { case binding(BindingAction) case view(View) + case filter(PresentationAction) case customAlert(PresentationAction) case delegate(DelegateAction) } public enum View { case backTapped + case filterTapped case removeTapped(Int) case removeConfirmed case removeCancelled @@ -67,6 +71,17 @@ public struct ClassMemberFeature { return .none case let .view(viewAction): return handleViewAction(state: &state, action: viewAction) + case .filter(.presented(.applyTapped)): + if let filter = state.filter { + state.appliedFilter = filter + } + state.filter = nil + return .none + case .filter(.presented(.dismissTapped)), .filter(.dismiss): + state.filter = nil + return .none + case .filter: + return .none case let .customAlert(alertAction): switch alertAction { case .presented(.confirmTapped): @@ -84,6 +99,9 @@ public struct ClassMemberFeature { return .none } } + .ifLet(\.$filter, action: \.filter) { + ClassMemberFilterFeature() + } .ifLet(\.$customAlert, action: \.customAlert) { CustomConfirmAlert() } @@ -95,6 +113,9 @@ extension ClassMemberFeature { switch action { case .backTapped: return .send(.delegate(.dismiss)) + case .filterTapped: + state.filter = state.appliedFilter + return .none case let .removeTapped(id): guard state.room.role == .owner, let member = state.members.first(where: { $0.id == id && !$0.isOwner }) @@ -121,3 +142,73 @@ extension ClassMemberFeature { } } } + +@Reducer +public struct ClassMemberFilterFeature { + @ObservableState + public struct State: Equatable { + public var participation: Participation = .all + public var completion: Completion = .all + public var attendance: Attendance = .all + public var sort: Sort = .name + + public init() {} + } + + public enum Action: Equatable { + case participationSelected(Participation) + case completionSelected(Completion) + case attendanceSelected(Attendance) + case sortSelected(Sort) + case applyTapped + case dismissTapped + } + + public enum Participation: String, CaseIterable, Equatable { + case all = "전체" + case notParticipated = "미참여 4" + case completed = "참여 완료 18" + case inProgress = "진행 중 10" + } + + public enum Completion: String, CaseIterable, Equatable { + case all = "전체" + case noComment = "댓글 미작성" + case noAfterVote = "사후 투표 미완료" + case inProgress = "진행 중 10" + } + + public enum Attendance: String, CaseIterable, Equatable { + case all = "전체" + case changed = "입장 변화" + case unchanged = "입장 유지" + case inProgress = "진행 중 10" + } + + public enum Sort: String, CaseIterable, Equatable { + case name = "이름순" + case comments = "댓글 많은순" + case replies = "대댓글 많은순" + case recommendations = "추천순" + } + + public init() {} + + public var body: some Reducer { + Reduce { state, action in + switch action { + case let .participationSelected(value): + state.participation = value + case let .completionSelected(value): + state.completion = value + case let .attendanceSelected(value): + state.attendance = value + case let .sortSelected(value): + state.sort = value + case .applyTapped, .dismissTapped: + break + } + return .none + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift index 23fc8b6c..5e9f3ae8 100644 --- a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -39,6 +39,12 @@ public struct ClassMemberView: View { .screenBackground() .toolbar(.hidden, for: .navigationBar) .toolbar(.hidden, for: .tabBar) + .pickeModal( + $store.scope(state: \.filter, action: \.filter), + dimOpacity: 0.28 + ) { filterStore in + filterSheet(store: filterStore) + } .customAlert($store.scope(state: \.customAlert, action: \.customAlert)) } } @@ -49,9 +55,17 @@ private extension ClassMemberView { Image(systemName: "magnifyingglass") .font(.system(size: 18)) .foregroundStyle(.gray300) - TextField("이름을 입력해 주세요.", text: $store.searchText) - .pretendardFont(.medium15) + TextField("이름을 입력해주세요.", text: $store.searchText) + .pretendardFont(family: .Medium, size: 13) .foregroundStyle(.gray800) + + Button { send(.filterTapped) } label: { + Image(systemName: "slider.horizontal.3") + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(.gray500) + .frame(width: 32, height: 32) + } + .accessibilityLabel("멤버 필터") } .padding(.horizontal, 12) .frame(height: 44) @@ -90,4 +104,106 @@ private extension ClassMemberView { .frame(height: 68) .overlay(alignment: .bottom) { Rectangle().fill(.beige600).frame(height: 1) } } + + @ViewBuilder + func filterSheet(store: StoreOf) -> some View { + ZStack(alignment: .bottom) { + Color.clear + .ignoresSafeArea() + .contentShape(Rectangle()) + .onTapGesture { store.send(.dismissTapped) } + + VStack(alignment: .leading, spacing: 20) { + sheetHandle + filterSection( + title: "참여 상태", + detail: "총 " + String(self.store.members.count) + "명", + options: ClassMemberFilterFeature.Participation.allCases.filter { $0 != .all }, + selected: { store.participation == $0 }, + action: { store.send(.participationSelected($0)) } + ) + filterSection( + title: "미완료", + detail: "총 " + String(self.store.members.count) + "명", + options: ClassMemberFilterFeature.Completion.allCases.filter { $0 != .all }, + selected: { store.completion == $0 }, + action: { store.send(.completionSelected($0)) } + ) + filterSection( + title: "입장 변화", + detail: "총 " + String(self.store.members.count) + "명", + options: ClassMemberFilterFeature.Attendance.allCases.filter { $0 != .all }, + selected: { store.attendance == $0 }, + action: { store.send(.attendanceSelected($0)) } + ) + filterSection( + title: "정렬", + detail: "총 " + String(self.store.members.count) + "명", + options: ClassMemberFilterFeature.Sort.allCases, + selected: { store.sort == $0 }, + action: { store.send(.sortSelected($0)) } + ) + + Button { store.send(.applyTapped) } label: { + Text("적용하기") + } + .ctaButtonStyle(.primary, size: .large, height: 52) + } + .padding(.top, 12) + .padding(.horizontal, 16) + .padding(.bottom, 32) + .frame(maxWidth: .infinity) + .background( + UnevenRoundedRectangle(topLeadingRadius: 26, topTrailingRadius: 26) + .fill(.beige50) + .ignoresSafeArea(edges: .bottom) + ) + } + } + + @ViewBuilder + func filterSection( + title: String, + detail: String, + options: [Option], + selected: @escaping (Option) -> Bool, + action: @escaping (Option) -> Void + ) -> some View where Option.RawValue == String { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 10) { + Text(title) + .pretendardFont(family: .SemiBold, size: 13) + .foregroundStyle(.gray800) + Spacer(minLength: 0) + Text(detail) + .pretendardFont(family: .Medium, size: 10) + .foregroundStyle(.gray300) + } + + HStack(spacing: 8) { + ForEach(options, id: \.self) { option in + Button { action(option) } label: { + Text(option.rawValue) + .pretendardFont(family: .Medium, size: 13) + .foregroundStyle(selected(option) ? .beige50 : .gray300) + .lineLimit(1) + .fixedSize(horizontal: true, vertical: false) + .padding(.horizontal, 12) + .frame(height: 30) + .background(selected(option) ? .primary500 : .beige200, in: RoundedRectangle(cornerRadius: 2)) + } + .buttonStyle(.plain) + } + Spacer(minLength: 0) + } + } + } + + var sheetHandle: some View { + Capsule() + .fill(.gray50) + .frame(width: 40, height: 4) + .frame(maxWidth: .infinity) + .padding(.bottom, 10) + } } diff --git a/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift new file mode 100644 index 00000000..553b733a --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift @@ -0,0 +1,185 @@ +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassOwnerDashboardFeature { + public init() {} + + public enum Tab: String, CaseIterable, Hashable { + case home = "홈" + case members = "멤버" + case opinions = "의견" + } + + public enum OpinionFilter: String, CaseIterable, Hashable { + case all = "전체" + case recommended = "추천순" + case replies = "대댓글 많은순" + case inProgress = "진행 중 10" + } + + public enum MemberSort: String, CaseIterable, Hashable { + case name = "이름순" + case participation = "참여 많은순" + case needsReview = "확인 필요순" + } + + public struct Member: Equatable, Identifiable, Sendable { + public let id: Int + public let name: String + public let participationCount: Int + public let needsReview: Bool + + public init(id: Int, name: String, participationCount: Int, needsReview: Bool = false) { + self.id = id + self.name = name + self.participationCount = participationCount + self.needsReview = needsReview + } + } + + public struct Opinion: Equatable, Identifiable, Sendable { + public let id: Int + public let author: String + public let text: String + public let replyCount: Int + public let isReported: Bool + + public init( + id: Int, + author: String, + text: String, + replyCount: Int, + isReported: Bool = false + ) { + self.id = id + self.author = author + self.text = text + self.replyCount = replyCount + self.isReported = isReported + } + } + + @ObservableState + public struct State: Equatable { + public let room: ClassRoom + public var selectedTab: Tab = .home + public var opinionFilter: OpinionFilter = .all + public var memberSort: MemberSort = .name + public var searchText = "" + public var members: [Member] + public var opinions: [Opinion] + + public init(room: ClassRoom) { + self.room = room + members = [ + Member(id: 1, name: "김민지", participationCount: 8, needsReview: true), + Member(id: 2, name: "이서준", participationCount: 7), + Member(id: 3, name: "박지우", participationCount: 6), + Member(id: 4, name: "최유진", participationCount: 5), + Member(id: 5, name: "정다은", participationCount: 4), + Member(id: 6, name: "한도윤", participationCount: 3, needsReview: true), + ] + opinions = [ + Opinion( + id: 1, + author: "김민지", + text: "피해자 보호를 위한 기준도 함께 필요하지 않을까요?", + replyCount: 23 + ), + Opinion( + id: 2, + author: "이서준", + text: "제도화가 선택을 의무로 바꾸지 않도록 기준이 필요해요.", + replyCount: 12 + ), + Opinion( + id: 3, + author: "박지우", + text: "교육과 보호도 함께 고려해야 한다고 생각합니다.", + replyCount: 4 + ), + ] + } + + public var visibleMembers: [Member] { + let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) + let matching = query.isEmpty ? members : members.filter { $0.name.localizedCaseInsensitiveContains(query) } + switch memberSort { + case .name: + return matching.sorted { $0.name < $1.name } + case .participation: + return matching.sorted { $0.participationCount > $1.participationCount } + case .needsReview: + return matching.sorted { $0.needsReview && !$1.needsReview } + } + } + + public var visibleOpinions: [Opinion] { + switch opinionFilter { + case .all, .inProgress: + return opinions + case .recommended: + return opinions.sorted { $0.replyCount > $1.replyCount } + case .replies: + return opinions.sorted { $0.replyCount > $1.replyCount } + } + } + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + public enum View: Equatable { + case backTapped + case tabSelected(Tab) + case opinionFilterSelected(OpinionFilter) + case memberSortSelected(MemberSort) + case searchTextChanged(String) + case memberTapped(Int) + case opinionTapped(Int) + case feedbackTapped(Int) + } + + public enum DelegateAction: Equatable { + case dismiss + case openMemberDetail(Member) + case openReplyDetail(Opinion) + case openFeedback(Member) + } + + public var body: some Reducer { + Reduce { state, action in + switch action { + case let .view(viewAction): + switch viewAction { + case .backTapped: + return .send(.delegate(.dismiss)) + case let .tabSelected(tab): + state.selectedTab = tab + case let .opinionFilterSelected(filter): + state.opinionFilter = filter + case let .memberSortSelected(sort): + state.memberSort = sort + case let .searchTextChanged(text): + state.searchText = text + case let .memberTapped(id): + guard let member = state.members.first(where: { $0.id == id }) else { return .none } + return .send(.delegate(.openMemberDetail(member))) + case let .opinionTapped(id): + guard let opinion = state.opinions.first(where: { $0.id == id }) else { return .none } + return .send(.delegate(.openReplyDetail(opinion))) + case let .feedbackTapped(id): + guard let member = state.members.first(where: { $0.id == id }) else { return .none } + return .send(.delegate(.openFeedback(member))) + } + return .none + case .delegate: + return .none + } + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift b/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift new file mode 100644 index 00000000..d6a1e8f3 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift @@ -0,0 +1,262 @@ +import ClassDomainInterface +import ComposableArchitecture +import PickeDesignKit +import SwiftUI + +@ViewAction(for: ClassOwnerDashboardFeature.self) +public struct ClassOwnerDashboardView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) + .foregroundStyle(.gray800) + + titleHeader() + summaryHeader() + dashboardTabs() + TabView(selection: Binding( + get: { store.selectedTab }, + set: { send(.tabSelected($0)) } + )) { + ScrollView { homePage().padding(16) }.tag(ClassOwnerDashboardFeature.Tab.home) + ScrollView { membersPage().padding(16) }.tag(ClassOwnerDashboardFeature.Tab.members) + ScrollView { opinionsPage().padding(16) }.tag(ClassOwnerDashboardFeature.Tab.opinions) + } + .tabViewStyle(.page(indexDisplayMode: .never)) + } + .screenBackground() + .toolbar(.hidden, for: .navigationBar) + .toolbar(.visible, for: .tabBar) + } +} + +private extension ClassOwnerDashboardView { + @ViewBuilder + func titleHeader() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text("예시 데이터") + .pretendardFont(.medium13) + .foregroundStyle(.gray300) + Text(store.room.battle.title) + .pretendardFont(.semiBold24) + .foregroundStyle(.gray800) + .lineLimit(2) + .frame(maxWidth: .infinity, alignment: .leading) + Text("참여 멤버 \(store.room.memberCount)명") + .pretendardFont(.medium13) + .foregroundStyle(.gray500) + } + .padding(.horizontal, 16) + .frame(height: 166, alignment: .center) + } + + @ViewBuilder + func summaryHeader() -> some View { + VStack(spacing: 10) { + HStack { + Text("플라톤") + Text("59.5%") + Spacer() + Text("찬성") + Text("반대 40.5%") + } + .pretendardFont(family: .Medium, size: 12) + .foregroundStyle(.gray500) + GeometryReader { proxy in + HStack(spacing: 0) { + RoundedRectangle(cornerRadius: 3).fill(.primary500).frame(width: proxy.size.width * 0.595) + RoundedRectangle(cornerRadius: 3).fill(.secondary500) + } + } + .frame(height: 6) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + + @ViewBuilder + func dashboardTabs() -> some View { + HStack(spacing: 0) { + ForEach(ClassOwnerDashboardFeature.Tab.allCases, id: \.self) { tab in + Button { send(.tabSelected(tab)) } label: { + Text(tab.rawValue) + .pretendardFont(family: .Medium, size: 13) + .foregroundStyle(store.selectedTab == tab ? .primary500 : .gray300) + .frame(maxWidth: .infinity) + .frame(height: 36) + .overlay(alignment: .bottom) { + Rectangle() + .fill(store.selectedTab == tab ? .primary500 : .gray100) + .frame(height: store.selectedTab == tab ? 2 : 1) + } + } + .buttonStyle(.plain) + } + } + } + + @ViewBuilder + func homePage() -> some View { + VStack(alignment: .leading, spacing: 16) { + dashboardSection("클래스 참여 현황", trailing: "총 47회 참여") { + HStack(spacing: 8) { + metric("참여 학생", value: "28명") + metric("평균 참여", value: "59.5%") + metric("미참여", value: "4명") + } + } + dashboardSection("입장 분포", trailing: "총 47회 참여") { + HStack(spacing: 10) { + distribution("찬성", value: "59.5%", color: .primary500) + distribution("반대", value: "40.5%", color: .secondary500) + } + } + dashboardSection("확인이 필요한 멤버", trailing: "총 47회 참여") { + ForEach(store.members.filter(\.needsReview)) { memberRow($0, action: "피드백") } + } + dashboardSection("참여도가 높은 멤버", trailing: "총 47회 참여") { + ForEach(store.members.sorted { $0.participationCount > $1.participationCount }.prefix(2)) { member in + memberRow(member, action: "상세") + } + } + dashboardSection("주요 의견", trailing: "총 47회 참여") { + ForEach(store.opinions.prefix(2)) { opinion in opinionRow(opinion) } + } + } + } + + @ViewBuilder + func membersPage() -> some View { + VStack(alignment: .leading, spacing: 12) { + Text("멤버 \(store.room.memberCount)") + .pretendardFont(.semiBold15).foregroundStyle(.gray800) + TextField("멤버 검색", text: Binding( + get: { store.searchText }, + set: { send(.searchTextChanged($0)) } + )) + .pretendardFont(.regular13) + .padding(.horizontal, 12).frame(height: 40) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(ClassOwnerDashboardFeature.MemberSort.allCases, id: \.self) { sort in + Button { send(.memberSortSelected(sort)) } label: { + Text(sort.rawValue).pickeSortChip(isSelected: store.memberSort == sort) + } + .buttonStyle(.plain) + } + } + } + ForEach(store.visibleMembers) { member in + memberRow(member, action: member.needsReview ? "피드백" : "상세") + } + } + } + + @ViewBuilder + func opinionsPage() -> some View { + VStack(alignment: .leading, spacing: 12) { + opinionFilters() + ForEach(store.visibleOpinions) { opinion in opinionRow(opinion) } + } + } + + @ViewBuilder + func opinionFilters() -> some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(ClassOwnerDashboardFeature.OpinionFilter.allCases, id: \.self) { filter in + Button { send(.opinionFilterSelected(filter)) } label: { + Text(filter.rawValue).pickeSortChip(isSelected: store.opinionFilter == filter) + }.buttonStyle(.plain) + } + } + } + } + + @ViewBuilder + func dashboardSection( + _ title: String, + trailing: String, + @ViewBuilder content: () -> some View + ) -> some View { + VStack(alignment: .leading, spacing: 10) { + HStack { + Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) + Spacer() + Text(trailing).pretendardFont(.regular10).foregroundStyle(.gray300) + } + content() + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + + @ViewBuilder + func metric(_ title: String, value: String) -> some View { + VStack(alignment: .leading, spacing: 6) { + Text(title).pretendardFont(.regular10).foregroundStyle(.gray300) + Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + + @ViewBuilder + func distribution(_ title: String, value: String, color: Color) -> some View { + VStack(alignment: .leading, spacing: 6) { + HStack { Circle().fill(color).frame(width: 7, height: 7); Text(title); Spacer(); Text(value) } + .pretendardFont(.regular13).foregroundStyle(.gray500) + ProgressView(value: title == "찬성" ? 0.595 : 0.405).tint(color) + }.frame(maxWidth: .infinity) + } + + @ViewBuilder + func memberRow(_ member: ClassOwnerDashboardFeature.Member, action: String) -> some View { + Button { + send(member.needsReview ? .feedbackTapped(member.id) : .memberTapped(member.id)) + } label: { + HStack(spacing: 10) { + Text(String(member.name.prefix(1))).pickeAvatarFallback(size: 32).pickeAvatar(size: 32) + VStack(alignment: .leading, spacing: 2) { + Text(member.name).pretendardFont(.medium13).foregroundStyle(.gray700) + Text("배틀 참여 \(member.participationCount)회").pretendardFont(.regular10).foregroundStyle(.gray300) + } + Spacer() + Text(action).pretendardFont(family: .Bold, size: 12).foregroundStyle(.primary500) + } + .padding(.vertical, 7) + }.buttonStyle(.plain) + } + + @ViewBuilder + func opinionRow(_ opinion: ClassOwnerDashboardFeature.Opinion) -> some View { + Button { send(.opinionTapped(opinion.id)) } label: { + VStack(alignment: .leading, spacing: 7) { + HStack { + Text(opinion.author).pretendardFont(.medium13).foregroundStyle(.gray500) + Spacer() + if opinion.isReported { + Text("신고").pretendardFont(.regular10).foregroundStyle(.errorDefault) + } + } + Text(opinion.text) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .multilineTextAlignment(.leading) + Text("대댓글 \(opinion.replyCount)개").pretendardFont(.regular10).foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + }.buttonStyle(.plain) + } +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassFeedbackComposeFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassFeedbackComposeFeature.swift new file mode 100644 index 00000000..9eb068c2 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassFeedbackComposeFeature.swift @@ -0,0 +1,59 @@ +import ClassDomainInterface +import ComposableArchitecture +import Foundation + +@Reducer +public struct ClassFeedbackComposeFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public let room: ClassRoom + public let memberName: String + public var feedback = "" + public var showUnavailableAlert = false + + public init(room: ClassRoom, memberName: String) { + self.room = room + self.memberName = memberName + } + } + + public enum Action: ViewAction, BindableAction { + case binding(BindingAction) + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View: Equatable { + case backTapped + case cancelTapped + case submitTapped + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case submitted(String) + } + + public var body: some Reducer { + BindingReducer() + Reduce { state, action in + switch action { + case .binding: + return .none + case .view(.backTapped), .view(.cancelTapped): + return .send(.delegate(.dismiss)) + case .view(.submitTapped): + let feedback = state.feedback.trimmingCharacters(in: .whitespacesAndNewlines) + guard !feedback.isEmpty else { return .none } + state.showUnavailableAlert = true + return .none + case .delegate: + return .none + } + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift new file mode 100644 index 00000000..569f389a --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift @@ -0,0 +1,74 @@ +import ClassDomainInterface +import ComposableArchitecture + +@Reducer +public struct ClassMemberDetailFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public let room: ClassRoom + public let memberName: String + public let participationRate: String + public let commentCount: Int + public let replyCount: Int + public let receivedLikeCount: Int + + public init( + room: ClassRoom, + memberName: String = "플라톤", + participationRate: String = "59.5%", + commentCount: Int = 1, + replyCount: Int = 12, + receivedLikeCount: Int = 8 + ) { + self.room = room + self.memberName = memberName + self.participationRate = participationRate + self.commentCount = commentCount + self.replyCount = replyCount + self.receivedLikeCount = receivedLikeCount + } + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View: Equatable { + case backTapped + case commentTapped + case replyTapped + case feedbackTapped + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + case commentSelected + case replySelected + case feedbackSelected + } + + public var body: some Reducer { + Reduce { _, action in + switch action { + case let .view(viewAction): + switch viewAction { + case .backTapped: + return .send(.delegate(.dismiss)) + case .commentTapped: + return .send(.delegate(.commentSelected)) + case .replyTapped: + return .send(.delegate(.replySelected)) + case .feedbackTapped: + return .send(.delegate(.feedbackSelected)) + } + case .delegate: + return .none + } + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift new file mode 100644 index 00000000..7ba74bd5 --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift @@ -0,0 +1,66 @@ +import ClassDomainInterface +import ComposableArchitecture + +@Reducer +public struct ClassReplyDetailFeature { + public init() {} + + @ObservableState + public struct State: Equatable { + public let room: ClassRoom + public let replies: [Reply] + + public struct Reply: Equatable, Identifiable, Sendable { + public let id: Int + public let author: String + public let body: String + public let date: String + + public init(id: Int, author: String, body: String, date: String) { + self.id = id + self.author = author + self.body = body + self.date = date + } + } + + public init(room: ClassRoom, replies: [Reply]? = nil) { + self.room = room + self.replies = replies ?? Reply.mocks + } + } + + public enum Action: ViewAction { + case view(View) + case delegate(DelegateAction) + } + + @CasePathable + public enum View: Equatable { + case backTapped + } + + @CasePathable + public enum DelegateAction: Equatable { + case dismiss + } + + public var body: some Reducer { + Reduce { _, action in + switch action { + case .view(.backTapped): + return .send(.delegate(.dismiss)) + case .delegate: + return .none + } + } + } +} + +private extension ClassReplyDetailFeature.State.Reply { + static let mocks = [ + Self(id: 1, author: "김민지", body: "피해자 보호를 위한 기준도 함께 필요하지 않을까요?", date: "26.09.23. 14:02"), + Self(id: 2, author: "플라톤", body: "교육과 보호도 함께 필요하다고 생각해요.", date: "26.09.23. 14:02"), + Self(id: 3, author: "이도윤", body: "처벌만으로는 문제를 해결하기 어렵다고 느꼈어요.", date: "26.09.23. 14:03"), + ] +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift new file mode 100644 index 00000000..899d1efd --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift @@ -0,0 +1,100 @@ +import ComposableArchitecture +import PickeDesignKit +import PickeSharedUI +import SwiftUI + +@ViewAction(for: ClassFeedbackComposeFeature.self) +public struct ClassFeedbackComposeView: View { + @Bindable public var store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) + .foregroundStyle(.gray800) + Text("예시 데이터") + .pretendardFont(.medium10) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + + ScrollView { + VStack(alignment: .leading, spacing: 16) { + HStack(spacing: 12) { + PickeAvatarView(imageURL: nil, fallback: store.memberName, size: 40) + VStack(alignment: .leading, spacing: 4) { + Text(store.memberName) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(.gray800) + Text("운영자 피드백") + .pretendardFont(family: .Medium, size: 12) + .foregroundStyle(.gray300) + } + } + feedbackEditor + HStack(spacing: 8) { + Button { send(.cancelTapped) } label: { + Text("취소").frame(maxWidth: .infinity) + } + .buttonStyle(.plain) + .ctaButtonStyle(.secondary, size: .large, height: 52) + + Button { send(.submitTapped) } label: { + Text("등록").frame(maxWidth: .infinity) + } + .buttonStyle(.plain) + .ctaButtonStyle(.primary, size: .large, height: 52) + .disabled(store.feedback.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + } + .padding(16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .toolbar(.hidden, for: .navigationBar) + .toolbar(.hidden, for: .tabBar) + .alert("전송 준비 중", isPresented: $store.showUnavailableAlert) { + Button("확인", role: .cancel) {} + } message: { + Text("서버 연동 전이라 피드백을 전송할 수 없습니다.") + } + } +} + +private extension ClassFeedbackComposeView { + var feedbackEditor: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("운영자 피드백") + .pretendardFont(family: .SemiBold, size: 13) + .foregroundStyle(.gray800) + Spacer() + Text("총 47회 참여") + .pretendardFont(family: .Medium, size: 10) + .foregroundStyle(.gray300) + } + TextEditor(text: $store.feedback) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .scrollContentBackground(.hidden) + .padding(8) + .frame(height: 280) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(alignment: .topLeading) { + if store.feedback.isEmpty { + Text("학생에게 전할 피드백을 작성해주세요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + .padding(.horizontal, 12) + .padding(.top, 16) + .allowsHitTesting(false) + } + } + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + } +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift new file mode 100644 index 00000000..fabbb13a --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift @@ -0,0 +1,188 @@ +import ComposableArchitecture +import PickeDesignKit +import PickeSharedUI +import SwiftUI + +@ViewAction(for: ClassMemberDetailFeature.self) +public struct ClassMemberDetailView: View { + public let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) + .foregroundStyle(.gray800) + Text("예시 데이터") + .pretendardFont(.medium10) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + + ScrollView { + VStack(spacing: 16) { + memberHeader + summarySection + activitySection + feedbackSection + } + .padding(16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .toolbar(.hidden, for: .navigationBar) + .toolbar(.hidden, for: .tabBar) + } +} + +private extension ClassMemberDetailView { + var memberHeader: some View { + HStack(spacing: 12) { + PickeAvatarView(imageURL: nil, fallback: store.memberName, size: 40) + VStack(alignment: .leading, spacing: 4) { + Text(store.memberName) + .pretendardFont(family: .SemiBold, size: 16) + .foregroundStyle(.gray800) + Text("클래스 멤버") + .pretendardFont(family: .Medium, size: 12) + .foregroundStyle(.gray300) + } + Spacer() + } + .padding(.vertical, 12) + .overlay(alignment: .bottom) { + Rectangle().fill(.beige600).frame(height: 1) + } + } + + var summarySection: some View { + detailSection("참여 요약", trailing: "총 47회 참여") { + HStack(spacing: 0) { + metric(value: store.participationRate, label: "참여율") + separator + metric(value: "28회", label: "배틀 참여") + separator + metric(value: "19회", label: "미참여") + } + .padding(.vertical, 12) + } + } + + var activitySection: some View { + VStack(spacing: 16) { + detailSection("남긴 댓글", trailing: "총 47회 참여") { + Button { send(.commentTapped) } label: { + activityCard( + title: "낮춰야 한다", + body: "제도화가 무서운 건, 사회적 압력이 선택을 의무로 바꿀 수 있다는 거예요.", + icon: "chevron.right" + ) + } + .buttonStyle(.plain) + } + + detailSection("남긴 대댓글", trailing: "총 47회 참여") { + Button { send(.replyTapped) } label: { + VStack(alignment: .leading, spacing: 8) { + Text("토론을 들으면서 처벌만으로는 해결되지 않는 점을 이해하게 됐어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .multilineTextAlignment(.leading) + Text("26.09.23. 14:02") + .pretendardFont(family: .SemiBold, size: 10) + .foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + .buttonStyle(.plain) + } + } + } + + var feedbackSection: some View { + detailSection("운영자 피드백", trailing: "총 47회 참여") { + VStack(alignment: .leading, spacing: 12) { + Text("처음에는 처벌 기준에 집중했지만, 토론 후 다른 관점을 받아들이고 자신의 생각을 바꾼 과정이 잘 드러났어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .lineLimit(3) + Button { send(.feedbackTapped) } label: { + Text("피드백 작성") + .frame(maxWidth: .infinity) + } + .buttonStyle(.plain) + .ctaButtonStyle(.primary, size: .medium, height: 42) + } + } + } + + @ViewBuilder + func detailSection( + _ title: String, + trailing: String, + @ViewBuilder content: () -> some View + ) -> some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + Text(title) + .pretendardFont(family: .SemiBold, size: 13) + .foregroundStyle(.gray800) + Spacer() + Text(trailing) + .pretendardFont(family: .Medium, size: 10) + .foregroundStyle(.gray300) + } + content() + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + + @ViewBuilder + func metric(value: String, label: String) -> some View { + VStack(spacing: 2) { + Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) + Text(label).pretendardFont(.regular13).foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity) + } + + var separator: some View { + Rectangle().fill(.beige600).frame(width: 1, height: 47) + } + + @ViewBuilder + func activityCard(title: String, body: String, icon: String) -> some View { + HStack(alignment: .top, spacing: 8) { + VStack(alignment: .leading, spacing: 8) { + Text(title).pickeBadge(.filled, size: .tag) + Text(body) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .multilineTextAlignment(.leading) + HStack(spacing: 12) { + Label("1,340", systemImage: "heart") + Label("23", systemImage: "bubble") + } + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + Spacer(minLength: 0) + Image(systemName: icon) + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(.gray300) + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } +} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift new file mode 100644 index 00000000..f636134e --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift @@ -0,0 +1,70 @@ +import ComposableArchitecture +import PickeDesignKit +import PickeSharedUI +import SwiftUI + +@ViewAction(for: ClassReplyDetailFeature.self) +public struct ClassReplyDetailView: View { + public let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + VStack(spacing: 0) { + PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) + .foregroundStyle(.gray800) + Text("예시 데이터") + .pretendardFont(.medium10) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + + ScrollView { + VStack(spacing: 12) { + ForEach(store.replies) { reply in + replyCard(reply) + } + } + .padding(16) + } + .scrollIndicators(.hidden) + } + .screenBackground() + .toolbar(.hidden, for: .navigationBar) + .toolbar(.hidden, for: .tabBar) + } +} + +private extension ClassReplyDetailView { + @ViewBuilder + func replyCard(_ reply: ClassReplyDetailFeature.State.Reply) -> some View { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { + PickeAvatarView(imageURL: nil, fallback: reply.author, size: 32) + Text(reply.author) + .pretendardFont(family: .SemiBold, size: 13) + .foregroundStyle(.gray800) + Spacer() + Text(reply.date) + .pretendardFont(family: .Medium, size: 10) + .foregroundStyle(.gray300) + } + Text(reply.body) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .frame(maxWidth: .infinity, alignment: .leading) + HStack(spacing: 12) { + Label("12", systemImage: "heart") + Label("답글", systemImage: "bubble") + } + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } +} diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift index b88036d2..f6ba315f 100644 --- a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -221,9 +221,12 @@ private extension ClassReportView { Text("“피해자 보호를 위한 기준도 함께 필요하지 않을까요?”") .pretendardFont(.regular13) .foregroundStyle(.gray700) - Text("좋아요 1,340 · 대댓글 23") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + HStack(spacing: 12) { + Label("1,340", systemImage: "heart") + Label("23", systemImage: "bubble") + } + .pretendardFont(family: .Medium, size: 12) + .foregroundStyle(.gray300) } } @@ -253,7 +256,7 @@ private extension ClassReportView { reportCard(padding: 16, spacing: 8) { cardHeading("다음 생각 해보기") Text("처벌과 교화를 함께 한다면,\n무엇을 기준으로 균형을 잡을까요?") - .pretendardFont(.regular13) + .pretendardFont(family: .SemiBold, size: 18) .foregroundStyle(.gray700) Text("내 의견과 반대되는 입장도 함께 떠올려 보세요.") .pretendardFont(.regular13) diff --git a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift index b67391bd..8d38d6bd 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassDetailTests.swift @@ -77,6 +77,25 @@ struct ClassDetailTests { await store.receive(\.delegate, .openReport(room)) } + @Test + func 닉네임_수정_모달은_입력한_이름을_보존하고_닫힌다() async { + let store = TestStore(initialState: ClassDetailFeature.State(room: ClassRoom.mocks[1])) { + ClassDetailFeature() + } + + await store.send(.view(.nicknameTapped)) { + $0.modal = .init(kind: .nickname) + $0.draftNickname = "" + } + await store.send(.binding(.set(\.draftNickname, " 민지 "))) { + $0.draftNickname = " 민지 " + } + await store.send(.view(.nicknameSaved)) { + $0.modal = nil + $0.draftNickname = "민지" + } + } + @Test func 삭제가_완료된_뒤에만_화면을_닫는다() async { let room = ClassRoom.mocks[0] diff --git a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift index 8c75f1a6..0f7dccf8 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift @@ -16,7 +16,7 @@ struct ClassMemberTests { await store.send(.view(.removeTapped(member.id))) { $0.selectedMember = member $0.customAlert = CustomAlertState( - title: "\(member.name)님을\n클래스에서 내보낼까요?", + title: "\(member.name)님을 클래스에서 내보낼까요?\n한 번 내보내면 되돌릴 수 없어요.", confirmTitle: "내보내기", cancelTitle: "뒤로가기", isDestructive: true, @@ -52,4 +52,27 @@ struct ClassMemberTests { } #expect(store.state.visibleMembers.allSatisfy { $0.name.contains("학생 1") }) } + + @Test + func 멤버_필터를_열고_선택한_조건을_적용한다() async { + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + ClassMemberFeature() + } + + await store.send(.view(.filterTapped)) { + $0.filter = .init() + } + await store.send( + .filter(.presented(.participationSelected(.completed))) + ) { + $0.filter?.participation = .completed + } + await store.send(.filter(.presented(.applyTapped))) { + $0.appliedFilter.participation = .completed + $0.filter = nil + } + await store.send(.view(.filterTapped)) { + $0.filter = $0.appliedFilter + } + } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift b/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift new file mode 100644 index 00000000..7d854202 --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift @@ -0,0 +1,52 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct ClassOwnerDashboardTests { + @Test + func 멤버_정렬과_검색을_함께_적용한다() async { + let store = TestStore(initialState: ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0])) { + ClassOwnerDashboardFeature() + } + + await store.send(.view(.memberSortSelected(.participation))) { + $0.memberSort = .participation + } + #expect(store.state.visibleMembers.map(\.participationCount) == [8, 7, 6, 5, 4, 3]) + + await store.send(.view(.searchTextChanged("김민지"))) { + $0.searchText = "김민지" + } + #expect(store.state.visibleMembers.map(\.name) == ["김민지"]) + } + + @Test + func 피드백_대상_멤버를_전달한다() async { + let store = TestStore(initialState: ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0])) { + ClassOwnerDashboardFeature() + } + let member = store.state.members[0] + + await store.send(.view(.feedbackTapped(member.id))) + await store.receive(\.delegate, .openFeedback(member)) + } + + @Test + func 서버_연동_전에는_피드백_등록을_전송하지_않는다() async { + let store = TestStore(initialState: ClassFeedbackComposeFeature.State( + room: ClassRoom.mocks[0], + memberName: "김민지" + )) { + ClassFeedbackComposeFeature() + } + + await store.send(.binding(.set(\.feedback, "좋은 의견이에요"))) { + $0.feedback = "좋은 의견이에요" + } + await store.send(.view(.submitTapped)) { + $0.showUnavailableAlert = true + } + } +} diff --git a/Projects/Feature/Home/Sources/Attendance/View/AttendanceModalView.swift b/Projects/Feature/Home/Sources/Attendance/View/AttendanceModalView.swift index b78fbfe7..ff8df784 100644 --- a/Projects/Feature/Home/Sources/Attendance/View/AttendanceModalView.swift +++ b/Projects/Feature/Home/Sources/Attendance/View/AttendanceModalView.swift @@ -3,9 +3,8 @@ // Home // -import SwiftUI - import ComposableArchitecture +import SwiftUI @ViewAction(for: AttendanceModalFeature.self) public struct AttendanceModalView: View { @@ -27,7 +26,7 @@ public struct AttendanceModalView: View { extension AttendanceModalView { @ViewBuilder private func dimmedBackground() -> some View { - Color.black.opacity(0.4) + Color.clear .ignoresSafeArea() .contentShape(Rectangle()) .onTapGesture { send(.dismissTapped) } diff --git a/Projects/UI/PickeSharedUI/Sources/Modal/PickeModal.swift b/Projects/UI/PickeSharedUI/Sources/Modal/PickeModal.swift index b38abbbf..ae9bfeb3 100644 --- a/Projects/UI/PickeSharedUI/Sources/Modal/PickeModal.swift +++ b/Projects/UI/PickeSharedUI/Sources/Modal/PickeModal.swift @@ -3,27 +3,42 @@ // DesignSystem // -import SwiftUI - -import PickeDesignKit - import ComposableArchitecture +import PickeDesignKit +import SwiftUI public extension View { /// 하단에서 슬라이드업 되는 커스텀 모달(바텀시트) 오버레이. + /// 딤은 제자리에서 페이드되고, 시트만 아래에서 올라온다. /// - Parameters: /// - store: `@Presents` 스코프 바인딩 (nil 이면 미표시). + /// - dimOpacity: 딤 배경 불투명도. /// - content: 표시할 모달 뷰 (스코프된 스토어를 받음). func pickeModal( _ store: Binding?>, + dimOpacity: Double = 0.4, @ViewBuilder content: @escaping (Store) -> some View ) -> some View { overlay { - if let modalStore = store.wrappedValue { - content(modalStore) - .transition(.move(edge: .bottom).combined(with: .opacity)) + ZStack { + if store.wrappedValue != nil { + Color.black.opacity(dimOpacity) + .ignoresSafeArea() + .allowsHitTesting(false) + .transition(.opacity.animation(.easeInOut(duration: 0.2))) + } + if let modalStore = store.wrappedValue { + content(modalStore) + .transition(.move(edge: .bottom)) + } } } - .animation(.easeInOut(duration: 0.3), value: store.wrappedValue != nil) + .animation( + .spring( + response: 0.36, + dampingFraction: 0.86 + ), + value: store.wrappedValue != nil + ) } } From cb61493e88959fe05e16e661c9d47edd1929c194 Mon Sep 17 00:00:00 2001 From: Roy Date: Thu, 1 Oct 2026 23:06:42 +0900 Subject: [PATCH 39/40] =?UTF-8?q?PR=20=EB=A6=AC=EB=B7=B0=20=EC=A7=80?= =?UTF-8?q?=EC=A0=81=20=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클래스 데이터의 목 응답을 운영 경로에서 제거하고 UseCase 의존성 및 화면 모델을 정리합니다. 멤버·리포트의 가짜 상태 변경을 제거하고 탭 식별자와 준비 중인 버튼 동작을 보완합니다. --- .../MainTab/MainTabCoordinator.swift | 10 +- .../Entity/ClassOwner/ClassOwnerMember.swift | 18 ++ .../ClassOwner/ClassOwnerMemberSort.swift | 5 + .../Entity/ClassOwner/ClassOwnerOpinion.swift | 21 ++ .../ClassOwner/ClassOwnerOpinionFilter.swift | 6 + .../Entity/ClassOwner/ClassOwnerTab.swift | 5 + .../Sources/Repository/ClassInterface.swift | 12 +- .../Sources/UseCase/ClassUseCaseImpl.swift} | 15 +- .../Sources/ClassLiveDependencies.swift | 4 - .../Repository/ClassRepositoryImpl.swift | 28 +- .../Tests/Sources/ClassDomainTests.swift | 31 ++- .../Reducer/ClassIntroFeature.swift | 9 +- .../ClassIntro/View/ClassIntroView.swift | 15 ++ .../Reducer/ClassMemberFeature.swift | 62 ++--- .../ClassMember/View/ClassMemberView.swift | 43 +-- .../Reducer/ClassOwnerDashboardFeature.swift | 99 ++----- .../View/ClassOwnerDashboardView.swift | 16 +- .../View/Components/ClassBattleListCard.swift | 4 +- .../Reducer/ClassReportFeature.swift | 8 - .../ClassReport/View/ClassReportView.swift | 245 ++++-------------- .../Class/Tests/Sources/ClassJoinTests.swift | 6 +- .../Tests/Sources/ClassMemberTests.swift | 46 ++-- .../Tests/Sources/ClassReportTests.swift | 4 - .../Class/Tests/Sources/ClassTests.swift | 14 + 24 files changed, 303 insertions(+), 423 deletions(-) create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMember.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMemberSort.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerTab.swift rename Projects/Domain/ClassDomain/{Sources/UseCase/ClassUseCase.swift => Interface/Sources/UseCase/ClassUseCaseImpl.swift} (78%) diff --git a/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift index 744b5ad8..2d6dabf8 100644 --- a/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift +++ b/Projects/App/Sources/Navigation/MainTab/MainTabCoordinator.swift @@ -18,11 +18,11 @@ public struct MainTabCoordinator { public init() {} public enum Tab: Int, CaseIterable { - case home - case explore - case quickBattle - case classroom - case myPage + case home = 0 + case explore = 1 + case quickBattle = 2 + case classroom = 4 + case myPage = 3 public var title: String { switch self { diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMember.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMember.swift new file mode 100644 index 00000000..682b3580 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMember.swift @@ -0,0 +1,18 @@ +public struct ClassOwnerMember: Equatable, Identifiable, Sendable { + public let id: Int + public let name: String + public let participationCount: Int + public let needsReview: Bool + + public init( + id: Int, + name: String, + participationCount: Int, + needsReview: Bool = false + ) { + self.id = id + self.name = name + self.participationCount = participationCount + self.needsReview = needsReview + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMemberSort.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMemberSort.swift new file mode 100644 index 00000000..496bf88b --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerMemberSort.swift @@ -0,0 +1,5 @@ +public enum ClassOwnerMemberSort: String, CaseIterable, Hashable, Sendable { + case name = "이름순" + case participation = "참여 많은순" + case needsReview = "확인 필요순" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift new file mode 100644 index 00000000..5a1084ff --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift @@ -0,0 +1,21 @@ +public struct ClassOwnerOpinion: Equatable, Identifiable, Sendable { + public let id: Int + public let author: String + public let text: String + public let replyCount: Int + public let isReported: Bool + + public init( + id: Int, + author: String, + text: String, + replyCount: Int, + isReported: Bool = false + ) { + self.id = id + self.author = author + self.text = text + self.replyCount = replyCount + self.isReported = isReported + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift new file mode 100644 index 00000000..bcadcaaa --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift @@ -0,0 +1,6 @@ +public enum ClassOwnerOpinionFilter: String, CaseIterable, Hashable, Sendable { + case all = "전체" + case recommended = "추천순" + case replies = "대댓글 많은순" + case inProgress = "진행 중 10" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerTab.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerTab.swift new file mode 100644 index 00000000..0a2be3ab --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerTab.swift @@ -0,0 +1,5 @@ +public enum ClassOwnerTab: String, CaseIterable, Hashable, Sendable { + case home = "홈" + case members = "멤버" + case opinions = "의견" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift index 973d5559..628a9ecd 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift @@ -17,20 +17,14 @@ public enum ClassRepositoryDependency: TestDependencyKey { } } -public enum ClassUseCaseDependency: TestDependencyKey { - public static var testValue: any ClassInterface { - MockClassRepository() - } -} - public extension DependencyValues { var classRepository: any ClassInterface { get { self[ClassRepositoryDependency.self] } set { self[ClassRepositoryDependency.self] = newValue } } - var classUseCase: any ClassInterface { - get { self[ClassUseCaseDependency.self] } - set { self[ClassUseCaseDependency.self] = newValue } + var classUseCase: ClassUseCaseImpl { + get { self[ClassUseCaseImpl.self] } + set { self[ClassUseCaseImpl.self] = newValue } } } diff --git a/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift b/Projects/Domain/ClassDomain/Interface/Sources/UseCase/ClassUseCaseImpl.swift similarity index 78% rename from Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift rename to Projects/Domain/ClassDomain/Interface/Sources/UseCase/ClassUseCaseImpl.swift index 6fc9fbb3..d55237da 100644 --- a/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCase.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/UseCase/ClassUseCaseImpl.swift @@ -1,8 +1,19 @@ -import ClassDomainInterface import ComposableArchitecture import Foundation -public struct ClassUseCaseImpl: ClassInterface { +public struct ClassUseCaseImpl: ClassInterface, DependencyKey { + public static var liveValue: ClassUseCaseImpl { + ClassUseCaseImpl() + } + + public static var testValue: ClassUseCaseImpl { + ClassUseCaseImpl() + } + + public static var previewValue: ClassUseCaseImpl { + ClassUseCaseImpl() + } + @Dependency(\.classRepository) private var repository public init() {} diff --git a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift index 78608474..ad6a3667 100644 --- a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift +++ b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift @@ -1,10 +1,6 @@ import ClassDomainInterface import ComposableArchitecture -extension ClassUseCaseDependency: DependencyKey { - public static var liveValue: any ClassInterface { ClassUseCaseImpl() } -} - extension ClassRepositoryDependency: DependencyKey { public static var liveValue: any ClassInterface { ClassRepositoryImpl() } } diff --git a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift index f0dd3512..5797142b 100644 --- a/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift +++ b/Projects/Domain/ClassDomain/Sources/Repository/ClassRepositoryImpl.swift @@ -2,35 +2,35 @@ import ClassDomainInterface import Foundation public struct ClassRepositoryImpl: ClassInterface { - private static let mock = MockClassRepository() + private static let unavailableMessage = "클래스 서버 API가 연결되지 않았습니다." public init() {} public func fetchMyClasses() async throws -> [ClassRoom] { - try await Self.mock.fetchMyClasses() + throw ClassError.network(Self.unavailableMessage) } - public func fetchRecommendedBattles(filter: ClassTopicFilter) async throws -> [ClassBattleSummary] { - try await Self.mock.fetchRecommendedBattles(filter: filter) + public func fetchRecommendedBattles(filter _: ClassTopicFilter) async throws -> [ClassBattleSummary] { + throw ClassError.network(Self.unavailableMessage) } - public func createClass(_ creation: ClassCreation) async throws -> ClassRoom { - try await Self.mock.createClass(creation) + public func createClass(_: ClassCreation) async throws -> ClassRoom { + throw ClassError.network(Self.unavailableMessage) } - public func fetchClass(joinCode: String) async throws -> ClassRoom { - try await Self.mock.fetchClass(joinCode: joinCode) + public func fetchClass(joinCode _: String) async throws -> ClassRoom { + throw ClassError.network(Self.unavailableMessage) } - public func joinClass(joinCode: String, nickname: String) async throws -> ClassRoom { - try await Self.mock.joinClass(joinCode: joinCode, nickname: nickname) + public func joinClass(joinCode _: String, nickname _: String) async throws -> ClassRoom { + throw ClassError.network(Self.unavailableMessage) } - public func updateDeadline(id: Int, deadline: Date) async throws -> ClassRoom { - try await Self.mock.updateDeadline(id: id, deadline: deadline) + public func updateDeadline(id _: Int, deadline _: Date) async throws -> ClassRoom { + throw ClassError.network(Self.unavailableMessage) } - public func deleteClass(id: Int) async throws { - try await Self.mock.deleteClass(id: id) + public func deleteClass(id _: Int) async throws { + throw ClassError.network(Self.unavailableMessage) } } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift index 51d8640c..7d8261a5 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassDomainTests.swift @@ -1,23 +1,30 @@ import ClassDomain import ClassDomainInterface +import ComposableArchitecture import Foundation import Testing struct ClassDomainTests { @Test - func 서로_다른_live_저장소에서_생성한_클래스를_조회할_수_있다() async throws { - let created = try await ClassRepositoryImpl().createClass( - ClassCreation( - name: "공유 상태 확인", - deadline: Date(timeIntervalSince1970: 1_800_000_000), - battleId: 101, - allowsAnonymousOpinion: true, - requiresComment: false - ) - ) + func live_저장소는_mock_클래스를_반환하지_않는다() async throws { + do { + _ = try await ClassRepositoryImpl().fetchMyClasses() + Issue.record("live 저장소가 서버 연결 없이 클래스 목록을 반환함") + } catch { + #expect(ClassError.from(error) == .network("클래스 서버 API가 연결되지 않았습니다.")) + } + } + + @Test + func useCase는_주입된_repository를_사용한다() async throws { + let room = ClassRoom.mocks[0] + let useCase = withDependencies { + $0.classRepository = MockClassRepository(rooms: [room], availableRooms: []) + } operation: { + ClassUseCaseImpl() + } - let rooms = try await ClassRepositoryImpl().fetchMyClasses() - #expect(rooms.contains(created)) + #expect(try await useCase.fetchMyClasses() == [room]) } @Test diff --git a/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift b/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift index 0e6b7ccd..86a91100 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/Reducer/ClassIntroFeature.swift @@ -14,6 +14,7 @@ public struct ClassIntroFeature { @ObservableState public struct State: Equatable { @Presents public var join: ClassJoinFeature.State? + public var showTicketNotice = false public init() {} } @@ -32,6 +33,7 @@ public struct ClassIntroFeature { case myClassesTapped case createTapped case ticketTapped + case ticketNoticeDismissed } @CasePathable @@ -109,7 +111,12 @@ extension ClassIntroFeature { return .send(.delegate(.create)) case .ticketTapped: - return .send(.delegate(.ticket)) + state.showTicketNotice = true + return .none + + case .ticketNoticeDismissed: + state.showTicketNotice = false + return .none } } diff --git a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift index 1327426b..87f07e50 100644 --- a/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift +++ b/Projects/Feature/Class/Sources/ClassIntro/View/ClassIntroView.swift @@ -47,6 +47,21 @@ public struct ClassIntroView: View { ) { joinStore in ClassJoinView(store: joinStore) } + .alert( + "이용권 등록 준비 중", + isPresented: Binding( + get: { store.showTicketNotice }, + set: { + if !$0 { + send(.ticketNoticeDismissed) + } + } + ) + ) { + Button("확인", role: .cancel) {} + } message: { + Text("이용권 등록 기능은 아직 사용할 수 없습니다.") + } } } diff --git a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift index 8d0443f1..24c00592 100644 --- a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift +++ b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift @@ -1,7 +1,6 @@ import ClassDomainInterface import ComposableArchitecture import Foundation -import PickeSharedUI @Reducer public struct ClassMemberFeature { @@ -24,10 +23,8 @@ public struct ClassMemberFeature { public let room: ClassRoom public var members: [Member] public var searchText = "" - public var selectedMember: Member? public var appliedFilter = ClassMemberFilterFeature.State() @Presents public var filter: ClassMemberFilterFeature.State? - @Presents public var customAlert: CustomAlertState? public init(room: ClassRoom) { self.room = room @@ -39,7 +36,18 @@ public struct ClassMemberFeature { public var visibleMembers: [Member] { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) - return query.isEmpty ? members : members.filter { $0.name.localizedCaseInsensitiveContains(query) } + let filteredMembers = query.isEmpty + ? members + : members.filter { $0.name.localizedCaseInsensitiveContains(query) } + + switch appliedFilter.sort { + case .name: + return filteredMembers.sorted { + $0.name.localizedStandardCompare($1.name) == .orderedAscending + } + case .comments, .replies, .recommendations: + return filteredMembers + } } } @@ -47,7 +55,6 @@ public struct ClassMemberFeature { case binding(BindingAction) case view(View) case filter(PresentationAction) - case customAlert(PresentationAction) case delegate(DelegateAction) } @@ -55,8 +62,6 @@ public struct ClassMemberFeature { case backTapped case filterTapped case removeTapped(Int) - case removeConfirmed - case removeCancelled } public enum DelegateAction: Equatable { @@ -82,19 +87,6 @@ public struct ClassMemberFeature { return .none case .filter: return .none - case let .customAlert(alertAction): - switch alertAction { - case .presented(.confirmTapped): - guard let member = state.selectedMember else { return .none } - state.members.removeAll(where: { $0.id == member.id }) - state.selectedMember = nil - state.customAlert = nil - return .none - case .presented(.cancelTapped), .dismiss: - state.selectedMember = nil - state.customAlert = nil - return .none - } case .delegate: return .none } @@ -102,9 +94,6 @@ public struct ClassMemberFeature { .ifLet(\.$filter, action: \.filter) { ClassMemberFilterFeature() } - .ifLet(\.$customAlert, action: \.customAlert) { - CustomConfirmAlert() - } } } @@ -117,27 +106,8 @@ extension ClassMemberFeature { state.filter = state.appliedFilter return .none case let .removeTapped(id): - guard state.room.role == .owner, - let member = state.members.first(where: { $0.id == id && !$0.isOwner }) - else { return .none } - state.selectedMember = member - state.customAlert = CustomAlertState( - title: "\(member.name)님을 클래스에서 내보낼까요?\n한 번 내보내면 되돌릴 수 없어요.", - confirmTitle: "내보내기", - cancelTitle: "뒤로가기", - isDestructive: true, - style: .deleteConfirm - ) - return .none - case .removeConfirmed: - guard let member = state.selectedMember else { return .none } - state.members.removeAll(where: { $0.id == member.id }) - state.selectedMember = nil - state.customAlert = nil - return .none - case .removeCancelled: - state.selectedMember = nil - state.customAlert = nil + // Member removal requires a server mutation. Keep this action inert until that API exists. + _ = id return .none } } @@ -198,12 +168,16 @@ public struct ClassMemberFilterFeature { Reduce { state, action in switch action { case let .participationSelected(value): + guard value == .all else { return .none } state.participation = value case let .completionSelected(value): + guard value == .all else { return .none } state.completion = value case let .attendanceSelected(value): + guard value == .all else { return .none } state.attendance = value case let .sortSelected(value): + guard value == .name else { return .none } state.sort = value case .applyTapped, .dismissTapped: break diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift index 5e9f3ae8..91149adf 100644 --- a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -20,6 +20,15 @@ public struct ClassMemberView: View { .padding(.horizontal, 16) .padding(.top, 12) + if store.room.role == .owner { + Text("멤버 내보내기는 서버 연동 후 제공됩니다.") + .pretendardFont(family: .Medium, size: 12) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.top, 8) + } + ScrollView { LazyVStack(alignment: .leading, spacing: 0) { Text("멤버 \(store.members.count)") @@ -45,7 +54,6 @@ public struct ClassMemberView: View { ) { filterStore in filterSheet(store: filterStore) } - .customAlert($store.scope(state: \.customAlert, action: \.customAlert)) } } @@ -91,14 +99,6 @@ private extension ClassMemberView { .padding(.horizontal, 8) .padding(.vertical, 4) .background(.beige600, in: RoundedRectangle(cornerRadius: 2)) - } else if store.room.role == .owner { - Button { send(.removeTapped(member.id)) } label: { - Image(systemName: "minus.circle") - .font(.system(size: 20)) - .foregroundStyle(.gray500) - .frame(width: 32, height: 32) - } - .accessibilityLabel("\(member.name) 내보내기") } } .frame(height: 68) @@ -117,31 +117,35 @@ private extension ClassMemberView { sheetHandle filterSection( title: "참여 상태", - detail: "총 " + String(self.store.members.count) + "명", + detail: "제공 예정", options: ClassMemberFilterFeature.Participation.allCases.filter { $0 != .all }, selected: { store.participation == $0 }, - action: { store.send(.participationSelected($0)) } + action: { store.send(.participationSelected($0)) }, + enabled: { _ in false } ) filterSection( title: "미완료", - detail: "총 " + String(self.store.members.count) + "명", + detail: "제공 예정", options: ClassMemberFilterFeature.Completion.allCases.filter { $0 != .all }, selected: { store.completion == $0 }, - action: { store.send(.completionSelected($0)) } + action: { store.send(.completionSelected($0)) }, + enabled: { _ in false } ) filterSection( title: "입장 변화", - detail: "총 " + String(self.store.members.count) + "명", + detail: "제공 예정", options: ClassMemberFilterFeature.Attendance.allCases.filter { $0 != .all }, selected: { store.attendance == $0 }, - action: { store.send(.attendanceSelected($0)) } + action: { store.send(.attendanceSelected($0)) }, + enabled: { _ in false } ) filterSection( title: "정렬", - detail: "총 " + String(self.store.members.count) + "명", + detail: "이름순만 제공", options: ClassMemberFilterFeature.Sort.allCases, selected: { store.sort == $0 }, - action: { store.send(.sortSelected($0)) } + action: { store.send(.sortSelected($0)) }, + enabled: { $0 == .name } ) Button { store.send(.applyTapped) } label: { @@ -167,7 +171,8 @@ private extension ClassMemberView { detail: String, options: [Option], selected: @escaping (Option) -> Bool, - action: @escaping (Option) -> Void + action: @escaping (Option) -> Void, + enabled: @escaping (Option) -> Bool ) -> some View where Option.RawValue == String { VStack(alignment: .leading, spacing: 12) { HStack(spacing: 10) { @@ -186,6 +191,7 @@ private extension ClassMemberView { Text(option.rawValue) .pretendardFont(family: .Medium, size: 13) .foregroundStyle(selected(option) ? .beige50 : .gray300) + .opacity(enabled(option) ? 1 : 0.5) .lineLimit(1) .fixedSize(horizontal: true, vertical: false) .padding(.horizontal, 12) @@ -193,6 +199,7 @@ private extension ClassMemberView { .background(selected(option) ? .primary500 : .beige200, in: RoundedRectangle(cornerRadius: 2)) } .buttonStyle(.plain) + .disabled(!enabled(option)) } Spacer(minLength: 0) } diff --git a/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift index 553b733a..f4b0604b 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift @@ -6,95 +6,40 @@ import Foundation public struct ClassOwnerDashboardFeature { public init() {} - public enum Tab: String, CaseIterable, Hashable { - case home = "홈" - case members = "멤버" - case opinions = "의견" - } - - public enum OpinionFilter: String, CaseIterable, Hashable { - case all = "전체" - case recommended = "추천순" - case replies = "대댓글 많은순" - case inProgress = "진행 중 10" - } - - public enum MemberSort: String, CaseIterable, Hashable { - case name = "이름순" - case participation = "참여 많은순" - case needsReview = "확인 필요순" - } - - public struct Member: Equatable, Identifiable, Sendable { - public let id: Int - public let name: String - public let participationCount: Int - public let needsReview: Bool - - public init(id: Int, name: String, participationCount: Int, needsReview: Bool = false) { - self.id = id - self.name = name - self.participationCount = participationCount - self.needsReview = needsReview - } - } - - public struct Opinion: Equatable, Identifiable, Sendable { - public let id: Int - public let author: String - public let text: String - public let replyCount: Int - public let isReported: Bool - - public init( - id: Int, - author: String, - text: String, - replyCount: Int, - isReported: Bool = false - ) { - self.id = id - self.author = author - self.text = text - self.replyCount = replyCount - self.isReported = isReported - } - } - @ObservableState public struct State: Equatable { public let room: ClassRoom - public var selectedTab: Tab = .home - public var opinionFilter: OpinionFilter = .all - public var memberSort: MemberSort = .name + public var selectedTab: ClassOwnerTab = .home + public var opinionFilter: ClassOwnerOpinionFilter = .all + public var memberSort: ClassOwnerMemberSort = .name public var searchText = "" - public var members: [Member] - public var opinions: [Opinion] + public var members: [ClassOwnerMember] + public var opinions: [ClassOwnerOpinion] public init(room: ClassRoom) { self.room = room members = [ - Member(id: 1, name: "김민지", participationCount: 8, needsReview: true), - Member(id: 2, name: "이서준", participationCount: 7), - Member(id: 3, name: "박지우", participationCount: 6), - Member(id: 4, name: "최유진", participationCount: 5), - Member(id: 5, name: "정다은", participationCount: 4), - Member(id: 6, name: "한도윤", participationCount: 3, needsReview: true), + ClassOwnerMember(id: 1, name: "김민지", participationCount: 8, needsReview: true), + ClassOwnerMember(id: 2, name: "이서준", participationCount: 7), + ClassOwnerMember(id: 3, name: "박지우", participationCount: 6), + ClassOwnerMember(id: 4, name: "최유진", participationCount: 5), + ClassOwnerMember(id: 5, name: "정다은", participationCount: 4), + ClassOwnerMember(id: 6, name: "한도윤", participationCount: 3, needsReview: true), ] opinions = [ - Opinion( + ClassOwnerOpinion( id: 1, author: "김민지", text: "피해자 보호를 위한 기준도 함께 필요하지 않을까요?", replyCount: 23 ), - Opinion( + ClassOwnerOpinion( id: 2, author: "이서준", text: "제도화가 선택을 의무로 바꾸지 않도록 기준이 필요해요.", replyCount: 12 ), - Opinion( + ClassOwnerOpinion( id: 3, author: "박지우", text: "교육과 보호도 함께 고려해야 한다고 생각합니다.", @@ -103,7 +48,7 @@ public struct ClassOwnerDashboardFeature { ] } - public var visibleMembers: [Member] { + public var visibleMembers: [ClassOwnerMember] { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) let matching = query.isEmpty ? members : members.filter { $0.name.localizedCaseInsensitiveContains(query) } switch memberSort { @@ -116,7 +61,7 @@ public struct ClassOwnerDashboardFeature { } } - public var visibleOpinions: [Opinion] { + public var visibleOpinions: [ClassOwnerOpinion] { switch opinionFilter { case .all, .inProgress: return opinions @@ -135,9 +80,9 @@ public struct ClassOwnerDashboardFeature { public enum View: Equatable { case backTapped - case tabSelected(Tab) - case opinionFilterSelected(OpinionFilter) - case memberSortSelected(MemberSort) + case tabSelected(ClassOwnerTab) + case opinionFilterSelected(ClassOwnerOpinionFilter) + case memberSortSelected(ClassOwnerMemberSort) case searchTextChanged(String) case memberTapped(Int) case opinionTapped(Int) @@ -146,9 +91,9 @@ public struct ClassOwnerDashboardFeature { public enum DelegateAction: Equatable { case dismiss - case openMemberDetail(Member) - case openReplyDetail(Opinion) - case openFeedback(Member) + case openMemberDetail(ClassOwnerMember) + case openReplyDetail(ClassOwnerOpinion) + case openFeedback(ClassOwnerMember) } public var body: some Reducer { diff --git a/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift b/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift index d6a1e8f3..62adea46 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift @@ -23,9 +23,9 @@ public struct ClassOwnerDashboardView: View { get: { store.selectedTab }, set: { send(.tabSelected($0)) } )) { - ScrollView { homePage().padding(16) }.tag(ClassOwnerDashboardFeature.Tab.home) - ScrollView { membersPage().padding(16) }.tag(ClassOwnerDashboardFeature.Tab.members) - ScrollView { opinionsPage().padding(16) }.tag(ClassOwnerDashboardFeature.Tab.opinions) + ScrollView { homePage().padding(16) }.tag(ClassOwnerTab.home) + ScrollView { membersPage().padding(16) }.tag(ClassOwnerTab.members) + ScrollView { opinionsPage().padding(16) }.tag(ClassOwnerTab.opinions) } .tabViewStyle(.page(indexDisplayMode: .never)) } @@ -82,7 +82,7 @@ private extension ClassOwnerDashboardView { @ViewBuilder func dashboardTabs() -> some View { HStack(spacing: 0) { - ForEach(ClassOwnerDashboardFeature.Tab.allCases, id: \.self) { tab in + ForEach(ClassOwnerTab.allCases, id: \.self) { tab in Button { send(.tabSelected(tab)) } label: { Text(tab.rawValue) .pretendardFont(family: .Medium, size: 13) @@ -145,7 +145,7 @@ private extension ClassOwnerDashboardView { .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { - ForEach(ClassOwnerDashboardFeature.MemberSort.allCases, id: \.self) { sort in + ForEach(ClassOwnerMemberSort.allCases, id: \.self) { sort in Button { send(.memberSortSelected(sort)) } label: { Text(sort.rawValue).pickeSortChip(isSelected: store.memberSort == sort) } @@ -171,7 +171,7 @@ private extension ClassOwnerDashboardView { func opinionFilters() -> some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { - ForEach(ClassOwnerDashboardFeature.OpinionFilter.allCases, id: \.self) { filter in + ForEach(ClassOwnerOpinionFilter.allCases, id: \.self) { filter in Button { send(.opinionFilterSelected(filter)) } label: { Text(filter.rawValue).pickeSortChip(isSelected: store.opinionFilter == filter) }.buttonStyle(.plain) @@ -219,7 +219,7 @@ private extension ClassOwnerDashboardView { } @ViewBuilder - func memberRow(_ member: ClassOwnerDashboardFeature.Member, action: String) -> some View { + func memberRow(_ member: ClassOwnerMember, action: String) -> some View { Button { send(member.needsReview ? .feedbackTapped(member.id) : .memberTapped(member.id)) } label: { @@ -237,7 +237,7 @@ private extension ClassOwnerDashboardView { } @ViewBuilder - func opinionRow(_ opinion: ClassOwnerDashboardFeature.Opinion) -> some View { + func opinionRow(_ opinion: ClassOwnerOpinion) -> some View { Button { send(.opinionTapped(opinion.id)) } label: { VStack(alignment: .leading, spacing: 7) { HStack { diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift index fbb82018..071f5384 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/Components/ClassBattleListCard.swift @@ -61,9 +61,11 @@ private extension ClassBattleListCard { Text("미리듣기") .pretendardFont(.semiBold12) } - .foregroundStyle(.primary500) + .foregroundStyle(.gray300) } .buttonStyle(.plain) + .disabled(true) + .accessibilityHint("미리듣기는 준비 중입니다") } @ViewBuilder diff --git a/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift b/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift index 6b3376a7..801b5eb8 100644 --- a/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift +++ b/Projects/Feature/Class/Sources/ClassReport/Reducer/ClassReportFeature.swift @@ -16,7 +16,6 @@ public struct ClassReportFeature { public struct State: Equatable { public var room: ClassRoom public var selectedTab: Tab = .summary - public var isChartDrawn = false public init(room: ClassRoom) { self.room = room @@ -32,7 +31,6 @@ public struct ClassReportFeature { public enum View: Equatable { case backTapped case tabSelected(Tab) - case chartAnimationStarted } @CasePathable @@ -48,12 +46,6 @@ public struct ClassReportFeature { case let .view(.tabSelected(tab)): guard state.selectedTab != tab else { return .none } state.selectedTab = tab - state.isChartDrawn = false - return .none - case .view(.chartAnimationStarted): - if state.selectedTab == .classResult { - state.isChartDrawn = true - } return .none case .delegate: return .none diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift index f6ba315f..b1f8d330 100644 --- a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -5,7 +5,6 @@ import SwiftUI @ViewAction(for: ClassReportFeature.self) public struct ClassReportView: View { public let store: StoreOf - @Environment(\.accessibilityReduceMotion) private var reduceMotion public init(store: StoreOf) { self.store = store @@ -29,11 +28,6 @@ public struct ClassReportView: View { .screenBackground() .toolbar(.hidden, for: .navigationBar) .toolbar(.hidden, for: .tabBar) - .onChange(of: store.selectedTab) { _, tab in - if tab == .classResult { - send(.chartAnimationStarted) - } - } } } @@ -116,23 +110,24 @@ private extension ClassReportView { func summaryContent() -> some View { reportCard { cardHeading("내 참여 요약", destination: .participation) - voteChange() - participationCounts() + unavailableReport( + title: "내 투표와 참여 기록을 불러올 수 없어요", + message: "리포트 데이터가 연결되면 이곳에서 확인할 수 있어요." + ) } reportCard { cardHeading("AI 리포트", destination: .feedback) - Text("다른 해결방법까지 생각을 확장했어요") - .pretendardFont(family: .SemiBold, size: 18) - .foregroundStyle(.gray800) - Text("“교육과 보호도 함께 필요하다”는 대댓글에서 처벌 외의 대안을 제시했어요") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + unavailableReport( + title: "AI 리포트를 준비하고 있어요", + message: "작성한 의견이 분석되면 이곳에서 확인할 수 있어요." + ) } reportCard { cardHeading("선생님 피드백", destination: .feedback) - Text("“왜 생각이 달라졌는지 자신의 말로 설명한 점이 좋았어요”") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + unavailableReport( + title: "선생님 피드백 데이터를 불러올 수 없어요", + message: "선생님 피드백이 등록되면 이곳에서 확인할 수 있어요." + ) } } @@ -140,54 +135,24 @@ private extension ClassReportView { func participationContent() -> some View { reportCard { cardHeading("내 투표 결과") - voteChange() - participationCounts() + unavailableReport( + title: "내 투표 결과를 불러올 수 없어요", + message: "이 클래스의 투표 결과가 연결되면 표시할게요." + ) } reportCard(padding: 16, spacing: 8) { cardHeading("내 댓글") - HStack { - Text("낮춰야한다").pickeBadge(.filled, size: .tag) - Spacer() - Text("26.09.23. 14:02") - .pretendardFont(family: .SemiBold, size: 10) - .foregroundStyle(.gray300) - } - Text("제도화가 무서운 건, 사회적 압력이 '선택'을 '의무'로 바꿀 수 있다는 거예요. 네덜란드 사례를 보면 우려가 현실이 되고 있죠.") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - HStack(spacing: 12) { - Label("1,340", systemImage: "heart") - Label("23", systemImage: "bubble") - } - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + unavailableReport( + title: "댓글 데이터를 불러올 수 없어요", + message: "댓글 데이터가 연결되면 이곳에서 확인할 수 있어요." + ) } reportCard(padding: 16, spacing: 8) { cardHeading("내 대댓글") - VStack(alignment: .leading, spacing: 4) { - Text("김민지 · 교정이 우선이다") - .pretendardFont(family: .SemiBold, size: 12) - .foregroundStyle(.gray500) - Text("제도화가 무서운 건, 사회적 압력이 '선택'을 '의무'로 바꿀 수 있다는 거예요.") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) - } - .padding(12) - .frame(maxWidth: .infinity, alignment: .leading) - .background(.beige300, in: RoundedRectangle(cornerRadius: 2)) - Text("↳ 내 대댓글") - .pretendardFont(family: .SemiBold, size: 12) - .foregroundStyle(.primary500) - Text("토론을 들으면서 처벌만으로는 해결되지 않는 점을 이해하게 됐어요. 교육과 보호도 함께 필요하다고 생각해요.") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - HStack { - Text("26.09.23. 14:02") - Spacer() - Label("12", systemImage: "heart") - } - .pretendardFont(family: .SemiBold, size: 10) - .foregroundStyle(.gray300) + unavailableReport( + title: "대댓글 데이터를 불러올 수 없어요", + message: "대댓글 데이터가 연결되면 이곳에서 확인할 수 있어요." + ) } } @@ -196,37 +161,34 @@ private extension ClassReportView { reportCard { cardHeading("우리 클래스 참여 현황") HStack { - Text("참여한 학생") + Text("클래스 멤버") .pretendardFont(.regular13) .foregroundStyle(.gray300) Spacer() - Text("28 / 32") + Text("\(store.room.memberCount)명") .pretendardFont(.semiBold15) .foregroundStyle(.gray800) } - voteBar("사전 투표 결과", primary: "낮춰야 한다", secondary: "교정이 우선이다", fraction: 0.67, color: .secondary500) - voteBar("사후 투표 결과", primary: "낮춰야 한다", secondary: "교정이 우선이다", fraction: 0.49, color: .primary500) + unavailableReport( + title: "투표 분포를 집계할 수 없어요", + message: "현재 클래스 데이터에는 투표 집계가 포함되어 있지 않아요." + ) } reportCard(padding: 16, spacing: 8) { cardHeading("클래스 결과 요약") - Text("입장을 바꾼 학생은 6명이에요.\n연령하향 → 교정우선 5명, 교정 우선 → 연령 하향 1명이에요.\n교정 우선 선택은 10명에서 14명으로 늘었어요.") + Text("\(store.room.battle.optionATitle)\n\(store.room.battle.optionBTitle)") .pretendardFont(.regular13) .foregroundStyle(.gray700) + Text("두 선택지에 대한 클래스 투표 결과가 연결되면 비교해 드릴게요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) } reportCard(padding: 16, spacing: 8) { cardHeading("BEST 댓글") - Text("김민지 · 교정이 우선이다") - .pretendardFont(family: .SemiBold, size: 12) - .foregroundStyle(.gray500) - Text("“피해자 보호를 위한 기준도 함께 필요하지 않을까요?”") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - HStack(spacing: 12) { - Label("1,340", systemImage: "heart") - Label("23", systemImage: "bubble") - } - .pretendardFont(family: .Medium, size: 12) - .foregroundStyle(.gray300) + unavailableReport( + title: "BEST 댓글 데이터를 불러올 수 없어요", + message: "댓글 반응 데이터가 연결되면 가장 많은 공감을 받은 댓글을 보여드릴게요." + ) } } @@ -234,46 +196,26 @@ private extension ClassReportView { func feedbackContent() -> some View { reportCard { cardHeading("AI 피드백") - feedbackRow( - "내 생각의 변화를 설명했어요", - description: "“처벌만으로는 해결되지 않는 점”을 언급하며, 처음과 입장이 달리진 이유를 드러냈어요.", - icon: "arrow.left.arrow.right" - ) - Divider().overlay(.beige500) - feedbackRow("다른 해결방법을 제안했어요", description: "반대의견에 그치지 않고 “교육과 보호”라는 대안을 덧붙였어요.", icon: "lightbulb") - Divider().overlay(.beige500) - feedbackRow( - "근거를 한 단계 더 구체적으로", - description: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지, 사례나 자료 하나를 연결하면 주장이 더 설득력 있어져요.", - icon: "sparkles" + unavailableReport( + title: "AI 분석 결과가 아직 없어요", + message: "내 의견과 댓글 데이터가 연결되면 생각의 변화와 근거를 분석해 드릴게요." ) - Divider().overlay(.beige500) - Text("분석에 사용한 내 대댓글 보기") - .pretendardFont(family: .Bold, size: 12) - .foregroundStyle(.primary500) - .frame(maxWidth: .infinity, alignment: .leading) } reportCard(padding: 16, spacing: 8) { cardHeading("다음 생각 해보기") - Text("처벌과 교화를 함께 한다면,\n무엇을 기준으로 균형을 잡을까요?") + Text(store.room.battle.title) .pretendardFont(family: .SemiBold, size: 18) .foregroundStyle(.gray700) - Text("내 의견과 반대되는 입장도 함께 떠올려 보세요.") + Text("두 선택지를 비교하며 내 생각을 정리해 보세요.") .pretendardFont(.regular13) .foregroundStyle(.gray300) } reportCard(padding: 16, spacing: 8) { cardHeading("선생님 피드백") - Text("처음과 나중의 생각이 어떻게 달라졌는지 스스로 정리한 점이 좋았어요. 단순히 입장을 바꾸는 데서 끝나지 않고, 왜 바뀌었는지 설명하려는 태도가 인상적이었습니다.") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - HStack { - Text("김민지 선생님") - Spacer() - Text("26.09.23. 14:02") - } - .pretendardFont(family: .SemiBold, size: 10) - .foregroundStyle(.gray300) + unavailableReport( + title: "선생님 피드백 데이터를 불러올 수 없어요", + message: "선생님이 피드백을 남기면 이곳에서 확인할 수 있어요." + ) } } @@ -315,93 +257,14 @@ private extension ClassReportView { } @ViewBuilder - func voteChange() -> some View { - HStack(spacing: 0) { - voteChoice("사전 선택", value: "낮춰야 한다") - Text("입장 변경") - .pretendardFont(family: .SemiBold, size: 10) - .foregroundStyle(.beige50) - .padding(.horizontal, 6) - .padding(.vertical, 2) - .background(.primary500, in: Capsule()) - voteChoice("사후 선택", value: "교정이 우선이다") - } - .frame(height: 60) - .background(.beige300, in: RoundedRectangle(cornerRadius: 2)) - } - - @ViewBuilder - func voteChoice(_ label: String, value: String) -> some View { - VStack(spacing: 4) { - Text(label) - .pretendardFont(family: .Medium, size: 11) - .foregroundStyle(.gray300) - Text(value) - .pretendardFont(family: .SemiBold, size: 12) + func unavailableReport(title: String, message: String) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .pretendardFont(.semiBold15) .foregroundStyle(.gray800) - } - .frame(maxWidth: .infinity) - } - - @ViewBuilder - func participationCounts() -> some View { - HStack(spacing: 0) { - count("1", label: "댓글") - Rectangle().fill(.beige600).frame(width: 1, height: 47) - count("12", label: "대댓글") - Rectangle().fill(.beige600).frame(width: 1, height: 47) - count("8", label: "받은 좋아요") - } - .frame(height: 55) - } - - @ViewBuilder - func count(_ value: String, label: String) -> some View { - VStack(spacing: 2) { - Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) - Text(label).pretendardFont(.regular13).foregroundStyle(.gray300) - } - .frame(maxWidth: .infinity) - } - - @ViewBuilder - func voteBar(_ title: String, primary: String, secondary: String, fraction: CGFloat, color: Color) -> some View { - VStack(alignment: .leading, spacing: 6) { - Text(title).pretendardFont(.regular13).foregroundStyle(.gray500) - GeometryReader { proxy in - ZStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 2) - .fill(color) - .frame(width: proxy.size.width * fraction, height: 34) - .scaleEffect(x: store.isChartDrawn ? 1 : 0, anchor: .leading) - .animation(reduceMotion ? nil : .easeOut(duration: 0.7), value: store.isChartDrawn) - HStack(spacing: 12) { - Text(primary) - .pretendardFont(.regular13) - .foregroundStyle(.gray800) - .frame(width: proxy.size.width * fraction, height: 34) - Text(secondary) - .pretendardFont(.regular13) - .foregroundStyle(.gray500) - } - } - } - .frame(height: 34) - .background(.beige300, in: RoundedRectangle(cornerRadius: 2)) - } - } - - @ViewBuilder - func feedbackRow(_ title: String, description: String, icon: String) -> some View { - HStack(alignment: .top, spacing: 6) { - Image(systemName: icon) - .font(.system(size: 20)) - .foregroundStyle(.primary500) - .frame(width: 24, height: 24) - VStack(alignment: .leading, spacing: 2) { - Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) - Text(description).pretendardFont(.regular13).foregroundStyle(.gray500) - } + Text(message) + .pretendardFont(.regular13) + .foregroundStyle(.gray300) } } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift index a49edf93..65428a42 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift @@ -22,7 +22,7 @@ struct ClassJoinTests { let store = TestStore(initialState: ClassJoinFeature.State()) { ClassJoinFeature() } withDependencies: { - $0.classUseCase = StubClassUseCase(fetchClass: { _ in room }) + $0.classRepository = StubClassUseCase(fetchClass: { _ in room }) } await store.send(.binding(.set(\.joinCode, " pk9t3s "))) { @@ -46,7 +46,7 @@ struct ClassJoinTests { let store = TestStore(initialState: state) { ClassJoinFeature() } withDependencies: { - $0.classUseCase = StubClassUseCase(joinClass: { code, nickname in + $0.classRepository = StubClassUseCase(joinClass: { code, nickname in #expect(code == room.joinCode) #expect(nickname == "민지") return room @@ -69,7 +69,7 @@ struct ClassJoinTests { let store = TestStore(initialState: ClassJoinFeature.State()) { ClassJoinFeature() } withDependencies: { - $0.classUseCase = StubClassUseCase(fetchClass: { _ in + $0.classRepository = StubClassUseCase(fetchClass: { _ in throw ClassError.invalidCode }) } diff --git a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift index 0f7dccf8..2e62dea7 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift @@ -1,34 +1,20 @@ @testable import Class import ClassDomainInterface import ComposableArchitecture -import PickeSharedUI import Testing @MainActor struct ClassMemberTests { @Test - func 선생님은_멤버를_선택하고_삭제할_수_있다() async { + func 멤버_내보내기는_서버_연동_전까지_상태를_변경하지_않는다() async { let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { ClassMemberFeature() } let member = store.state.members[1] + let originalMembers = store.state.members - await store.send(.view(.removeTapped(member.id))) { - $0.selectedMember = member - $0.customAlert = CustomAlertState( - title: "\(member.name)님을 클래스에서 내보낼까요?\n한 번 내보내면 되돌릴 수 없어요.", - confirmTitle: "내보내기", - cancelTitle: "뒤로가기", - isDestructive: true, - style: .deleteConfirm - ) - } - await store.send(.customAlert(.presented(.confirmTapped))) { - $0.members.remove(at: 1) - $0.selectedMember = nil - $0.customAlert = nil - } - #expect(store.state.members.count == ClassRoom.mocks[0].memberCount - 1) + await store.send(.view(.removeTapped(member.id))) + #expect(store.state.members == originalMembers) } @Test @@ -38,7 +24,7 @@ struct ClassMemberTests { } await store.send(.view(.removeTapped(2))) - #expect(store.state.selectedMember == nil) + #expect(store.state.members.count == ClassRoom.mocks[1].memberCount) } @Test @@ -64,13 +50,29 @@ struct ClassMemberTests { } await store.send( .filter(.presented(.participationSelected(.completed))) - ) { - $0.filter?.participation = .completed + ) + await store.send(.filter(.presented(.applyTapped))) { + $0.filter = nil + } + #expect(store.state.appliedFilter.participation == .all) + } + + @Test + func 이름순_정렬은_시드된_멤버에_적용된다() async { + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + ClassMemberFeature() + } + + await store.send(.view(.filterTapped)) { + $0.filter = .init() } + await store.send(.filter(.presented(.sortSelected(.name)))) await store.send(.filter(.presented(.applyTapped))) { - $0.appliedFilter.participation = .completed $0.filter = nil } + #expect(store.state.visibleMembers == store.state.members.sorted { + $0.name.localizedStandardCompare($1.name) == .orderedAscending + }) await store.send(.view(.filterTapped)) { $0.filter = $0.appliedFilter } diff --git a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift index 524b9e7e..d8bc3d3e 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift @@ -14,13 +14,9 @@ struct ClassReportTests { await store.send(.view(.tabSelected(.classResult))) { $0.selectedTab = .classResult } - await store.send(.view(.chartAnimationStarted)) { - $0.isChartDrawn = true - } await store.send(.view(.tabSelected(.classResult))) await store.send(.view(.tabSelected(.feedback))) { $0.selectedTab = .feedback - $0.isChartDrawn = false } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassTests.swift b/Projects/Feature/Class/Tests/Sources/ClassTests.swift index 26a8d315..e9747923 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassTests.swift @@ -72,6 +72,20 @@ struct ClassTests { await store.receive(\.delegate.create) } + @Test + func 이용권_등록_기능이_없으면_안내를_표시한다() async { + let store = TestStore(initialState: ClassIntroFeature.State()) { + ClassIntroFeature() + } + + await store.send(.view(.ticketTapped)) { + $0.showTicketNotice = true + } + await store.send(.view(.ticketNoticeDismissed)) { + $0.showTicketNotice = false + } + } + @Test func 같은_카테고리를_다시_누르면_선택이_해제된다() async { let store = TestStore(initialState: ClassTopicFeature.State()) { From 7881f6f22e32a7adeea2d33e0d62db4adaf330a2 Mon Sep 17 00:00:00 2001 From: Roy Date: Fri, 2 Oct 2026 22:45:34 +0900 Subject: [PATCH 40/40] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9D=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=99=80=20=EB=94=94=EC=9E=90=EC=9D=B8=EC=9D=84=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클래스 멤버와 운영자 화면의 상태 및 이동 동작을 정리하고 피그마 예시는 미리보기 데이터로 분리한다. 도메인 유스케이스 의존성과 클래스 화면 테스트를 기존 모듈 방식에 맞춘다. --- .../Navigation/Class/ClassCoordinator.swift | 13 +- .../Entity/ClassMember/ClassMember.swift | 11 + .../ClassMember/ClassMemberAttendance.swift | 6 + .../ClassMember/ClassMemberCompletion.swift | 6 + .../ClassMemberParticipation.swift | 6 + .../Entity/ClassMember/ClassMemberSort.swift | 6 + .../Entity/ClassOwner/ClassOwnerOpinion.swift | 8 + .../ClassOwner/ClassOwnerOpinionFilter.swift | 6 +- .../Sources/Repository/ClassInterface.swift | 12 +- .../Sources/ClassLiveDependencies.swift | 10 +- .../Sources/UseCase/ClassUseCaseImpl.swift | 15 +- .../Reducer/ClassMemberFeature.swift | 97 ++--- .../ClassMember/View/ClassMemberView.swift | 40 +- .../Reducer/ClassOwnerDashboardFeature.swift | 206 +++++++-- .../View/ClassOwnerDashboardView.swift | 409 +++++++++++++----- .../Reducer/ClassMemberDetailFeature.swift | 70 ++- .../Reducer/ClassReplyDetailFeature.swift | 25 +- .../View/ClassFeedbackComposeView.swift | 9 - .../View/ClassMemberDetailView.swift | 237 +++++----- .../View/ClassReplyDetailView.swift | 54 ++- .../Reducer/ClassRecommendFeature.swift | 22 +- .../View/ClassRecommendView.swift | 7 +- .../View/ClassReportPreviewContent.swift | 70 +++ .../ClassReport/View/ClassReportView.swift | 334 +++++++++++--- .../MyClass/Reducer/MyClassFeature.swift | 23 +- .../Sources/MyClass/View/MyClassView.swift | 11 +- .../Class/Tests/Sources/ClassJoinTests.swift | 6 +- .../Tests/Sources/ClassMemberTests.swift | 62 ++- .../Sources/ClassOwnerDashboardTests.swift | 111 ++++- .../Tests/Sources/ClassOwnerDetailTests.swift | 58 +++ .../Tests/Sources/ClassRecommendTests.swift | 7 +- .../Tests/Sources/ClassReportTests.swift | 11 + .../Class/Tests/Sources/MyClassTests.swift | 7 +- 33 files changed, 1481 insertions(+), 494 deletions(-) create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMember.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberAttendance.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberCompletion.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberParticipation.swift create mode 100644 Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberSort.swift rename Projects/Domain/ClassDomain/{Interface => }/Sources/UseCase/ClassUseCaseImpl.swift (78%) create mode 100644 Projects/Feature/Class/Sources/ClassReport/View/ClassReportPreviewContent.swift create mode 100644 Projects/Feature/Class/Tests/Sources/ClassOwnerDetailTests.swift diff --git a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift index 7e0b4237..14a4b7bb 100644 --- a/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift +++ b/Projects/App/Sources/Navigation/Class/ClassCoordinator.swift @@ -156,12 +156,12 @@ private extension ClassCoordinator { case let .routeAction(_, action: .ownerDashboard(.delegate(.openMemberDetail(member)))): guard case let .ownerDashboard(dashboard)? = state.routes.last?.screen else { return .none } - state.routes.push(.ownerMemberDetail(.init(room: dashboard.room, memberName: member.name))) + state.routes.push(.ownerMemberDetail(.init(room: dashboard.room, member: member))) return .none - case .routeAction(_, action: .ownerDashboard(.delegate(.openReplyDetail))): + case let .routeAction(_, action: .ownerDashboard(.delegate(.openReplyDetail(opinion)))): guard case let .ownerDashboard(dashboard)? = state.routes.last?.screen else { return .none } - state.routes.push(.ownerReplyDetail(.init(room: dashboard.room))) + state.routes.push(.ownerReplyDetail(.init(room: dashboard.room, opinion: opinion))) return .none case let .routeAction(_, action: .ownerDashboard(.delegate(.openFeedback(member)))): @@ -169,15 +169,14 @@ private extension ClassCoordinator { state.routes.push(.ownerFeedback(.init(room: dashboard.room, memberName: member.name))) return .none - case .routeAction(_, action: .ownerMemberDetail(.delegate(.replySelected))), - .routeAction(_, action: .ownerMemberDetail(.delegate(.commentSelected))): + case let .routeAction(_, action: .ownerMemberDetail(.delegate(.commentSelected(opinion)))): guard case let .ownerMemberDetail(member)? = state.routes.last?.screen else { return .none } - state.routes.push(.ownerReplyDetail(.init(room: member.room))) + state.routes.push(.ownerReplyDetail(.init(room: member.room, opinion: opinion))) return .none case .routeAction(_, action: .ownerMemberDetail(.delegate(.feedbackSelected))): guard case let .ownerMemberDetail(member)? = state.routes.last?.screen else { return .none } - state.routes.push(.ownerFeedback(.init(room: member.room, memberName: member.memberName))) + state.routes.push(.ownerFeedback(.init(room: member.room, memberName: member.member.name))) return .none case .routeAction(_, action: .ownerFeedback(.delegate(.submitted))): diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMember.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMember.swift new file mode 100644 index 00000000..a4d6cbd2 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMember.swift @@ -0,0 +1,11 @@ +public struct ClassMember: Equatable, Identifiable, Sendable { + public let id: Int + public let name: String + public let isOwner: Bool + + public init(id: Int, name: String, isOwner: Bool = false) { + self.id = id + self.name = name + self.isOwner = isOwner + } +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberAttendance.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberAttendance.swift new file mode 100644 index 00000000..dd4e2e4e --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberAttendance.swift @@ -0,0 +1,6 @@ +public enum ClassMemberAttendance: String, CaseIterable, Equatable, Sendable { + case all = "전체" + case changed = "입장 변화" + case unchanged = "입장 유지" + case inProgress = "진행 중" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberCompletion.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberCompletion.swift new file mode 100644 index 00000000..43365761 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberCompletion.swift @@ -0,0 +1,6 @@ +public enum ClassMemberCompletion: String, CaseIterable, Equatable, Sendable { + case all = "전체" + case noComment = "댓글 미작성" + case noAfterVote = "사후 투표 미완료" + case inProgress = "진행 중" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberParticipation.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberParticipation.swift new file mode 100644 index 00000000..997d76da --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberParticipation.swift @@ -0,0 +1,6 @@ +public enum ClassMemberParticipation: String, CaseIterable, Equatable, Sendable { + case all = "전체" + case notParticipated = "미참여" + case completed = "참여 완료" + case inProgress = "진행 중" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberSort.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberSort.swift new file mode 100644 index 00000000..79d81463 --- /dev/null +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassMember/ClassMemberSort.swift @@ -0,0 +1,6 @@ +public enum ClassMemberSort: String, CaseIterable, Equatable, Sendable { + case name = "이름순" + case comments = "댓글 많은순" + case replies = "대댓글 많은순" + case recommendations = "추천순" +} diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift index 5a1084ff..e3c0a04b 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinion.swift @@ -1,8 +1,12 @@ +import Foundation + public struct ClassOwnerOpinion: Equatable, Identifiable, Sendable { public let id: Int public let author: String public let text: String public let replyCount: Int + public let recommendationCount: Int? + public let createdAt: Date? public let isReported: Bool public init( @@ -10,12 +14,16 @@ public struct ClassOwnerOpinion: Equatable, Identifiable, Sendable { author: String, text: String, replyCount: Int, + recommendationCount: Int? = nil, + createdAt: Date? = nil, isReported: Bool = false ) { self.id = id self.author = author self.text = text self.replyCount = replyCount + self.recommendationCount = recommendationCount + self.createdAt = createdAt self.isReported = isReported } } diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift index bcadcaaa..5fcd69c5 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Entity/ClassOwner/ClassOwnerOpinionFilter.swift @@ -1,6 +1,4 @@ public enum ClassOwnerOpinionFilter: String, CaseIterable, Hashable, Sendable { - case all = "전체" - case recommended = "추천순" - case replies = "대댓글 많은순" - case inProgress = "진행 중 10" + case popular = "인기순" + case latest = "최신순" } diff --git a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift index 628a9ecd..973d5559 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift +++ b/Projects/Domain/ClassDomain/Interface/Sources/Repository/ClassInterface.swift @@ -17,14 +17,20 @@ public enum ClassRepositoryDependency: TestDependencyKey { } } +public enum ClassUseCaseDependency: TestDependencyKey { + public static var testValue: any ClassInterface { + MockClassRepository() + } +} + public extension DependencyValues { var classRepository: any ClassInterface { get { self[ClassRepositoryDependency.self] } set { self[ClassRepositoryDependency.self] = newValue } } - var classUseCase: ClassUseCaseImpl { - get { self[ClassUseCaseImpl.self] } - set { self[ClassUseCaseImpl.self] = newValue } + var classUseCase: any ClassInterface { + get { self[ClassUseCaseDependency.self] } + set { self[ClassUseCaseDependency.self] = newValue } } } diff --git a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift index ad6a3667..9bebe255 100644 --- a/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift +++ b/Projects/Domain/ClassDomain/Sources/ClassLiveDependencies.swift @@ -1,6 +1,14 @@ import ClassDomainInterface import ComposableArchitecture +extension ClassUseCaseDependency: DependencyKey { + public static var liveValue: any ClassInterface { + ClassUseCaseImpl() + } +} + extension ClassRepositoryDependency: DependencyKey { - public static var liveValue: any ClassInterface { ClassRepositoryImpl() } + public static var liveValue: any ClassInterface { + ClassRepositoryImpl() + } } diff --git a/Projects/Domain/ClassDomain/Interface/Sources/UseCase/ClassUseCaseImpl.swift b/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCaseImpl.swift similarity index 78% rename from Projects/Domain/ClassDomain/Interface/Sources/UseCase/ClassUseCaseImpl.swift rename to Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCaseImpl.swift index d55237da..6fc9fbb3 100644 --- a/Projects/Domain/ClassDomain/Interface/Sources/UseCase/ClassUseCaseImpl.swift +++ b/Projects/Domain/ClassDomain/Sources/UseCase/ClassUseCaseImpl.swift @@ -1,19 +1,8 @@ +import ClassDomainInterface import ComposableArchitecture import Foundation -public struct ClassUseCaseImpl: ClassInterface, DependencyKey { - public static var liveValue: ClassUseCaseImpl { - ClassUseCaseImpl() - } - - public static var testValue: ClassUseCaseImpl { - ClassUseCaseImpl() - } - - public static var previewValue: ClassUseCaseImpl { - ClassUseCaseImpl() - } - +public struct ClassUseCaseImpl: ClassInterface { @Dependency(\.classRepository) private var repository public init() {} diff --git a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift index 24c00592..f2ca2014 100644 --- a/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift +++ b/Projects/Feature/Class/Sources/ClassMember/Reducer/ClassMemberFeature.swift @@ -8,33 +8,24 @@ public struct ClassMemberFeature { @ObservableState public struct State: Equatable { - public struct Member: Equatable, Identifiable { - public let id: Int - public let name: String - public let isOwner: Bool - - public init(id: Int, name: String, isOwner: Bool = false) { - self.id = id - self.name = name - self.isOwner = isOwner - } + public enum ViewState: Equatable { + case unavailable + case noSearchResults + case members([ClassMember]) } public let room: ClassRoom - public var members: [Member] + public var members: [ClassMember] public var searchText = "" public var appliedFilter = ClassMemberFilterFeature.State() @Presents public var filter: ClassMemberFilterFeature.State? - public init(room: ClassRoom) { + public init(room: ClassRoom, members: [ClassMember] = []) { self.room = room - members = [Member(id: 1, name: "선생님", isOwner: true)] - + (0 ..< max(0, room.memberCount - 1)).map { index in - Member(id: index + 2, name: "학생 \(index + 1)") - } + self.members = members } - public var visibleMembers: [Member] { + public var visibleMembers: [ClassMember] { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) let filteredMembers = query.isEmpty ? members @@ -49,6 +40,12 @@ public struct ClassMemberFeature { return filteredMembers } } + + public var viewState: ViewState { + guard !members.isEmpty else { return .unavailable } + let visible = visibleMembers + return visible.isEmpty ? .noSearchResults : .members(visible) + } } public enum Action: ViewAction, BindableAction { @@ -97,6 +94,28 @@ public struct ClassMemberFeature { } } +#if DEBUG + public extension ClassMemberFeature.State { + static func preview(room: ClassRoom) -> Self { + Self( + room: room, + members: [ + ClassMember(id: 1, name: "김선생", isOwner: true), + ClassMember(id: 2, name: "공은지"), + ClassMember(id: 3, name: "권동현"), + ClassMember(id: 4, name: "김민지"), + ClassMember(id: 5, name: "천다올"), + ClassMember(id: 6, name: "유시영"), + ClassMember(id: 7, name: "주천수"), + ClassMember(id: 8, name: "김예은"), + ClassMember(id: 9, name: "서원지"), + ClassMember(id: 10, name: "현준혁"), + ] + ) + } + } +#endif + extension ClassMemberFeature { private func handleViewAction(state: inout State, action: View) -> Effect { switch action { @@ -117,51 +136,23 @@ extension ClassMemberFeature { public struct ClassMemberFilterFeature { @ObservableState public struct State: Equatable { - public var participation: Participation = .all - public var completion: Completion = .all - public var attendance: Attendance = .all - public var sort: Sort = .name + public var participation: ClassMemberParticipation = .all + public var completion: ClassMemberCompletion = .all + public var attendance: ClassMemberAttendance = .all + public var sort: ClassMemberSort = .name public init() {} } public enum Action: Equatable { - case participationSelected(Participation) - case completionSelected(Completion) - case attendanceSelected(Attendance) - case sortSelected(Sort) + case participationSelected(ClassMemberParticipation) + case completionSelected(ClassMemberCompletion) + case attendanceSelected(ClassMemberAttendance) + case sortSelected(ClassMemberSort) case applyTapped case dismissTapped } - public enum Participation: String, CaseIterable, Equatable { - case all = "전체" - case notParticipated = "미참여 4" - case completed = "참여 완료 18" - case inProgress = "진행 중 10" - } - - public enum Completion: String, CaseIterable, Equatable { - case all = "전체" - case noComment = "댓글 미작성" - case noAfterVote = "사후 투표 미완료" - case inProgress = "진행 중 10" - } - - public enum Attendance: String, CaseIterable, Equatable { - case all = "전체" - case changed = "입장 변화" - case unchanged = "입장 유지" - case inProgress = "진행 중 10" - } - - public enum Sort: String, CaseIterable, Equatable { - case name = "이름순" - case comments = "댓글 많은순" - case replies = "대댓글 많은순" - case recommendations = "추천순" - } - public init() {} public var body: some Reducer { diff --git a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift index 91149adf..2a4c7387 100644 --- a/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift +++ b/Projects/Feature/Class/Sources/ClassMember/View/ClassMemberView.swift @@ -1,3 +1,4 @@ +import ClassDomainInterface import ComposableArchitecture import PickeDesignKit import PickeSharedUI @@ -31,13 +32,28 @@ public struct ClassMemberView: View { ScrollView { LazyVStack(alignment: .leading, spacing: 0) { - Text("멤버 \(store.members.count)") + Text(store.members.isEmpty ? "클래스 멤버 \(store.room.memberCount)" : "멤버 \(store.members.count)") .pretendardFont(family: .Medium, size: 14) .foregroundStyle(.gray300) .padding(.bottom, 4) - ForEach(store.visibleMembers) { member in - memberRow(member) + switch store.viewState { + case .unavailable: + Text("멤버 목록을 불러올 수 없어요.") + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity) + .padding(.top, 72) + case .noSearchResults: + Text("검색 결과가 없어요.") + .pretendardFont(family: .Medium, size: 14) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity) + .padding(.top, 72) + case let .members(members): + ForEach(members) { member in + memberRow(member) + } } } .padding(.horizontal, 16) @@ -57,6 +73,14 @@ public struct ClassMemberView: View { } } +#if DEBUG + #Preview("클래스 멤버") { + ClassMemberView(store: Store(initialState: .preview(room: ClassRoom.mocks[0])) { + ClassMemberFeature() + }) + } +#endif + private extension ClassMemberView { var searchField: some View { HStack(spacing: 8) { @@ -82,7 +106,7 @@ private extension ClassMemberView { } @ViewBuilder - func memberRow(_ member: ClassMemberFeature.State.Member) -> some View { + func memberRow(_ member: ClassMember) -> some View { HStack(spacing: 12) { PickeAvatarView(imageURL: nil, fallback: member.name, size: 36) @@ -118,7 +142,7 @@ private extension ClassMemberView { filterSection( title: "참여 상태", detail: "제공 예정", - options: ClassMemberFilterFeature.Participation.allCases.filter { $0 != .all }, + options: ClassMemberParticipation.allCases.filter { $0 != .all }, selected: { store.participation == $0 }, action: { store.send(.participationSelected($0)) }, enabled: { _ in false } @@ -126,7 +150,7 @@ private extension ClassMemberView { filterSection( title: "미완료", detail: "제공 예정", - options: ClassMemberFilterFeature.Completion.allCases.filter { $0 != .all }, + options: ClassMemberCompletion.allCases.filter { $0 != .all }, selected: { store.completion == $0 }, action: { store.send(.completionSelected($0)) }, enabled: { _ in false } @@ -134,7 +158,7 @@ private extension ClassMemberView { filterSection( title: "입장 변화", detail: "제공 예정", - options: ClassMemberFilterFeature.Attendance.allCases.filter { $0 != .all }, + options: ClassMemberAttendance.allCases.filter { $0 != .all }, selected: { store.attendance == $0 }, action: { store.send(.attendanceSelected($0)) }, enabled: { _ in false } @@ -142,7 +166,7 @@ private extension ClassMemberView { filterSection( title: "정렬", detail: "이름순만 제공", - options: ClassMemberFilterFeature.Sort.allCases, + options: ClassMemberSort.allCases, selected: { store.sort == $0 }, action: { store.send(.sortSelected($0)) }, enabled: { $0 == .name } diff --git a/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift index f4b0604b..da68fc99 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDashboard/Reducer/ClassOwnerDashboardFeature.swift @@ -8,49 +8,117 @@ public struct ClassOwnerDashboardFeature { @ObservableState public struct State: Equatable { + public struct PreviewMetrics: Equatable { + public let commentAuthorCount: Int + public let optionAVoteCount: Int + public let optionBVoteCount: Int + + public init(commentAuthorCount: Int, optionAVoteCount: Int, optionBVoteCount: Int) { + self.commentAuthorCount = commentAuthorCount + self.optionAVoteCount = optionAVoteCount + self.optionBVoteCount = optionBVoteCount + } + } + + public enum MemberViewState: Equatable { + case unavailable + case noSearchResults + case loaded([ClassOwnerMember]) + } + + public enum OpinionViewState: Equatable { + case empty + case loaded([ClassOwnerOpinion]) + } + + public enum TopOpinionViewState: Equatable { + case empty + case unavailable + case loaded([ClassOwnerOpinion]) + } + + public enum MemberSummaryState: Equatable { + case unavailable + case empty + case loaded([ClassOwnerMember]) + } + public let room: ClassRoom public var selectedTab: ClassOwnerTab = .home - public var opinionFilter: ClassOwnerOpinionFilter = .all + public var opinionFilter: ClassOwnerOpinionFilter = .popular public var memberSort: ClassOwnerMemberSort = .name + public var memberFilter: MemberFilter = .all public var searchText = "" - public var members: [ClassOwnerMember] - public var opinions: [ClassOwnerOpinion] + public var members: [ClassOwnerMember] = [] + public var opinions: [ClassOwnerOpinion] = [] + #if DEBUG + public var usesPreviewContent = false + public var previewMetrics: PreviewMetrics? + #endif public init(room: ClassRoom) { self.room = room - members = [ - ClassOwnerMember(id: 1, name: "김민지", participationCount: 8, needsReview: true), - ClassOwnerMember(id: 2, name: "이서준", participationCount: 7), - ClassOwnerMember(id: 3, name: "박지우", participationCount: 6), - ClassOwnerMember(id: 4, name: "최유진", participationCount: 5), - ClassOwnerMember(id: 5, name: "정다은", participationCount: 4), - ClassOwnerMember(id: 6, name: "한도윤", participationCount: 3, needsReview: true), - ] - opinions = [ - ClassOwnerOpinion( - id: 1, - author: "김민지", - text: "피해자 보호를 위한 기준도 함께 필요하지 않을까요?", - replyCount: 23 - ), - ClassOwnerOpinion( - id: 2, - author: "이서준", - text: "제도화가 선택을 의무로 바꾸지 않도록 기준이 필요해요.", - replyCount: 12 - ), - ClassOwnerOpinion( - id: 3, - author: "박지우", - text: "교육과 보호도 함께 고려해야 한다고 생각합니다.", - replyCount: 4 - ), - ] } - public var visibleMembers: [ClassOwnerMember] { + #if DEBUG + public static func preview(room: ClassRoom, tab: ClassOwnerTab = .home) -> Self { + var state = Self(room: room) + state.selectedTab = tab + state.usesPreviewContent = true + state.previewMetrics = PreviewMetrics(commentAuthorCount: 24, optionAVoteCount: 18, optionBVoteCount: 10) + state.members = [ + ClassOwnerMember(id: 1, name: "김민지", participationCount: 8, needsReview: true), + ClassOwnerMember(id: 2, name: "이서준", participationCount: 7), + ClassOwnerMember(id: 3, name: "박지우", participationCount: 6), + ClassOwnerMember(id: 4, name: "최유진", participationCount: 0), + ClassOwnerMember(id: 5, name: "정다은", participationCount: 0), + ClassOwnerMember(id: 6, name: "한도윤", participationCount: 0, needsReview: true), + ] + state.members += (7 ... 32).map { id in + ClassOwnerMember(id: id, name: "희망 \(id)", participationCount: id < 32 ? 1 : 0) + } + state.opinions = [ + ClassOwnerOpinion( + id: 1, + author: "김민지", + text: "피해자 보호를 위한 기준도 함께 필요하지 않을까요?", + replyCount: 23, + recommendationCount: 1340, + createdAt: Date(timeIntervalSince1970: 1_780_000_300) + ), + ClassOwnerOpinion( + id: 2, + author: "이서준", + text: "제도화가 선택을 의무로 바꾸지 않도록 기준이 필요해요.", + replyCount: 12, + recommendationCount: 840, + createdAt: Date(timeIntervalSince1970: 1_780_000_200) + ), + ClassOwnerOpinion( + id: 3, + author: "박지우", + text: "교육과 보호도 함께 고려해야 한다고 생각합니다.", + replyCount: 4, + recommendationCount: 510, + createdAt: Date(timeIntervalSince1970: 1_780_000_100) + ), + ] + return state + } + #endif + + public var searchedMembers: [ClassOwnerMember] { let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) - let matching = query.isEmpty ? members : members.filter { $0.name.localizedCaseInsensitiveContains(query) } + return query.isEmpty ? members : members.filter { $0.name.localizedCaseInsensitiveContains(query) } + } + + public var visibleMembers: [ClassOwnerMember] { + let searched = searchedMembers + let matching: [ClassOwnerMember] = switch memberFilter { + case .all: searched + case .participated: searched.filter { $0.participationCount > 0 } + case .notParticipated: searched.filter { $0.participationCount == 0 } + } switch memberSort { case .name: return matching.sorted { $0.name < $1.name } @@ -62,15 +130,68 @@ public struct ClassOwnerDashboardFeature { } public var visibleOpinions: [ClassOwnerOpinion] { - switch opinionFilter { - case .all, .inProgress: - return opinions - case .recommended: - return opinions.sorted { $0.replyCount > $1.replyCount } - case .replies: - return opinions.sorted { $0.replyCount > $1.replyCount } + guard let activeOpinionFilter else { return opinions } + switch activeOpinionFilter { + case .latest: + return opinions.sorted { ($0.createdAt ?? .distantPast) > ($1.createdAt ?? .distantPast) } + case .popular: + return opinions.sorted { ($0.recommendationCount ?? 0) > ($1.recommendationCount ?? 0) } } } + + public var activeOpinionFilter: ClassOwnerOpinionFilter? { + canSortOpinions(opinionFilter) ? opinionFilter : nil + } + + public var topOpinionViewState: TopOpinionViewState { + guard !opinions.isEmpty else { return .empty } + guard canSortOpinions(.popular) else { return .unavailable } + let top = Array(opinions.sorted { ($0.recommendationCount ?? 0) > ($1.recommendationCount ?? 0) }.prefix(2)) + return .loaded(top) + } + + public var memberViewState: MemberViewState { + guard !members.isEmpty else { return .unavailable } + let visible = visibleMembers + return visible.isEmpty ? .noSearchResults : .loaded(visible) + } + + public var needsReviewViewState: MemberSummaryState { + guard !members.isEmpty else { return .unavailable } + let matching = members.filter(\.needsReview) + return matching.isEmpty ? .empty : .loaded(matching) + } + + public var highParticipationViewState: MemberSummaryState { + guard !members.isEmpty else { return .unavailable } + let matching = Array(members.filter { $0.participationCount > 0 } + .sorted { $0.participationCount > $1.participationCount } + .prefix(3)) + return matching.isEmpty ? .empty : .loaded(matching) + } + + public var opinionViewState: OpinionViewState { + let visible = visibleOpinions + return visible.isEmpty ? .empty : .loaded(visible) + } + + public func canSortOpinions(_ filter: ClassOwnerOpinionFilter) -> Bool { + guard !opinions.isEmpty else { return false } + switch filter { + case .popular: return opinions.allSatisfy { $0.recommendationCount != nil } + case .latest: return opinions.allSatisfy { $0.createdAt != nil } + } + } + + public var availableOpinionFilters: Set { + Set(ClassOwnerOpinionFilter.allCases.filter(canSortOpinions)) + } + } + + public enum MemberFilter: CaseIterable, Hashable, Sendable { + case all + case participated + case notParticipated } public enum Action: ViewAction { @@ -83,6 +204,7 @@ public struct ClassOwnerDashboardFeature { case tabSelected(ClassOwnerTab) case opinionFilterSelected(ClassOwnerOpinionFilter) case memberSortSelected(ClassOwnerMemberSort) + case memberFilterSelected(MemberFilter) case searchTextChanged(String) case memberTapped(Int) case opinionTapped(Int) @@ -106,9 +228,13 @@ public struct ClassOwnerDashboardFeature { case let .tabSelected(tab): state.selectedTab = tab case let .opinionFilterSelected(filter): + guard state.canSortOpinions(filter) else { return .none } state.opinionFilter = filter case let .memberSortSelected(sort): state.memberSort = sort + case let .memberFilterSelected(filter): + guard !state.members.isEmpty || filter == .all else { return .none } + state.memberFilter = filter case let .searchTextChanged(text): state.searchText = text case let .memberTapped(id): diff --git a/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift b/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift index 62adea46..1f389da7 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDashboard/View/ClassOwnerDashboardView.swift @@ -1,8 +1,14 @@ import ClassDomainInterface import ComposableArchitecture +import Foundation import PickeDesignKit import SwiftUI +private enum MemberRowAction: String { + case detail = "상세" + case feedback = "피드백" +} + @ViewAction(for: ClassOwnerDashboardFeature.self) public struct ClassOwnerDashboardView: View { @Bindable public var store: StoreOf @@ -17,7 +23,6 @@ public struct ClassOwnerDashboardView: View { .foregroundStyle(.gray800) titleHeader() - summaryHeader() dashboardTabs() TabView(selection: Binding( get: { store.selectedTab }, @@ -39,52 +44,46 @@ private extension ClassOwnerDashboardView { @ViewBuilder func titleHeader() -> some View { VStack(alignment: .leading, spacing: 12) { - Text("예시 데이터") - .pretendardFont(.medium13) - .foregroundStyle(.gray300) + HStack(spacing: 8) { + Text(store.room.status == .open ? "진행 중" : "종료") + .foregroundStyle(.primary500) + Text("#\(store.room.battle.category.title)") + .foregroundStyle(.gray500) + } + .pretendardFont(.medium13) Text(store.room.battle.title) .pretendardFont(.semiBold24) .foregroundStyle(.gray800) .lineLimit(2) .frame(maxWidth: .infinity, alignment: .leading) - Text("참여 멤버 \(store.room.memberCount)명") + Text("\(Self.deadlineFormatter.string(from: store.room.deadline))까지") .pretendardFont(.medium13) .foregroundStyle(.gray500) } .padding(.horizontal, 16) - .frame(height: 166, alignment: .center) + .frame(height: 118, alignment: .center) } - @ViewBuilder - func summaryHeader() -> some View { - VStack(spacing: 10) { - HStack { - Text("플라톤") - Text("59.5%") - Spacer() - Text("찬성") - Text("반대 40.5%") - } - .pretendardFont(family: .Medium, size: 12) - .foregroundStyle(.gray500) - GeometryReader { proxy in - HStack(spacing: 0) { - RoundedRectangle(cornerRadius: 3).fill(.primary500).frame(width: proxy.size.width * 0.595) - RoundedRectangle(cornerRadius: 3).fill(.secondary500) - } - } - .frame(height: 6) - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - } + static let deadlineFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.dateFormat = "yyyy. MM. dd. EEEE HH:mm" + return formatter + }() + + static let opinionDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_KR") + formatter.dateFormat = "yy.MM.dd. HH:mm" + return formatter + }() @ViewBuilder func dashboardTabs() -> some View { HStack(spacing: 0) { ForEach(ClassOwnerTab.allCases, id: \.self) { tab in Button { send(.tabSelected(tab)) } label: { - Text(tab.rawValue) + Text(tab == .home ? "요약" : tab.rawValue) .pretendardFont(family: .Medium, size: 13) .foregroundStyle(store.selectedTab == tab ? .primary500 : .gray300) .frame(maxWidth: .infinity) @@ -102,30 +101,102 @@ private extension ClassOwnerDashboardView { @ViewBuilder func homePage() -> some View { - VStack(alignment: .leading, spacing: 16) { - dashboardSection("클래스 참여 현황", trailing: "총 47회 참여") { - HStack(spacing: 8) { - metric("참여 학생", value: "28명") - metric("평균 참여", value: "59.5%") - metric("미참여", value: "4명") + VStack(alignment: .leading, spacing: 24) { + dashboardSection("클래스 참여 현황") { + if let metrics = previewMetrics { + let total = store.members.count + let participated = store.members.filter { $0.participationCount > 0 }.count + VStack(spacing: 12) { + participationRow( + "배틀 참여율", + value: percentage(participated, of: total), + detail: "참여한 멤버", + count: "\(participated) / \(total)명", + progress: ratio(participated, of: total) + ) + participationRow( + "댓글 작성율", + value: percentage(metrics.commentAuthorCount, of: total), + detail: "댓글을 남긴 멤버", + count: "\(metrics.commentAuthorCount) / \(total)명", + progress: ratio(metrics.commentAuthorCount, of: total) + ) + } + .frame(height: 152) + .padding(.horizontal, 17) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } else { + unavailableContent("참여 현황을 불러올 수 없어요") } } - dashboardSection("입장 분포", trailing: "총 47회 참여") { - HStack(spacing: 10) { - distribution("찬성", value: "59.5%", color: .primary500) - distribution("반대", value: "40.5%", color: .secondary500) + dashboardSection("입장 분포") { + if let metrics = previewMetrics { + let total = metrics.optionAVoteCount + metrics.optionBVoteCount + HStack(spacing: 20) { + ZStack { + Circle().stroke(.beige600, lineWidth: 11) + Circle() + .trim(from: 0, to: ratio(metrics.optionAVoteCount, of: total)) + .stroke(.primary500, style: StrokeStyle(lineWidth: 11, lineCap: .round)) + .rotationEffect(.degrees(-90)) + VStack(spacing: 0) { + Text("\(total)명").pretendardFont(.semiBold24) + Text("총 참여").pretendardFont(.regular10) + } + } + .frame(width: 92, height: 92) + VStack(alignment: .leading, spacing: 6) { + Text( + "\(store.room.battle.optionATitle) \(metrics.optionAVoteCount)명 · \(roundedPercentage(metrics.optionAVoteCount, of: total))" + ) + Text( + "\(store.room.battle.optionBTitle) \(metrics.optionBVoteCount)명 · \(roundedPercentage(metrics.optionBVoteCount, of: total))" + ) + } + .pretendardFont(.regular13) + } + .foregroundStyle(.gray700) + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(minHeight: 134) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } else { + unavailableContent("입장 분포를 불러올 수 없어요") } } - dashboardSection("확인이 필요한 멤버", trailing: "총 47회 참여") { - ForEach(store.members.filter(\.needsReview)) { memberRow($0, action: "피드백") } + dashboardSection("확인이 필요한 멤버") { + switch store.needsReviewViewState { + case .unavailable: + unavailableContent("멤버 활동 정보가 없어요") + case .empty: + unavailableContent("확인이 필요한 멤버가 없어요") + case let .loaded(members): + ForEach(members) { memberRow($0, action: .feedback) } + } } - dashboardSection("참여도가 높은 멤버", trailing: "총 47회 참여") { - ForEach(store.members.sorted { $0.participationCount > $1.participationCount }.prefix(2)) { member in - memberRow(member, action: "상세") + dashboardSection("참여도가 높은 멤버") { + switch store.highParticipationViewState { + case .unavailable: + unavailableContent("멤버 활동 정보가 없어요") + case .empty: + unavailableContent("아직 참여한 멤버가 없어요") + case let .loaded(members): + ForEach(members) { member in + memberRow(member, action: .detail) + } } } - dashboardSection("주요 의견", trailing: "총 47회 참여") { - ForEach(store.opinions.prefix(2)) { opinion in opinionRow(opinion) } + dashboardSection("주요 의견") { + switch store.topOpinionViewState { + case .empty: + unavailableContent("등록된 의견이 없어요") + case .unavailable: + unavailableContent("주요 의견을 집계할 수 없어요") + case let .loaded(opinions): + ForEach(opinions) { opinion in opinionRow(opinion) } + } } } } @@ -133,16 +204,34 @@ private extension ClassOwnerDashboardView { @ViewBuilder func membersPage() -> some View { VStack(alignment: .leading, spacing: 12) { - Text("멤버 \(store.room.memberCount)") - .pretendardFont(.semiBold15).foregroundStyle(.gray800) - TextField("멤버 검색", text: Binding( - get: { store.searchText }, - set: { send(.searchTextChanged($0)) } - )) - .pretendardFont(.regular13) - .padding(.horizontal, 12).frame(height: 40) + HStack(spacing: 8) { + Image(systemName: "magnifyingglass") + .foregroundStyle(.gray500) + TextField("이름을 입력해주세요.", text: Binding( + get: { store.searchText }, + set: { send(.searchTextChanged($0)) } + )) + .pretendardFont(.regular13) + } + .padding(.horizontal, 12) + .frame(height: 44) .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(ClassOwnerDashboardFeature.MemberFilter.allCases, id: \.self) { filter in + Button { send(.memberFilterSelected(filter)) } label: { + Text(memberFilterTitle(filter)) + .pickeSortChip(isSelected: store.memberFilter == filter) + } + .buttonStyle(.plain) + .disabled(store.members.isEmpty && filter != .all) + .opacity(store.members.isEmpty && filter != .all ? 0.5 : 1) + } + } + } + Text(store.members.isEmpty ? "클래스 멤버 \(store.room.memberCount)" : "멤버 \(store.members.count)") + .pretendardFont(.semiBold15).foregroundStyle(.gray800) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(ClassOwnerMemberSort.allCases, id: \.self) { sort in @@ -153,8 +242,15 @@ private extension ClassOwnerDashboardView { } } } - ForEach(store.visibleMembers) { member in - memberRow(member, action: member.needsReview ? "피드백" : "상세") + switch store.memberViewState { + case .unavailable: + unavailableContent("멤버 활동 정보가 없어요") + case .noSearchResults: + unavailableContent("검색 결과가 없어요") + case let .loaded(members): + ForEach(members) { member in + memberRow(member, action: member.needsReview ? .feedback : .detail) + } } } } @@ -163,7 +259,12 @@ private extension ClassOwnerDashboardView { func opinionsPage() -> some View { VStack(alignment: .leading, spacing: 12) { opinionFilters() - ForEach(store.visibleOpinions) { opinion in opinionRow(opinion) } + switch store.opinionViewState { + case .empty: + unavailableContent("등록된 의견이 없어요") + case let .loaded(opinions): + ForEach(opinions) { opinion in opinionRow(opinion) } + } } } @@ -173,8 +274,11 @@ private extension ClassOwnerDashboardView { HStack(spacing: 8) { ForEach(ClassOwnerOpinionFilter.allCases, id: \.self) { filter in Button { send(.opinionFilterSelected(filter)) } label: { - Text(filter.rawValue).pickeSortChip(isSelected: store.opinionFilter == filter) - }.buttonStyle(.plain) + Text(filter.rawValue).pickeSortChip(isSelected: store.activeOpinionFilter == filter) + } + .buttonStyle(.plain) + .disabled(!store.availableOpinionFilters.contains(filter)) + .opacity(store.availableOpinionFilters.contains(filter) ? 1 : 0.5) } } } @@ -183,57 +287,119 @@ private extension ClassOwnerDashboardView { @ViewBuilder func dashboardSection( _ title: String, - trailing: String, @ViewBuilder content: () -> some View ) -> some View { - VStack(alignment: .leading, spacing: 10) { - HStack { - Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) - Spacer() - Text(trailing).pretendardFont(.regular10).foregroundStyle(.gray300) - } + VStack(alignment: .leading, spacing: 12) { + Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) content() } - .padding(12) .frame(maxWidth: .infinity, alignment: .leading) - .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) - .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) } - @ViewBuilder - func metric(_ title: String, value: String) -> some View { - VStack(alignment: .leading, spacing: 6) { - Text(title).pretendardFont(.regular10).foregroundStyle(.gray300) - Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) + var usesPreviewContent: Bool { + #if DEBUG + store.usesPreviewContent + #else + false + #endif + } + + var previewMetrics: ClassOwnerDashboardFeature.State.PreviewMetrics? { + #if DEBUG + store.previewMetrics + #else + nil + #endif + } + + func ratio(_ count: Int, of total: Int) -> CGFloat { + guard total > 0 else { return 0 } + return CGFloat(count) / CGFloat(total) + } + + func percentage(_ count: Int, of total: Int) -> String { + guard total > 0 else { return "0%" } + return "\((Double(count) / Double(total) * 100).formatted(.number.precision(.fractionLength(0 ... 1))))%" + } + + func roundedPercentage(_ count: Int, of total: Int) -> String { + guard total > 0 else { return "0%" } + return "\(Int((Double(count) / Double(total) * 100).rounded()))%" + } + + func memberFilterTitle(_ filter: ClassOwnerDashboardFeature.MemberFilter) -> String { + switch filter { + case .all: return "전체" + case .participated: + return store.members.isEmpty + ? "참여 완료" + : "참여 완료 \(store.searchedMembers.filter { $0.participationCount > 0 }.count)" + case .notParticipated: + return store.members.isEmpty + ? "미참여" + : "미참여 \(store.searchedMembers.filter { $0.participationCount == 0 }.count)" } - .frame(maxWidth: .infinity, alignment: .leading) } - @ViewBuilder - func distribution(_ title: String, value: String, color: Color) -> some View { - VStack(alignment: .leading, spacing: 6) { - HStack { Circle().fill(color).frame(width: 7, height: 7); Text(title); Spacer(); Text(value) } - .pretendardFont(.regular13).foregroundStyle(.gray500) - ProgressView(value: title == "찬성" ? 0.595 : 0.405).tint(color) - }.frame(maxWidth: .infinity) + func unavailableContent(_ message: String) -> some View { + Text(message) + .pretendardFont(.regular13) + .foregroundStyle(.gray500) + .frame(maxWidth: .infinity) + .frame(minHeight: 88) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + + func participationRow(_ title: String, value: String, detail: String, count: String, progress: CGFloat) -> some View { + VStack(spacing: 4) { + HStack { + Text(title).pretendardFont(.medium13) + Spacer() + Text(value).pretendardFont(.semiBold15) + } + HStack { + Text(detail) + Spacer() + Text(count) + } + .pretendardFont(.regular10) + .foregroundStyle(.gray500) + GeometryReader { geometry in + ZStack(alignment: .leading) { + Capsule().fill(.beige600) + Capsule().fill(.primary500).frame(width: geometry.size.width * progress) + } + } + .frame(height: 6) + } + .foregroundStyle(.gray800) + .frame(height: 51) } @ViewBuilder - func memberRow(_ member: ClassOwnerMember, action: String) -> some View { - Button { - send(member.needsReview ? .feedbackTapped(member.id) : .memberTapped(member.id)) - } label: { - HStack(spacing: 10) { - Text(String(member.name.prefix(1))).pickeAvatarFallback(size: 32).pickeAvatar(size: 32) - VStack(alignment: .leading, spacing: 2) { - Text(member.name).pretendardFont(.medium13).foregroundStyle(.gray700) - Text("배틀 참여 \(member.participationCount)회").pretendardFont(.regular10).foregroundStyle(.gray300) + func memberRow(_ member: ClassOwnerMember, action: MemberRowAction) -> some View { + HStack(spacing: 10) { + Button { send(.memberTapped(member.id)) } label: { + HStack(spacing: 10) { + Text(String(member.name.prefix(1))).pickeAvatarFallback(size: 32).pickeAvatar(size: 32) + VStack(alignment: .leading, spacing: 2) { + Text(member.name).pretendardFont(.medium13).foregroundStyle(.gray700) + Text("배틀 참여 \(member.participationCount)회").pretendardFont(.regular10).foregroundStyle(.gray300) + } + Spacer() } - Spacer() - Text(action).pretendardFont(family: .Bold, size: 12).foregroundStyle(.primary500) } - .padding(.vertical, 7) - }.buttonStyle(.plain) + .buttonStyle(.plain) + + Button { + send(action == .feedback ? .feedbackTapped(member.id) : .memberTapped(member.id)) + } label: { + Text(action.rawValue).pretendardFont(family: .Bold, size: 12).foregroundStyle(.primary500) + } + .buttonStyle(.plain) + } + .padding(.vertical, 7) } @ViewBuilder @@ -241,8 +407,24 @@ private extension ClassOwnerDashboardView { Button { send(.opinionTapped(opinion.id)) } label: { VStack(alignment: .leading, spacing: 7) { HStack { - Text(opinion.author).pretendardFont(.medium13).foregroundStyle(.gray500) + Text(String(opinion.author.prefix(1))).pickeAvatarFallback(size: 36).pickeAvatar(size: 36) + VStack(alignment: .leading, spacing: 2) { + Text(opinion.author).pretendardFont(.medium13).foregroundStyle(.gray500) + if usesPreviewContent, let createdAt = opinion.createdAt { + Text(Self.opinionDateFormatter.string(from: createdAt)) + .pretendardFont(.regular10) + .foregroundStyle(.gray300) + } + } Spacer() + if usesPreviewContent { + Text(store.room.battle.optionATitle) + .pretendardFont(.regular10) + .foregroundStyle(.primary500) + .padding(.horizontal, 4) + .frame(height: 21) + .background(.beige600, in: RoundedRectangle(cornerRadius: 2)) + } if opinion.isReported { Text("신고").pretendardFont(.regular10).foregroundStyle(.errorDefault) } @@ -251,7 +433,18 @@ private extension ClassOwnerDashboardView { .pretendardFont(.regular13) .foregroundStyle(.gray700) .multilineTextAlignment(.leading) - Text("대댓글 \(opinion.replyCount)개").pretendardFont(.regular10).foregroundStyle(.gray300) + .lineLimit(2) + Text("자세히 보기") + .pretendardFont(.regular10) + .foregroundStyle(.gray500) + HStack(spacing: 16) { + if let recommendationCount = opinion.recommendationCount { + Text("추천 \(recommendationCount.formatted())") + } + Text("대댓글 \(opinion.replyCount)") + } + .pretendardFont(.regular10) + .foregroundStyle(.gray300) } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) @@ -260,3 +453,23 @@ private extension ClassOwnerDashboardView { }.buttonStyle(.plain) } } + +#if DEBUG + #Preview("운영자 대시보드 · 요약") { + ClassOwnerDashboardView(store: Store(initialState: .preview(room: ClassRoom.mocks[0])) { + ClassOwnerDashboardFeature() + }) + } + + #Preview("운영자 대시보드 · 멤버") { + ClassOwnerDashboardView(store: Store(initialState: .preview(room: ClassRoom.mocks[0], tab: .members)) { + ClassOwnerDashboardFeature() + }) + } + + #Preview("운영자 대시보드 · 의견") { + ClassOwnerDashboardView(store: Store(initialState: .preview(room: ClassRoom.mocks[0], tab: .opinions)) { + ClassOwnerDashboardFeature() + }) + } +#endif diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift index 569f389a..0ee8eb1e 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassMemberDetailFeature.swift @@ -8,26 +8,55 @@ public struct ClassMemberDetailFeature { @ObservableState public struct State: Equatable { public let room: ClassRoom - public let memberName: String - public let participationRate: String - public let commentCount: Int - public let replyCount: Int - public let receivedLikeCount: Int + public let member: ClassOwnerMember + public let activity: Activity? + + public struct Activity: Equatable, Sendable { + public let initialStance: String + public let finalStance: String + public let commentCount: Int + public let replyCount: Int + public let receivedLikeCount: Int + public let comment: String? + public let commentOpinion: ClassOwnerOpinion? + public let reply: String? + public let feedback: String? + + public var changedStance: Bool { + initialStance != finalStance + } + + public init( + initialStance: String, + finalStance: String, + commentCount: Int, + replyCount: Int, + receivedLikeCount: Int, + comment: String? = nil, + commentOpinion: ClassOwnerOpinion? = nil, + reply: String? = nil, + feedback: String? = nil + ) { + self.initialStance = initialStance + self.finalStance = finalStance + self.commentCount = commentCount + self.replyCount = replyCount + self.receivedLikeCount = receivedLikeCount + self.comment = comment + self.commentOpinion = commentOpinion + self.reply = reply + self.feedback = feedback + } + } public init( room: ClassRoom, - memberName: String = "플라톤", - participationRate: String = "59.5%", - commentCount: Int = 1, - replyCount: Int = 12, - receivedLikeCount: Int = 8 + member: ClassOwnerMember, + activity: Activity? = nil ) { self.room = room - self.memberName = memberName - self.participationRate = participationRate - self.commentCount = commentCount - self.replyCount = replyCount - self.receivedLikeCount = receivedLikeCount + self.member = member + self.activity = activity } } @@ -40,29 +69,26 @@ public struct ClassMemberDetailFeature { public enum View: Equatable { case backTapped case commentTapped - case replyTapped case feedbackTapped } @CasePathable public enum DelegateAction: Equatable { case dismiss - case commentSelected - case replySelected + case commentSelected(ClassOwnerOpinion) case feedbackSelected } public var body: some Reducer { - Reduce { _, action in + Reduce { state, action in switch action { case let .view(viewAction): switch viewAction { case .backTapped: return .send(.delegate(.dismiss)) case .commentTapped: - return .send(.delegate(.commentSelected)) - case .replyTapped: - return .send(.delegate(.replySelected)) + guard let opinion = state.activity?.commentOpinion else { return .none } + return .send(.delegate(.commentSelected(opinion))) case .feedbackTapped: return .send(.delegate(.feedbackSelected)) } diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift index 7ba74bd5..9a6b35f9 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/Reducer/ClassReplyDetailFeature.swift @@ -7,7 +7,14 @@ public struct ClassReplyDetailFeature { @ObservableState public struct State: Equatable { + public enum ViewState: Equatable { + case unavailable + case empty + case loaded([Reply]) + } + public let room: ClassRoom + public let opinion: ClassOwnerOpinion? public let replies: [Reply] public struct Reply: Equatable, Identifiable, Sendable { @@ -24,9 +31,15 @@ public struct ClassReplyDetailFeature { } } - public init(room: ClassRoom, replies: [Reply]? = nil) { + public init(room: ClassRoom, opinion: ClassOwnerOpinion? = nil, replies: [Reply] = []) { self.room = room - self.replies = replies ?? Reply.mocks + self.opinion = opinion + self.replies = replies + } + + public var viewState: ViewState { + guard replies.isEmpty else { return .loaded(replies) } + return opinion?.replyCount == 0 ? .empty : .unavailable } } @@ -56,11 +69,3 @@ public struct ClassReplyDetailFeature { } } } - -private extension ClassReplyDetailFeature.State.Reply { - static let mocks = [ - Self(id: 1, author: "김민지", body: "피해자 보호를 위한 기준도 함께 필요하지 않을까요?", date: "26.09.23. 14:02"), - Self(id: 2, author: "플라톤", body: "교육과 보호도 함께 필요하다고 생각해요.", date: "26.09.23. 14:02"), - Self(id: 3, author: "이도윤", body: "처벌만으로는 문제를 해결하기 어렵다고 느꼈어요.", date: "26.09.23. 14:03"), - ] -} diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift index 899d1efd..c446f901 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassFeedbackComposeView.swift @@ -15,12 +15,6 @@ public struct ClassFeedbackComposeView: View { VStack(spacing: 0) { PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) .foregroundStyle(.gray800) - Text("예시 데이터") - .pretendardFont(.medium10) - .foregroundStyle(.gray300) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - ScrollView { VStack(alignment: .leading, spacing: 16) { HStack(spacing: 12) { @@ -73,9 +67,6 @@ private extension ClassFeedbackComposeView { .pretendardFont(family: .SemiBold, size: 13) .foregroundStyle(.gray800) Spacer() - Text("총 47회 참여") - .pretendardFont(family: .Medium, size: 10) - .foregroundStyle(.gray300) } TextEditor(text: $store.feedback) .pretendardFont(.regular13) diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift index fabbb13a..2ff7cced 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassMemberDetailView.swift @@ -1,3 +1,4 @@ +import ClassDomainInterface import ComposableArchitecture import PickeDesignKit import PickeSharedUI @@ -5,7 +6,7 @@ import SwiftUI @ViewAction(for: ClassMemberDetailFeature.self) public struct ClassMemberDetailView: View { - public let store: StoreOf + @Bindable public var store: StoreOf public init(store: StoreOf) { self.store = store @@ -15,17 +16,12 @@ public struct ClassMemberDetailView: View { VStack(spacing: 0) { PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) .foregroundStyle(.gray800) - Text("예시 데이터") - .pretendardFont(.medium10) - .foregroundStyle(.gray300) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - ScrollView { - VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 24) { memberHeader summarySection - activitySection + commentSection + replySection feedbackSection } .padding(16) @@ -41,148 +37,179 @@ public struct ClassMemberDetailView: View { private extension ClassMemberDetailView { var memberHeader: some View { HStack(spacing: 12) { - PickeAvatarView(imageURL: nil, fallback: store.memberName, size: 40) + PickeAvatarView(imageURL: nil, fallback: store.member.name, size: 40) VStack(alignment: .leading, spacing: 4) { - Text(store.memberName) - .pretendardFont(family: .SemiBold, size: 16) + Text(store.member.name) + .pretendardFont(.semiBold15) .foregroundStyle(.gray800) - Text("클래스 멤버") - .pretendardFont(family: .Medium, size: 12) - .foregroundStyle(.gray300) + if let activity = store.activity { + Text("댓글 \(activity.commentCount) · 대댓글 \(activity.replyCount)") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } } Spacer() - } - .padding(.vertical, 12) - .overlay(alignment: .bottom) { - Rectangle().fill(.beige600).frame(height: 1) + if store.member.participationCount > 0 { + Text("참여 완료").pickeBadge(.filled, size: .tag) + } } } var summarySection: some View { - detailSection("참여 요약", trailing: "총 47회 참여") { - HStack(spacing: 0) { - metric(value: store.participationRate, label: "참여율") - separator - metric(value: "28회", label: "배틀 참여") - separator - metric(value: "19회", label: "미참여") + section("참여 요약") { + if let activity = store.activity { + HStack(spacing: 8) { + stance(activity.initialStance, label: "사전 선택") + VStack(spacing: 4) { + Image(systemName: activity.changedStance ? "arrow.right" : "equal") + Text(activity.changedStance ? "입장 변경" : "입장 유지") + } + .pretendardFont(family: .Medium, size: 10) + .foregroundStyle(.gray300) + stance(activity.finalStance, label: "사후 선택") + } + HStack(spacing: 0) { + metric(activity.commentCount, label: "댓글") + Rectangle().fill(.beige600).frame(width: 1, height: 36) + metric(activity.replyCount, label: "대댓글") + Rectangle().fill(.beige600).frame(width: 1, height: 36) + metric(activity.receivedLikeCount, label: "받은 좋아요") + } + .padding(.vertical, 12) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + } else { + emptyCard("참여 기록이 아직 없어요.") } - .padding(.vertical, 12) } } - var activitySection: some View { - VStack(spacing: 16) { - detailSection("남긴 댓글", trailing: "총 47회 참여") { - Button { send(.commentTapped) } label: { - activityCard( - title: "낮춰야 한다", - body: "제도화가 무서운 건, 사회적 압력이 선택을 의무로 바꿀 수 있다는 거예요.", - icon: "chevron.right" - ) + var commentSection: some View { + section("남긴 댓글") { + if let comment = store.activity?.comment { + if store.activity?.commentOpinion != nil { + Button { send(.commentTapped) } label: { activityCard(comment, showsChevron: true) } + .buttonStyle(.plain) + } else { + activityCard(comment) } - .buttonStyle(.plain) + } else { + emptyCard("남긴 댓글이 없어요.") } + } + } - detailSection("남긴 대댓글", trailing: "총 47회 참여") { - Button { send(.replyTapped) } label: { - VStack(alignment: .leading, spacing: 8) { - Text("토론을 들으면서 처벌만으로는 해결되지 않는 점을 이해하게 됐어요.") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - .multilineTextAlignment(.leading) - Text("26.09.23. 14:02") - .pretendardFont(family: .SemiBold, size: 10) - .foregroundStyle(.gray300) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(12) - .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) - .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) - } - .buttonStyle(.plain) + var replySection: some View { + section("남긴 대댓글") { + if let reply = store.activity?.reply { + activityCard(reply) + } else { + emptyCard("남긴 대댓글이 없어요.") } } } var feedbackSection: some View { - detailSection("운영자 피드백", trailing: "총 47회 참여") { + section("운영자 피드백") { VStack(alignment: .leading, spacing: 12) { - Text("처음에는 처벌 기준에 집중했지만, 토론 후 다른 관점을 받아들이고 자신의 생각을 바꾼 과정이 잘 드러났어요.") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - .lineLimit(3) + if let feedback = store.activity?.feedback { + Text(feedback) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + } else { + Text("아직 작성된 피드백이 없어요.") + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + Text("참여 기록과 의견을 확인한 뒤 개별 피드백을 남겨주세요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } Button { send(.feedbackTapped) } label: { - Text("피드백 작성") + Text(store.activity?.feedback == nil ? "피드백 작성하기" : "피드백 수정하기") .frame(maxWidth: .infinity) } .buttonStyle(.plain) .ctaButtonStyle(.primary, size: .medium, height: 42) } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) } } - @ViewBuilder - func detailSection( - _ title: String, - trailing: String, - @ViewBuilder content: () -> some View - ) -> some View { + func section(_ title: String, @ViewBuilder content: () -> some View) -> some View { VStack(alignment: .leading, spacing: 12) { - HStack { - Text(title) - .pretendardFont(family: .SemiBold, size: 13) - .foregroundStyle(.gray800) - Spacer() - Text(trailing) - .pretendardFont(family: .Medium, size: 10) - .foregroundStyle(.gray300) - } + Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) content() } - .padding(12) .frame(maxWidth: .infinity, alignment: .leading) - .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) - .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) } - @ViewBuilder - func metric(value: String, label: String) -> some View { - VStack(spacing: 2) { - Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) + func stance(_ value: String, label: String) -> some View { + VStack(alignment: .leading, spacing: 8) { Text(label).pretendardFont(.regular13).foregroundStyle(.gray300) + Text(value).pretendardFont(.semiBold15).foregroundStyle(.gray800) } - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) } - var separator: some View { - Rectangle().fill(.beige600).frame(width: 1, height: 47) + func metric(_ value: Int, label: String) -> some View { + VStack(spacing: 4) { + Text("\(value)").pretendardFont(.semiBold15).foregroundStyle(.gray800) + Text(label).pretendardFont(.regular13).foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity) } - @ViewBuilder - func activityCard(title: String, body: String, icon: String) -> some View { - HStack(alignment: .top, spacing: 8) { - VStack(alignment: .leading, spacing: 8) { - Text(title).pickeBadge(.filled, size: .tag) - Text(body) - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - .multilineTextAlignment(.leading) - HStack(spacing: 12) { - Label("1,340", systemImage: "heart") - Label("23", systemImage: "bubble") - } + func activityCard(_ text: String, showsChevron: Bool = false) -> some View { + HStack(spacing: 8) { + Text(text) .pretendardFont(.regular13) - .foregroundStyle(.gray300) - } + .foregroundStyle(.gray700) + .multilineTextAlignment(.leading) Spacer(minLength: 0) - Image(systemName: icon) - .font(.system(size: 12, weight: .semibold)) - .foregroundStyle(.gray300) + if showsChevron { + Image(systemName: "chevron.right").foregroundStyle(.gray300) + } } - .padding(12) .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) - .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) + } + + func emptyCard(_ text: String) -> some View { + Text(text) + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) } } + +#if DEBUG + #Preview("운영자 멤버 상세") { + ClassMemberDetailView(store: Store(initialState: ClassMemberDetailFeature.State( + room: ClassRoom.mocks[0], + member: ClassOwnerMember(id: 1, name: "공은지", participationCount: 1), + activity: .init( + initialStance: "낮춰야 한다", + finalStance: "교정이 우선이다", + commentCount: 2, + replyCount: 3, + receivedLikeCount: 12, + comment: "제도화가 무서운 건, 사회적 압력이 선택을 의무로 바꿀 수 있다는 거예요.", + commentOpinion: ClassOwnerOpinion( + id: 101, + author: "공은지", + text: "제도화가 무서운 건, 사회적 압력이 선택을 의무로 바꿀 수 있다는 거예요.", + replyCount: 2 + ), + reply: "다른 의견을 읽고 생각을 바꿨어요." + ) + )) { + ClassMemberDetailFeature() + }) + } +#endif diff --git a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift index f636134e..ef7d7e3f 100644 --- a/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift +++ b/Projects/Feature/Class/Sources/ClassOwnerDetail/View/ClassReplyDetailView.swift @@ -5,7 +5,7 @@ import SwiftUI @ViewAction(for: ClassReplyDetailFeature.self) public struct ClassReplyDetailView: View { - public let store: StoreOf + @Bindable public var store: StoreOf public init(store: StoreOf) { self.store = store @@ -15,16 +15,46 @@ public struct ClassReplyDetailView: View { VStack(spacing: 0) { PickeNavigationBar(onBack: { send(.backTapped) }, centerTitle: store.room.name) .foregroundStyle(.gray800) - Text("예시 데이터") - .pretendardFont(.medium10) - .foregroundStyle(.gray300) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - ScrollView { VStack(spacing: 12) { - ForEach(store.replies) { reply in - replyCard(reply) + if let opinion = store.opinion { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + PickeAvatarView(imageURL: nil, fallback: opinion.author, size: 36) + Text(opinion.author) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + Spacer() + } + Text(opinion.text) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + .frame(maxWidth: .infinity, alignment: .leading) + Label("\(opinion.replyCount)", systemImage: "bubble") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) + } + switch store.viewState { + case .unavailable: + Text("대댓글을 불러올 수 없어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + case .empty: + Text("아직 대댓글이 없어요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + case let .loaded(replies): + ForEach(replies) { reply in + replyCard(reply) + } } } .padding(16) @@ -55,12 +85,6 @@ private extension ClassReplyDetailView { .pretendardFont(.regular13) .foregroundStyle(.gray700) .frame(maxWidth: .infinity, alignment: .leading) - HStack(spacing: 12) { - Label("12", systemImage: "heart") - Label("답글", systemImage: "bubble") - } - .pretendardFont(.regular13) - .foregroundStyle(.gray300) } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift index 0321bec9..7fb90743 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/Reducer/ClassRecommendFeature.swift @@ -15,6 +15,13 @@ public struct ClassRecommendFeature { @ObservableState public struct State: Equatable { + public enum ViewState: Equatable { + case loading + case error + case empty + case loaded + } + public var filter: ClassTopicFilter public var battles: [ClassBattleSummary] = [] public var selectedBattleId: Int? @@ -41,13 +48,14 @@ public struct ClassRecommendFeature { selectedBattle != nil } - public var shouldShowSkeleton: Bool { - isLoading && battles.isEmpty - } - - /// 로드 실패 & 표시할 배틀이 없을 때 스켈레톤 대신 오류를 노출한다. - public var shouldShowLoadError: Bool { - loadFailed && battles.isEmpty + public var viewState: ViewState { + if loadFailed && battles.isEmpty { + return .error + } + if isLoading && battles.isEmpty { + return .loading + } + return battles.isEmpty ? .empty : .loaded } } diff --git a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift index 0e5ad62d..62294b61 100644 --- a/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift +++ b/Projects/Feature/Class/Sources/ClassRecommend/View/ClassRecommendView.swift @@ -92,12 +92,13 @@ private extension ClassRecommendView { @ViewBuilder func resultSection() -> some View { Group { - if store.shouldShowLoadError { + switch store.viewState { + case .error: PickeRetryErrorView(message: "추천 배틀을 불러오지 못했어요") { send(.retryTapped) } .frame(maxWidth: .infinity, minHeight: 160) - } else if store.shouldShowSkeleton { + case .loading: ClassRecommendSkeletonView() - } else { + case .empty, .loaded: resultList() } } diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportPreviewContent.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportPreviewContent.swift new file mode 100644 index 00000000..5a293b0c --- /dev/null +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportPreviewContent.swift @@ -0,0 +1,70 @@ +import CoreGraphics + +struct ClassReportPreviewContent { + enum Key: Hashable { + case aiSummaryTitle, aiSummaryDetail, teacherSummary + case commentStance, commentDate, commentBody, commentLikes, commentReplies + case replyAuthor, replyParent, replyBody, replyDate, replyLikes + case participantTitle, participantCount, classSummary + case bestAuthor, bestBody, bestLikes, bestReplies + case feedbackTitle1, feedbackDetail1, feedbackTitle2, feedbackDetail2 + case feedbackTitle3, feedbackDetail3, nextQuestion, nextHint + case teacherFeedback, teacherName, teacherDate + case initialChoice, finalChoice, commentCount, replyCount, receivedLikeCount + } + + let texts: [Key: String] + let initialVoteLeading: CGFloat + let finalVoteLeading: CGFloat + + subscript(_ key: Key) -> String { + texts[key] ?? "" + } +} + +#if DEBUG + extension ClassReportPreviewContent { + static let figma = Self( + texts: [ + .aiSummaryTitle: "다른 해결방법까지 생각을 확장했어요", + .aiSummaryDetail: "“교육과 보호도 함께 필요하다”는 대댓글에서 처벌 외의 대안을 제시했어요", + .teacherSummary: "“왜 생각이 달라졌는지 자신의 말로 설명한 점이 좋았어요”", + .commentStance: "낮춰야한다", + .commentDate: "26.09.23. 14:02", + .commentBody: "제도화가 무서운 건, 사회적 압력이 '선택'을 '의무'로 바꿀 수 있다는 거예요. 네덜란드 사례를 보면 우려가 현실이 되고 있죠.", + .commentLikes: "1,340", + .commentReplies: "23", + .replyAuthor: "김민지 · 교정이 우선이다", + .replyParent: "제도화가 무서운 건, 사회적 압력이 '선택'을 '의무'로 바꿀 수 있다는 거예요.", + .replyBody: "토론을 들으면서 처벌만으로는 해결되지 않는 점을 이해하게 됐어요. 교육과 보호도 함께 필요하다고 생각해요.", + .replyDate: "26.09.23. 14:02", + .replyLikes: "12", + .participantTitle: "참여한 학생", + .participantCount: "28 / 32", + .classSummary: "입장을 바꾼 학생은 6명이에요.\n연령하향 → 교정우선 5명, 교정 우선 → 연령 하향 1명이에요.\n교정 우선 선택은 10명에서 14명으로 늘었어요.", + .bestAuthor: "김민지 · 교정이 우선이다", + .bestBody: "“피해자 보호를 위한 기준도 함께 필요하지 않을까요?”", + .bestLikes: "1,340", + .bestReplies: "23", + .feedbackTitle1: "내 생각의 변화를 설명했어요", + .feedbackDetail1: "“처벌만으로는 해결되지 않는 점”을 언급하며, 처음과 입장이 달리진 이유를 드러냈어요.", + .feedbackTitle2: "다른 해결방법을 제안했어요", + .feedbackDetail2: "반대의견에 그치지 않고 “교육과 보호”라는 대안을 덧붙였어요.", + .feedbackTitle3: "근거를 한 단계 더 구체적으로", + .feedbackDetail3: "교육과 보호가 재범을 줄이는 데 어떤 도움이 되는지, 사례나 자료 하나를 연결하면 주장이 더 설득력 있어져요.", + .nextQuestion: "처벌과 교화를 함께 한다면,\n무엇을 기준으로 균형을 잡을까요?", + .nextHint: "내 의견과 반대되는 입장도 함께 떠올려 보세요.", + .teacherFeedback: "처음과 나중의 생각이 어떻게 달라졌는지 스스로 정리한 점이 좋았어요. 단순히 입장을 바꾸는 데서 끝나지 않고, 왜 바뀌었는지 설명하려는 태도가 인상적이었습니다.", + .teacherName: "김민지 선생님", + .teacherDate: "26.09.23. 14:02", + .initialChoice: "낮춰야 한다", + .finalChoice: "교정이 우선이다", + .commentCount: "1", + .replyCount: "12", + .receivedLikeCount: "8", + ], + initialVoteLeading: 0.64, + finalVoteLeading: 0.50 + ) + } +#endif diff --git a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift index b1f8d330..9bcdde8d 100644 --- a/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift +++ b/Projects/Feature/Class/Sources/ClassReport/View/ClassReportView.swift @@ -4,10 +4,27 @@ import SwiftUI @ViewAction(for: ClassReportFeature.self) public struct ClassReportView: View { - public let store: StoreOf + @Bindable public var store: StoreOf + private let previewContent: ClassReportPreviewContent? public init(store: StoreOf) { self.store = store + previewContent = nil + } + + #if DEBUG + init(store: StoreOf, previewContent: ClassReportPreviewContent) { + self.store = store + self.previewContent = previewContent + } + #endif + + private var showsFigmaPreview: Bool { + previewContent != nil + } + + private func previewText(_ key: ClassReportPreviewContent.Key) -> String { + previewContent?[key] ?? "" } public var body: some View { @@ -108,127 +125,298 @@ private extension ClassReportView { @ViewBuilder func summaryContent() -> some View { - reportCard { + reportCard(minHeight: showsFigmaPreview ? 204 : 0) { cardHeading("내 참여 요약", destination: .participation) - unavailableReport( - title: "내 투표와 참여 기록을 불러올 수 없어요", - message: "리포트 데이터가 연결되면 이곳에서 확인할 수 있어요." - ) + voteAndActivitySummary() } - reportCard { + reportCard(minHeight: showsFigmaPreview ? 148 : 0) { cardHeading("AI 리포트", destination: .feedback) - unavailableReport( - title: "AI 리포트를 준비하고 있어요", - message: "작성한 의견이 분석되면 이곳에서 확인할 수 있어요." - ) + if showsFigmaPreview { + Text(previewText(.aiSummaryTitle)) + .pretendardFont(.semiBold15) + .foregroundStyle(.gray800) + Text(previewText(.aiSummaryDetail)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + } else { + unavailableReport( + title: "AI 리포트를 준비하고 있어요", + message: "작성한 의견이 분석되면 이곳에서 확인할 수 있어요." + ) + } } - reportCard { + reportCard(minHeight: showsFigmaPreview ? 95 : 0) { cardHeading("선생님 피드백", destination: .feedback) - unavailableReport( - title: "선생님 피드백 데이터를 불러올 수 없어요", - message: "선생님 피드백이 등록되면 이곳에서 확인할 수 있어요." - ) + if showsFigmaPreview { + Text(previewText(.teacherSummary)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + } else { + unavailableReport( + title: "선생님 피드백 데이터를 불러올 수 없어요", + message: "선생님 피드백이 등록되면 이곳에서 확인할 수 있어요." + ) + } } } @ViewBuilder func participationContent() -> some View { - reportCard { + reportCard(minHeight: showsFigmaPreview ? 204 : 0) { cardHeading("내 투표 결과") - unavailableReport( - title: "내 투표 결과를 불러올 수 없어요", - message: "이 클래스의 투표 결과가 연결되면 표시할게요." - ) + voteAndActivitySummary() } - reportCard(padding: 16, spacing: 8) { + reportCard(padding: 16, spacing: 8, minHeight: showsFigmaPreview ? 177 : 0) { cardHeading("내 댓글") - unavailableReport( - title: "댓글 데이터를 불러올 수 없어요", - message: "댓글 데이터가 연결되면 이곳에서 확인할 수 있어요." - ) + if showsFigmaPreview { + HStack { + Text(previewText(.commentStance)).pickeBadge(.filled, size: .tag) + Spacer() + Text(previewText(.commentDate)).pretendardFont(family: .Regular, size: 12).foregroundStyle(.gray300) + } + Text(previewText(.commentBody)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + reactions(likes: previewText(.commentLikes), replies: previewText(.commentReplies)) + } else { + unavailableReport( + title: "댓글 데이터를 불러올 수 없어요", + message: "댓글 데이터가 연결되면 이곳에서 확인할 수 있어요." + ) + } } - reportCard(padding: 16, spacing: 8) { + reportCard(padding: 16, spacing: 8, minHeight: showsFigmaPreview ? 225 : 0) { cardHeading("내 대댓글") - unavailableReport( - title: "대댓글 데이터를 불러올 수 없어요", - message: "대댓글 데이터가 연결되면 이곳에서 확인할 수 있어요." - ) + if showsFigmaPreview { + Text(previewText(.replyAuthor)) + .pretendardFont(.semiBold13) + .foregroundStyle(.gray800) + Text(previewText(.replyParent)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Rectangle().fill(.beige600).frame(height: 1) + Text("내 대댓글").pretendardFont(.semiBold13).foregroundStyle(.gray800) + Text(previewText(.replyBody)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + HStack { + Text(previewText(.replyDate)) + Spacer() + Image(systemName: "heart") + Text(previewText(.replyLikes)) + } + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.gray300) + } else { + unavailableReport( + title: "대댓글 데이터를 불러올 수 없어요", + message: "대댓글 데이터가 연결되면 이곳에서 확인할 수 있어요." + ) + } } } @ViewBuilder func classResultContent() -> some View { - reportCard { + reportCard(minHeight: showsFigmaPreview ? 243 : 0) { cardHeading("우리 클래스 참여 현황") HStack { - Text("클래스 멤버") + Text(showsFigmaPreview ? previewText(.participantTitle) : "클래스 멤버") .pretendardFont(.regular13) .foregroundStyle(.gray300) Spacer() - Text("\(store.room.memberCount)명") + Text(showsFigmaPreview ? previewText(.participantCount) : "\(store.room.memberCount)명") .pretendardFont(.semiBold15) .foregroundStyle(.gray800) } - unavailableReport( - title: "투표 분포를 집계할 수 없어요", - message: "현재 클래스 데이터에는 투표 집계가 포함되어 있지 않아요." - ) + if showsFigmaPreview { + voteDistribution(title: "사전 투표 결과", leading: previewContent?.initialVoteLeading ?? 0) + voteDistribution(title: "사후 투표 결과", leading: previewContent?.finalVoteLeading ?? 0) + } else { + unavailableReport( + title: "투표 분포를 집계할 수 없어요", + message: "현재 클래스 데이터에는 투표 집계가 포함되어 있지 않아요." + ) + } } - reportCard(padding: 16, spacing: 8) { + reportCard(padding: 16, spacing: 8, minHeight: showsFigmaPreview ? 120 : 0) { cardHeading("클래스 결과 요약") - Text("\(store.room.battle.optionATitle)\n\(store.room.battle.optionBTitle)") - .pretendardFont(.regular13) - .foregroundStyle(.gray700) - Text("두 선택지에 대한 클래스 투표 결과가 연결되면 비교해 드릴게요.") - .pretendardFont(.regular13) - .foregroundStyle(.gray300) + if showsFigmaPreview { + Text(previewText(.classSummary)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + } else { + Text("\(store.room.battle.optionATitle)\n\(store.room.battle.optionBTitle)") + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + Text("두 선택지에 대한 클래스 투표 결과가 연결되면 비교해 드릴게요.") + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } } - reportCard(padding: 16, spacing: 8) { + reportCard(padding: 16, spacing: 8, minHeight: showsFigmaPreview ? 142 : 0) { cardHeading("BEST 댓글") - unavailableReport( - title: "BEST 댓글 데이터를 불러올 수 없어요", - message: "댓글 반응 데이터가 연결되면 가장 많은 공감을 받은 댓글을 보여드릴게요." - ) + if showsFigmaPreview { + Text(previewText(.bestAuthor)) + .pretendardFont(.semiBold13) + .foregroundStyle(.gray800) + Text(previewText(.bestBody)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + reactions(likes: previewText(.bestLikes), replies: previewText(.bestReplies)) + } else { + unavailableReport( + title: "BEST 댓글 데이터를 불러올 수 없어요", + message: "댓글 반응 데이터가 연결되면 가장 많은 공감을 받은 댓글을 보여드릴게요." + ) + } } } @ViewBuilder func feedbackContent() -> some View { - reportCard { + reportCard(minHeight: showsFigmaPreview ? 361 : 0) { cardHeading("AI 피드백") - unavailableReport( - title: "AI 분석 결과가 아직 없어요", - message: "내 의견과 댓글 데이터가 연결되면 생각의 변화와 근거를 분석해 드릴게요." - ) + if showsFigmaPreview { + feedbackPoint(previewText(.feedbackTitle1), detail: previewText(.feedbackDetail1)) + feedbackPoint(previewText(.feedbackTitle2), detail: previewText(.feedbackDetail2)) + feedbackPoint(previewText(.feedbackTitle3), detail: previewText(.feedbackDetail3)) + Text("분석에 사용한 내 대댓글 보기") + .pretendardFont(.semiBold13) + .foregroundStyle(.primary500) + } else { + unavailableReport( + title: "AI 분석 결과가 아직 없어요", + message: "내 의견과 댓글 데이터가 연결되면 생각의 변화와 근거를 분석해 드릴게요." + ) + } } - reportCard(padding: 16, spacing: 8) { + reportCard(padding: 16, spacing: 8, minHeight: showsFigmaPreview ? 138 : 0) { cardHeading("다음 생각 해보기") - Text(store.room.battle.title) + Text(showsFigmaPreview ? previewText(.nextQuestion) : store.room.battle.title) .pretendardFont(family: .SemiBold, size: 18) .foregroundStyle(.gray700) - Text("두 선택지를 비교하며 내 생각을 정리해 보세요.") + Text(showsFigmaPreview ? previewText(.nextHint) : "두 선택지를 비교하며 내 생각을 정리해 보세요.") .pretendardFont(.regular13) .foregroundStyle(.gray300) } - reportCard(padding: 16, spacing: 8) { + reportCard(padding: 16, spacing: 8, minHeight: showsFigmaPreview ? 142 : 0) { cardHeading("선생님 피드백") + if showsFigmaPreview { + Text(previewText(.teacherFeedback)) + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + HStack { + Text(previewText(.teacherName)) + Spacer() + Text(previewText(.teacherDate)) + } + .pretendardFont(family: .Regular, size: 12) + .foregroundStyle(.gray300) + } else { + unavailableReport( + title: "선생님 피드백 데이터를 불러올 수 없어요", + message: "선생님이 피드백을 남기면 이곳에서 확인할 수 있어요." + ) + } + } + } + + @ViewBuilder + func voteAndActivitySummary() -> some View { + if showsFigmaPreview { + HStack(spacing: 8) { + voteChoice("사전 선택", value: previewText(.initialChoice)) + VStack(spacing: 4) { + Image(systemName: "arrow.right") + Text("입장 변경").pretendardFont(family: .Regular, size: 12) + } + .foregroundStyle(.primary500) + voteChoice("사후 선택", value: previewText(.finalChoice)) + } + .frame(maxWidth: .infinity) + HStack(spacing: 0) { + activityCount(previewText(.commentCount), label: "댓글") + activityCount(previewText(.replyCount), label: "대댓글") + activityCount(previewText(.receivedLikeCount), label: "받은 좋아요") + } + } else { unavailableReport( - title: "선생님 피드백 데이터를 불러올 수 없어요", - message: "선생님이 피드백을 남기면 이곳에서 확인할 수 있어요." + title: "내 투표와 참여 기록을 불러올 수 없어요", + message: "리포트 데이터가 연결되면 이곳에서 확인할 수 있어요." ) } } + func voteChoice(_ title: String, value: String) -> some View { + VStack(spacing: 4) { + Text(title).pretendardFont(family: .Regular, size: 12).foregroundStyle(.gray300) + Text(value).pretendardFont(.semiBold13).foregroundStyle(.gray800) + } + .frame(maxWidth: .infinity) + } + + func activityCount(_ count: String, label: String) -> some View { + VStack(spacing: 2) { + Text(count).pretendardFont(family: .SemiBold, size: 18).foregroundStyle(.gray800) + Text(label).pretendardFont(.regular13).foregroundStyle(.gray300) + } + .frame(maxWidth: .infinity) + } + + func reactions(likes: String, replies: String) -> some View { + HStack(spacing: 16) { + Label(likes, systemImage: "heart") + Label(replies, systemImage: "bubble.right") + } + .pretendardFont(.regular13) + .foregroundStyle(.gray300) + } + + func voteDistribution(title: String, leading: CGFloat) -> some View { + VStack(alignment: .leading, spacing: 6) { + Text(title).pretendardFont(.regular13).foregroundStyle(.gray700) + GeometryReader { geometry in + HStack(spacing: 0) { + Rectangle().fill(Color.primary500).frame(width: geometry.size.width * leading) + Rectangle().fill(Color.gray300) + } + } + .frame(height: 8) + HStack { + Text(store.room.battle.optionATitle) + Spacer() + Text(store.room.battle.optionBTitle) + } + .pretendardFont(.regular13) + .foregroundStyle(.gray700) + } + } + + func feedbackPoint(_ title: String, detail: String) -> some View { + HStack(alignment: .top, spacing: 10) { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.primary500) + VStack(alignment: .leading, spacing: 4) { + Text(title).pretendardFont(.semiBold15).foregroundStyle(.gray800) + Text(detail).pretendardFont(.regular13).foregroundStyle(.gray700) + } + } + .padding(.vertical, 4) + } + @ViewBuilder func reportCard( padding: CGFloat = 12, spacing: CGFloat = 12, + minHeight: CGFloat = 0, @ViewBuilder content: () -> some View ) -> some View { VStack(alignment: .leading, spacing: spacing, content: content) .padding(.vertical, padding) .padding(.horizontal, 16) .frame(maxWidth: .infinity, alignment: .leading) + .frame(minHeight: minHeight, alignment: .top) .background(.beige50, in: RoundedRectangle(cornerRadius: 2)) .overlay(RoundedRectangle(cornerRadius: 2).stroke(.beige600, lineWidth: 1)) } @@ -268,3 +456,27 @@ private extension ClassReportView { } } } + +#if DEBUG + private func figmaReportStore(tab: ClassReportFeature.Tab) -> StoreOf { + var state = ClassReportFeature.State(room: .mocks[0]) + state.selectedTab = tab + return Store(initialState: state) { ClassReportFeature() } + } + + #Preview("리포트 · 요약") { + ClassReportView(store: figmaReportStore(tab: .summary), previewContent: .figma) + } + + #Preview("리포트 · 내 참여") { + ClassReportView(store: figmaReportStore(tab: .participation), previewContent: .figma) + } + + #Preview("리포트 · 클래스 결과") { + ClassReportView(store: figmaReportStore(tab: .classResult), previewContent: .figma) + } + + #Preview("리포트 · 피드백") { + ClassReportView(store: figmaReportStore(tab: .feedback), previewContent: .figma) + } +#endif diff --git a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift index 52a18b06..93c143fe 100644 --- a/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift +++ b/Projects/Feature/Class/Sources/MyClass/Reducer/MyClassFeature.swift @@ -7,6 +7,13 @@ public struct MyClassFeature { @ObservableState public struct State: Equatable { + public enum ViewState: Equatable { + case loading + case error + case empty + case loaded([ClassRoom]) + } + public enum Progress: Hashable, CaseIterable { case all case open @@ -20,13 +27,15 @@ public struct MyClassFeature { public init() {} - public var shouldShowSkeleton: Bool { - isLoading && rooms.isEmpty - } - - /// 로드 실패 & 표시할 클래스가 없을 때 스켈레톤 대신 오류를 노출한다. - public var shouldShowLoadError: Bool { - errorMessage != nil && rooms.isEmpty + public var viewState: ViewState { + if errorMessage != nil && rooms.isEmpty { + return .error + } + if isLoading && rooms.isEmpty { + return .loading + } + let visible = visibleRooms + return visible.isEmpty ? .empty : .loaded(visible) } public var visibleRooms: [ClassRoom] { diff --git a/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift b/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift index 40ba8a9d..dcc644ee 100644 --- a/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift +++ b/Projects/Feature/Class/Sources/MyClass/View/MyClassView.swift @@ -50,19 +50,20 @@ private extension MyClassView { @ViewBuilder func roomList() -> some View { - if store.shouldShowLoadError { + switch store.viewState { + case .error: PickeRetryErrorView(message: "클래스를 불러오지 못했어요") { send(.retryTapped) } .frame(maxWidth: .infinity, minHeight: 160) - } else if store.shouldShowSkeleton { + case .loading: MyClassSkeletonView() - } else if store.visibleRooms.isEmpty { + case .empty: Text("아직 클래스가 없어요") .pretendardFont(.medium15) .foregroundStyle(.gray300) .frame(maxWidth: .infinity, minHeight: 160) - } else { + case let .loaded(rooms): LazyVStack(spacing: 12) { - ForEach(store.visibleRooms) { room in + ForEach(rooms) { room in Button { send(.roomTapped(room.id)) } label: { MyClassRoomCard(room: room) } diff --git a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift index 65428a42..a49edf93 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassJoinTests.swift @@ -22,7 +22,7 @@ struct ClassJoinTests { let store = TestStore(initialState: ClassJoinFeature.State()) { ClassJoinFeature() } withDependencies: { - $0.classRepository = StubClassUseCase(fetchClass: { _ in room }) + $0.classUseCase = StubClassUseCase(fetchClass: { _ in room }) } await store.send(.binding(.set(\.joinCode, " pk9t3s "))) { @@ -46,7 +46,7 @@ struct ClassJoinTests { let store = TestStore(initialState: state) { ClassJoinFeature() } withDependencies: { - $0.classRepository = StubClassUseCase(joinClass: { code, nickname in + $0.classUseCase = StubClassUseCase(joinClass: { code, nickname in #expect(code == room.joinCode) #expect(nickname == "민지") return room @@ -69,7 +69,7 @@ struct ClassJoinTests { let store = TestStore(initialState: ClassJoinFeature.State()) { ClassJoinFeature() } withDependencies: { - $0.classRepository = StubClassUseCase(fetchClass: { _ in + $0.classUseCase = StubClassUseCase(fetchClass: { _ in throw ClassError.invalidCode }) } diff --git a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift index 2e62dea7..df51c48b 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassMemberTests.swift @@ -7,10 +7,10 @@ import Testing struct ClassMemberTests { @Test func 멤버_내보내기는_서버_연동_전까지_상태를_변경하지_않는다() async { - let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + let member = ClassMember(id: 2, name: "김민지") + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0], members: [member])) { ClassMemberFeature() } - let member = store.state.members[1] let originalMembers = store.state.members await store.send(.view(.removeTapped(member.id))) @@ -24,19 +24,40 @@ struct ClassMemberTests { } await store.send(.view(.removeTapped(2))) - #expect(store.state.members.count == ClassRoom.mocks[1].memberCount) + #expect(store.state.members.isEmpty) + #expect(store.state.room.memberCount == ClassRoom.mocks[1].memberCount) + } + + @Test + func 멤버_자료가_없으면_인원수만_보존하고_행은_만들지_않는다() { + let state = ClassMemberFeature.State(room: ClassRoom.mocks[0]) + + #expect(state.room.memberCount == ClassRoom.mocks[0].memberCount) + #expect(state.members.isEmpty) + #expect(state.visibleMembers.isEmpty) + #expect(state.viewState == .unavailable) } @Test func 검색어에_맞는_멤버만_표시한다() async { - let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + let members = [ + ClassMember(id: 1, name: "김민지"), + ClassMember(id: 2, name: "서원지"), + ] + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0], members: members)) { ClassMemberFeature() } - await store.send(.binding(.set(\.searchText, "학생 1"))) { - $0.searchText = "학생 1" + await store.send(.binding(.set(\.searchText, "김민지"))) { + $0.searchText = "김민지" } - #expect(store.state.visibleMembers.allSatisfy { $0.name.contains("학생 1") }) + #expect(store.state.visibleMembers == [members[0]]) + #expect(store.state.viewState == .members([members[0]])) + + await store.send(.binding(.set(\.searchText, "없는 이름"))) { + $0.searchText = "없는 이름" + } + #expect(store.state.viewState == .noSearchResults) } @Test @@ -58,8 +79,12 @@ struct ClassMemberTests { } @Test - func 이름순_정렬은_시드된_멤버에_적용된다() async { - let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + func 이름순_정렬은_전달된_멤버에_적용된다() async { + let members = [ + ClassMember(id: 1, name: "서원지"), + ClassMember(id: 2, name: "김민지"), + ] + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0], members: members)) { ClassMemberFeature() } @@ -77,4 +102,23 @@ struct ClassMemberTests { $0.filter = $0.appliedFilter } } + + @Test + func 지원하지_않는_필터와_정렬은_선택해도_적용되지_않는다() async { + let store = TestStore(initialState: ClassMemberFeature.State(room: ClassRoom.mocks[0])) { + ClassMemberFeature() + } + + await store.send(.view(.filterTapped)) { + $0.filter = .init() + } + await store.send(.filter(.presented(.participationSelected(.notParticipated)))) + await store.send(.filter(.presented(.completionSelected(.noComment)))) + await store.send(.filter(.presented(.attendanceSelected(.changed)))) + await store.send(.filter(.presented(.sortSelected(.comments)))) + await store.send(.filter(.presented(.applyTapped))) { + $0.filter = nil + } + #expect(store.state.appliedFilter == .init()) + } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift b/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift index 7d854202..c2edd07a 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassOwnerDashboardTests.swift @@ -1,20 +1,49 @@ @testable import Class import ClassDomainInterface import ComposableArchitecture +import Foundation import Testing @MainActor struct ClassOwnerDashboardTests { + @Test + func 기본_상태에는_예시_멤버와_의견이_없다() { + let state = ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0]) + #expect(state.members.isEmpty) + #expect(state.opinions.isEmpty) + #expect(state.visibleMembers.isEmpty) + #expect(state.visibleOpinions.isEmpty) + #expect(state.needsReviewViewState == .unavailable) + #expect(state.highParticipationViewState == .unavailable) + #expect(state.topOpinionViewState == .empty) + } + + @Test + func 참여_요약은_해당_멤버가_없으면_빈_상태로_표시한다() { + var state = ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0]) + state.members = [ + ClassOwnerMember(id: 1, name: "첫 멤버", participationCount: 1), + ClassOwnerMember(id: 2, name: "둘째 멤버", participationCount: 0), + ] + + #expect(state.needsReviewViewState == .empty) + #expect(state.highParticipationViewState == .loaded([state.members[0]])) + + state.members[0] = ClassOwnerMember(id: 1, name: "첫 멤버", participationCount: 0) + #expect(state.highParticipationViewState == .empty) + } + @Test func 멤버_정렬과_검색을_함께_적용한다() async { - let store = TestStore(initialState: ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0])) { + let store = TestStore(initialState: ClassOwnerDashboardFeature.State.preview(room: ClassRoom.mocks[0])) { ClassOwnerDashboardFeature() } await store.send(.view(.memberSortSelected(.participation))) { $0.memberSort = .participation } - #expect(store.state.visibleMembers.map(\.participationCount) == [8, 7, 6, 5, 4, 3]) + #expect(store.state.visibleMembers.prefix(3).map(\.participationCount) == [8, 7, 6]) + #expect(store.state.visibleMembers.filter { $0.participationCount > 0 }.count == 28) await store.send(.view(.searchTextChanged("김민지"))) { $0.searchText = "김민지" @@ -22,17 +51,93 @@ struct ClassOwnerDashboardTests { #expect(store.state.visibleMembers.map(\.name) == ["김민지"]) } + @Test + func 참여_상태_필터는_미참여_멤버만_표시한다() async { + let store = TestStore(initialState: ClassOwnerDashboardFeature.State.preview(room: ClassRoom.mocks[0])) { + ClassOwnerDashboardFeature() + } + + await store.send(.view(.memberFilterSelected(.notParticipated))) { + $0.memberFilter = .notParticipated + } + #expect(store.state.visibleMembers.count == 4) + #expect(store.state.visibleMembers.allSatisfy { $0.participationCount == 0 }) + } + @Test func 피드백_대상_멤버를_전달한다() async { - let store = TestStore(initialState: ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0])) { + let store = TestStore(initialState: ClassOwnerDashboardFeature.State.preview(room: ClassRoom.mocks[0])) { ClassOwnerDashboardFeature() } let member = store.state.members[0] await store.send(.view(.feedbackTapped(member.id))) await store.receive(\.delegate, .openFeedback(member)) + + await store.send(.view(.memberTapped(member.id))) + await store.receive(\.delegate, .openMemberDetail(member)) + } + + @Test + func 의견_정렬은_인기순과_전달된_최신순을_구분한다() async { + var state = ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0]) + state.opinions = [ + ClassOwnerOpinion( + id: 1, + author: "첫 의견", + text: "A", + replyCount: 1, + recommendationCount: 1, + createdAt: Date(timeIntervalSince1970: 200) + ), + ClassOwnerOpinion( + id: 2, + author: "둘째 의견", + text: "B", + replyCount: 3, + recommendationCount: 3, + createdAt: Date(timeIntervalSince1970: 100) + ), + ] + let store = TestStore(initialState: state) { ClassOwnerDashboardFeature() } + + #expect(store.state.visibleOpinions.map(\.id) == [2, 1]) + #expect(store.state.topOpinionViewState == .loaded([state.opinions[1], state.opinions[0]])) + await store.send(.view(.opinionFilterSelected(.latest))) { + $0.opinionFilter = .latest + } + #expect(store.state.visibleOpinions.map(\.id) == [1, 2]) } + @Test + func 정렬_기준이_없는_의견은_필터를_적용하지_않는다() async { + var state = ClassOwnerDashboardFeature.State(room: ClassRoom.mocks[0]) + state.opinions = [ClassOwnerOpinion(id: 1, author: "첫 의견", text: "A", replyCount: 2)] + let store = TestStore(initialState: state) { ClassOwnerDashboardFeature() } + + #expect(!store.state.canSortOpinions(.popular)) + #expect(!store.state.canSortOpinions(.latest)) + #expect(store.state.activeOpinionFilter == nil) + #expect(store.state.topOpinionViewState == .unavailable) + await store.send(.view(.opinionFilterSelected(.latest))) + #expect(store.state.opinionFilter == .popular) + #expect(store.state.visibleOpinions.map(\.id) == [1]) + } + + #if DEBUG + @Test + func 미리보기_참여_수치와_멤버_목록이_같은_인원을_사용한다() { + let state = ClassOwnerDashboardFeature.State.preview(room: ClassRoom.mocks[0]) + let participated = state.members.filter { $0.participationCount > 0 }.count + + #expect(state.members.count == 32) + #expect(participated == 28) + #expect(state.previewMetrics?.optionAVoteCount == 18) + #expect(state.previewMetrics?.optionBVoteCount == 10) + #expect(state.previewMetrics?.commentAuthorCount == 24) + } + #endif + @Test func 서버_연동_전에는_피드백_등록을_전송하지_않는다() async { let store = TestStore(initialState: ClassFeedbackComposeFeature.State( diff --git a/Projects/Feature/Class/Tests/Sources/ClassOwnerDetailTests.swift b/Projects/Feature/Class/Tests/Sources/ClassOwnerDetailTests.swift new file mode 100644 index 00000000..4227431b --- /dev/null +++ b/Projects/Feature/Class/Tests/Sources/ClassOwnerDetailTests.swift @@ -0,0 +1,58 @@ +@testable import Class +import ClassDomainInterface +import ComposableArchitecture +import Testing + +@MainActor +struct ClassOwnerDetailTests { + @Test + func 멤버_상세는_전달받은_멤버를_표시하고_활동_기록을_추측하지_않는다() { + let member = ClassOwnerMember(id: 42, name: "공은지", participationCount: 1) + let state = ClassMemberDetailFeature.State(room: ClassRoom.mocks[0], member: member) + + #expect(state.member == member) + #expect(state.activity == nil) + } + + @Test + func 대댓글_상세는_선택한_의견을_유지하고_임의의_답글을_만들지_않는다() { + let opinion = ClassOwnerOpinion(id: 7, author: "공은지", text: "제 의견입니다.", replyCount: 3) + let state = ClassReplyDetailFeature.State(room: ClassRoom.mocks[0], opinion: opinion) + + #expect(state.opinion == opinion) + #expect(state.replies.isEmpty) + #expect(state.viewState == .unavailable) + } + + @Test + func 실제_답글이_없을_때만_빈_상태를_표시한다() { + let opinion = ClassOwnerOpinion(id: 8, author: "공은지", text: "제 의견입니다.", replyCount: 0) + let state = ClassReplyDetailFeature.State(room: ClassRoom.mocks[0], opinion: opinion) + + #expect(state.viewState == .empty) + } + + @Test + func 멤버_댓글을_누르면_선택한_내용을_전달한다() async { + let member = ClassOwnerMember(id: 42, name: "공은지", participationCount: 1) + let opinion = ClassOwnerOpinion(id: 101, author: "공은지", text: "제 의견입니다.", replyCount: 1) + let activity = ClassMemberDetailFeature.State.Activity( + initialStance: "찬성", + finalStance: "찬성", + commentCount: 1, + replyCount: 2, + receivedLikeCount: 0, + comment: "제 의견입니다.", + commentOpinion: opinion + ) + #expect(!activity.changedStance) + let store = TestStore(initialState: ClassMemberDetailFeature.State( + room: ClassRoom.mocks[0], member: member, activity: activity + )) { + ClassMemberDetailFeature() + } + + await store.send(.view(.commentTapped)) + await store.receive(\.delegate, .commentSelected(opinion)) + } +} diff --git a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift index d324a16c..6b5c6e54 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassRecommendTests.swift @@ -58,14 +58,13 @@ struct ClassRecommendTests { func 로딩_중이고_배틀이_없으면_스켈레톤을_보여주고_실패하면_오류로_바꾼다() { var state = ClassRecommendFeature.State(filter: .init()) state.isLoading = true - #expect(state.shouldShowSkeleton) + #expect(state.viewState == .loading) state.isLoading = false state.loadFailed = true - #expect(state.shouldShowSkeleton == false) - #expect(state.shouldShowLoadError) + #expect(state.viewState == .error) state.battles = ClassBattleSummary.mocks - #expect(state.shouldShowLoadError == false) + #expect(state.viewState == .loaded) } } diff --git a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift index d8bc3d3e..24b0cca7 100644 --- a/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift +++ b/Projects/Feature/Class/Tests/Sources/ClassReportTests.swift @@ -29,4 +29,15 @@ struct ClassReportTests { await store.send(.view(.backTapped)) await store.receive(\.delegate.dismiss) } + + #if DEBUG + @Test + func 리포트_예시_내용은_명시적인_미리보기_자료에_보관한다() { + let preview = ClassReportPreviewContent.figma + + #expect(preview[.participantCount] == "28 / 32") + #expect(preview[.teacherName] == "김민지 선생님") + #expect(preview.initialVoteLeading == 0.64) + } + #endif } diff --git a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift index 98df831a..21a62ed5 100644 --- a/Projects/Feature/Class/Tests/Sources/MyClassTests.swift +++ b/Projects/Feature/Class/Tests/Sources/MyClassTests.swift @@ -40,14 +40,13 @@ struct MyClassTests { func 로딩_중이고_클래스가_없으면_스켈레톤을_보여주고_실패하면_오류로_바꾼다() { var state = MyClassFeature.State() state.isLoading = true - #expect(state.shouldShowSkeleton) + #expect(state.viewState == .loading) state.isLoading = false state.errorMessage = "error" - #expect(state.shouldShowSkeleton == false) - #expect(state.shouldShowLoadError) + #expect(state.viewState == .error) state.rooms = ClassRoom.mocks - #expect(state.shouldShowLoadError == false) + #expect(state.viewState == .loaded(ClassRoom.mocks)) } }