diff --git a/Plugin/UtilityPlugin/ProjectDescriptionHelpers/Dependency+Project.swift b/Plugin/UtilityPlugin/ProjectDescriptionHelpers/Dependency+Project.swift index 615c7ed9..b1dc414a 100644 --- a/Plugin/UtilityPlugin/ProjectDescriptionHelpers/Dependency+Project.swift +++ b/Plugin/UtilityPlugin/ProjectDescriptionHelpers/Dependency+Project.swift @@ -10,6 +10,8 @@ public extension TargetDependency { } public extension TargetDependency.Project.Features { + static let RemainApplyFeature = TargetDependency.feature(name: "RemainApplyFeature") + static let StudyRoomFeature = TargetDependency.feature(name: "StudyRoomFeature") static let SplashFeature = TargetDependency.feature(name: "SplashFeature") static let MyPageFeature = TargetDependency.feature(name: "MyPageFeature") static let NoticeFeature = TargetDependency.feature(name: "NoticeFeature") diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index 224f38d3..5281a80d 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -8,8 +8,14 @@ let isCI = (ProcessInfo.processInfo.environment["TUIST_CI"] ?? "0") == "1" ? tru let settinges: Settings = .settings(base: Environment.baseSetting, configurations: [ - .debug(name: .dev, xcconfig: isCI ? nil : .relativeToXCConfig(type: .dev, name: Environment.targetName)), - .release(name: .prod, xcconfig: isCI ? nil : .relativeToXCConfig(type: .prod, name: Environment.targetName)) + .debug(name: .dev, xcconfig: isCI ? nil : .relativeToXCConfig( + type: .dev, + name: Environment.targetName) + ), + .release(name: .prod, xcconfig: isCI ? nil : .relativeToXCConfig( + type: .prod, + name: Environment.targetName) + ) ], defaultSettings: .recommended) diff --git a/Projects/App/Sources/Application/DI/AppComponent.swift b/Projects/App/Sources/Application/DI/AppComponent.swift index 57b8290e..8cc62d50 100644 --- a/Projects/App/Sources/Application/DI/AppComponent.swift +++ b/Projects/App/Sources/Application/DI/AppComponent.swift @@ -9,6 +9,8 @@ import RenewalPasswordFeature import MainTabFeature import HomeFeature import ApplyFeature +import StudyRoomFeature +import RemainApplyFeature import MyPageFeature import NoticeFeature import SplashFeature @@ -112,4 +114,10 @@ public extension AppComponent { var studyRoomListComponent: StudyRoomListComponent { StudyRoomListComponent(parent: self) } + var applyPageComponent: ApplyPageComponent { + ApplyPageComponent(parent: self) + } + var remainApplyComponent: RemainApplyComponent { + RemainApplyComponent(parent: self) + } } diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 018fe92a..b034507e 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -14,11 +14,13 @@ import MyPageFeature import NeedleFoundation import NetworkModule import NoticeFeature +import RemainApplyFeature import RenewalPasswordFeature import RootFeature import SigninFeature import SignupFeature import SplashFeature +import StudyRoomFeature import SwiftUI // swiftlint:disable unused_declaration @@ -182,8 +184,8 @@ private class MainTabDependency2826cdb310ed0b17a725Provider: MainTabDependency { var homeComponent: HomeComponent { return appComponent.homeComponent } - var studyRoomListComponent: StudyRoomListComponent { - return appComponent.studyRoomListComponent + var applyPageComponent: ApplyPageComponent { + return appComponent.applyPageComponent } var noticeListComponent: NoticeListComponent { return appComponent.noticeListComponent @@ -286,6 +288,50 @@ private class CheckPasswordDependencyd8ff624643356835c570Provider: CheckPassword private func factorycb24ea072925f86bef40f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return CheckPasswordDependencyd8ff624643356835c570Provider(appComponent: parent1(component) as! AppComponent) } +private class StudyRoomDetailDependency00589e4f8d1416a01b43Provider: StudyRoomDetailDependency { + var fetchStudyAvailableTimeUseCase: any FetchStudyAvailableTimeUseCase { + return appComponent.fetchStudyAvailableTimeUseCase + } + var fetchSeatTypesUseCase: any FetchSeatTypesUseCase { + return appComponent.fetchSeatTypesUseCase + } + var fetchDetailStudyRoomUseCase: any FetchDetailStudyRoomUseCase { + return appComponent.fetchDetailStudyRoomUseCase + } + var applyStudyRoomSeatUseCase: any ApplyStudyRoomSeatUseCase { + return appComponent.applyStudyRoomSeatUseCase + } + var cancelStudyRoomSeatUseCase: any CancelStudyRoomSeatUseCase { + return appComponent.cancelStudyRoomSeatUseCase + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->StudyRoomDetailComponent +private func factorya36f40c25dcb280bae0ff47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return StudyRoomDetailDependency00589e4f8d1416a01b43Provider(appComponent: parent1(component) as! AppComponent) +} +private class StudyRoomListDependencyef56e26c25d5de596604Provider: StudyRoomListDependency { + var fetchStudyRoomListUseCase: any FetchStudyRoomListUseCase { + return appComponent.fetchStudyRoomListUseCase + } + var fetchStudyAvailableTimeUseCase: any FetchStudyAvailableTimeUseCase { + return appComponent.fetchStudyAvailableTimeUseCase + } + var studyRoomDetailComponent: StudyRoomDetailComponent { + return appComponent.studyRoomDetailComponent + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->StudyRoomListComponent +private func factory7451c5364e65ee2d46bbf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return StudyRoomListDependencyef56e26c25d5de596604Provider(appComponent: parent1(component) as! AppComponent) +} private class RootDependency3944cc797a4a88956fb5Provider: RootDependency { var signinComponent: SigninComponent { return appComponent.signinComponent @@ -343,49 +389,21 @@ private class HomeDependency443c4e1871277bd8432aProvider: HomeDependency { private func factory67229cdf0f755562b2b1f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return HomeDependency443c4e1871277bd8432aProvider(appComponent: parent1(component) as! AppComponent) } -private class StudyRoomDetailDependency00589e4f8d1416a01b43Provider: StudyRoomDetailDependency { - var fetchStudyAvailableTimeUseCase: any FetchStudyAvailableTimeUseCase { - return appComponent.fetchStudyAvailableTimeUseCase - } - var fetchSeatTypesUseCase: any FetchSeatTypesUseCase { - return appComponent.fetchSeatTypesUseCase - } - var fetchDetailStudyRoomUseCase: any FetchDetailStudyRoomUseCase { - return appComponent.fetchDetailStudyRoomUseCase - } - var applyStudyRoomSeatUseCase: any ApplyStudyRoomSeatUseCase { - return appComponent.applyStudyRoomSeatUseCase - } - var cancelStudyRoomSeatUseCase: any CancelStudyRoomSeatUseCase { - return appComponent.cancelStudyRoomSeatUseCase - } - private let appComponent: AppComponent - init(appComponent: AppComponent) { - self.appComponent = appComponent - } -} -/// ^->AppComponent->StudyRoomDetailComponent -private func factorya36f40c25dcb280bae0ff47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { - return StudyRoomDetailDependency00589e4f8d1416a01b43Provider(appComponent: parent1(component) as! AppComponent) -} -private class StudyRoomListDependencyef56e26c25d5de596604Provider: StudyRoomListDependency { - var fetchStudyRoomListUseCase: any FetchStudyRoomListUseCase { - return appComponent.fetchStudyRoomListUseCase - } - var fetchStudyAvailableTimeUseCase: any FetchStudyAvailableTimeUseCase { - return appComponent.fetchStudyAvailableTimeUseCase +private class ApplyPageDependency3fe4e7c221b14c86d427Provider: ApplyPageDependency { + var studyRoomListComponent: StudyRoomListComponent { + return appComponent.studyRoomListComponent } - var studyRoomDetailComponent: StudyRoomDetailComponent { - return appComponent.studyRoomDetailComponent + var remainApplyComponent: RemainApplyComponent { + return appComponent.remainApplyComponent } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent } } -/// ^->AppComponent->StudyRoomListComponent -private func factory7451c5364e65ee2d46bbf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { - return StudyRoomListDependencyef56e26c25d5de596604Provider(appComponent: parent1(component) as! AppComponent) +/// ^->AppComponent->ApplyPageComponent +private func factory45f688c5d4c7f313fc8df47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return ApplyPageDependency3fe4e7c221b14c86d427Provider(appComponent: parent1(component) as! AppComponent) } private class AuthenticationEmailDependency73189eb572618b10e0fbProvider: AuthenticationEmailDependency { var verifyAuthCodeUseCase: any VerifyAuthCodeUseCase { @@ -435,6 +453,28 @@ private class EnterInformationDependency9204f24c784151f429ddProvider: EnterInfor private func factory359a960501e79e833f64f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return EnterInformationDependency9204f24c784151f429ddProvider(appComponent: parent1(component) as! AppComponent) } +private class RemainApplyDependency4d8caef674dc801cbb54Provider: RemainApplyDependency { + var fetchMyRemainApplicationItemsUseCase: any FetchMyRemainApplicationItemsUseCase { + return appComponent.fetchMyRemainApplicationItemsUseCase + } + var fetchRemainApplicationListUseCase: any FetchRemainApplicationListUseCase { + return appComponent.fetchRemainApplicationListUseCase + } + var fetchRemainsAvailableTimeUseCase: any FetchRemainsAvailableTimeUseCase { + return appComponent.fetchRemainsAvailableTimeUseCase + } + var remainingApplicationsChangesUseCase: any RemainingApplicationsChangesUseCase { + return appComponent.remainingApplicationsChangesUseCase + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->RemainApplyComponent +private func factory9615846346c92a2f8176f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return RemainApplyDependency4d8caef674dc801cbb54Provider(appComponent: parent1(component) as! AppComponent) +} private class NoticeListDependency0e93eb53be8626c408e4Provider: NoticeListDependency { var fetchNoticeListUseCase: any FetchNoticeListUseCase { return appComponent.fetchNoticeListUseCase @@ -529,6 +569,8 @@ extension AppComponent: Registration { localTable["checkPasswordComponent-CheckPasswordComponent"] = { [unowned self] in self.checkPasswordComponent as Any } localTable["modifyPasswordComponent-ModifyPasswordComponent"] = { [unowned self] in self.modifyPasswordComponent as Any } localTable["studyRoomListComponent-StudyRoomListComponent"] = { [unowned self] in self.studyRoomListComponent as Any } + localTable["applyPageComponent-ApplyPageComponent"] = { [unowned self] in self.applyPageComponent as Any } + localTable["remainApplyComponent-RemainApplyComponent"] = { [unowned self] in self.remainApplyComponent as Any } localTable["remoteNoticeDataSource-any RemoteNoticeDataSource"] = { [unowned self] in self.remoteNoticeDataSource as Any } localTable["noticeRepository-any NoticeRepository"] = { [unowned self] in self.noticeRepository as Any } localTable["fetchWhetherNewNoticeUseCase-any FetchWhetherNewNoticeUseCase"] = { [unowned self] in self.fetchWhetherNewNoticeUseCase as Any } @@ -629,7 +671,7 @@ extension SignupProfileImageComponent: Registration { extension MainTabComponent: Registration { public func registerItems() { keyPathToName[\MainTabDependency.homeComponent] = "homeComponent-HomeComponent" - keyPathToName[\MainTabDependency.studyRoomListComponent] = "studyRoomListComponent-StudyRoomListComponent" + keyPathToName[\MainTabDependency.applyPageComponent] = "applyPageComponent-ApplyPageComponent" keyPathToName[\MainTabDependency.noticeListComponent] = "noticeListComponent-NoticeListComponent" keyPathToName[\MainTabDependency.myPageComponent] = "myPageComponent-MyPageComponent" } @@ -666,6 +708,22 @@ extension CheckPasswordComponent: Registration { keyPathToName[\CheckPasswordDependency.modifyPasswordComponent] = "modifyPasswordComponent-ModifyPasswordComponent" } } +extension StudyRoomDetailComponent: Registration { + public func registerItems() { + keyPathToName[\StudyRoomDetailDependency.fetchStudyAvailableTimeUseCase] = "fetchStudyAvailableTimeUseCase-any FetchStudyAvailableTimeUseCase" + keyPathToName[\StudyRoomDetailDependency.fetchSeatTypesUseCase] = "fetchSeatTypesUseCase-any FetchSeatTypesUseCase" + keyPathToName[\StudyRoomDetailDependency.fetchDetailStudyRoomUseCase] = "fetchDetailStudyRoomUseCase-any FetchDetailStudyRoomUseCase" + keyPathToName[\StudyRoomDetailDependency.applyStudyRoomSeatUseCase] = "applyStudyRoomSeatUseCase-any ApplyStudyRoomSeatUseCase" + keyPathToName[\StudyRoomDetailDependency.cancelStudyRoomSeatUseCase] = "cancelStudyRoomSeatUseCase-any CancelStudyRoomSeatUseCase" + } +} +extension StudyRoomListComponent: Registration { + public func registerItems() { + keyPathToName[\StudyRoomListDependency.fetchStudyRoomListUseCase] = "fetchStudyRoomListUseCase-any FetchStudyRoomListUseCase" + keyPathToName[\StudyRoomListDependency.fetchStudyAvailableTimeUseCase] = "fetchStudyAvailableTimeUseCase-any FetchStudyAvailableTimeUseCase" + keyPathToName[\StudyRoomListDependency.studyRoomDetailComponent] = "studyRoomDetailComponent-StudyRoomDetailComponent" + } +} extension RootComponent: Registration { public func registerItems() { keyPathToName[\RootDependency.signinComponent] = "signinComponent-SigninComponent" @@ -687,20 +745,10 @@ extension HomeComponent: Registration { keyPathToName[\HomeDependency.fetchWhetherNewNoticeUseCase] = "fetchWhetherNewNoticeUseCase-any FetchWhetherNewNoticeUseCase" } } -extension StudyRoomDetailComponent: Registration { - public func registerItems() { - keyPathToName[\StudyRoomDetailDependency.fetchStudyAvailableTimeUseCase] = "fetchStudyAvailableTimeUseCase-any FetchStudyAvailableTimeUseCase" - keyPathToName[\StudyRoomDetailDependency.fetchSeatTypesUseCase] = "fetchSeatTypesUseCase-any FetchSeatTypesUseCase" - keyPathToName[\StudyRoomDetailDependency.fetchDetailStudyRoomUseCase] = "fetchDetailStudyRoomUseCase-any FetchDetailStudyRoomUseCase" - keyPathToName[\StudyRoomDetailDependency.applyStudyRoomSeatUseCase] = "applyStudyRoomSeatUseCase-any ApplyStudyRoomSeatUseCase" - keyPathToName[\StudyRoomDetailDependency.cancelStudyRoomSeatUseCase] = "cancelStudyRoomSeatUseCase-any CancelStudyRoomSeatUseCase" - } -} -extension StudyRoomListComponent: Registration { +extension ApplyPageComponent: Registration { public func registerItems() { - keyPathToName[\StudyRoomListDependency.fetchStudyRoomListUseCase] = "fetchStudyRoomListUseCase-any FetchStudyRoomListUseCase" - keyPathToName[\StudyRoomListDependency.fetchStudyAvailableTimeUseCase] = "fetchStudyAvailableTimeUseCase-any FetchStudyAvailableTimeUseCase" - keyPathToName[\StudyRoomListDependency.studyRoomDetailComponent] = "studyRoomDetailComponent-StudyRoomDetailComponent" + keyPathToName[\ApplyPageDependency.studyRoomListComponent] = "studyRoomListComponent-StudyRoomListComponent" + keyPathToName[\ApplyPageDependency.remainApplyComponent] = "remainApplyComponent-RemainApplyComponent" } } extension AuthenticationEmailComponent: Registration { @@ -721,6 +769,14 @@ extension EnterInformationComponent: Registration { keyPathToName[\EnterInformationDependency.authenticationEmailComponent] = "authenticationEmailComponent-AuthenticationEmailComponent" } } +extension RemainApplyComponent: Registration { + public func registerItems() { + keyPathToName[\RemainApplyDependency.fetchMyRemainApplicationItemsUseCase] = "fetchMyRemainApplicationItemsUseCase-any FetchMyRemainApplicationItemsUseCase" + keyPathToName[\RemainApplyDependency.fetchRemainApplicationListUseCase] = "fetchRemainApplicationListUseCase-any FetchRemainApplicationListUseCase" + keyPathToName[\RemainApplyDependency.fetchRemainsAvailableTimeUseCase] = "fetchRemainsAvailableTimeUseCase-any FetchRemainsAvailableTimeUseCase" + keyPathToName[\RemainApplyDependency.remainingApplicationsChangesUseCase] = "remainingApplicationsChangesUseCase-any RemainingApplicationsChangesUseCase" + } +} extension NoticeListComponent: Registration { public func registerItems() { keyPathToName[\NoticeListDependency.fetchNoticeListUseCase] = "fetchNoticeListUseCase-any FetchNoticeListUseCase" @@ -770,14 +826,16 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi registerProviderFactory("^->AppComponent->RewardPointDetailComponent", factory87993268d9e212be8b1af47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->ChangeProfileComponent", factory239204ef0c47c0c68c97f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->CheckPasswordComponent", factorycb24ea072925f86bef40f47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->StudyRoomDetailComponent", factorya36f40c25dcb280bae0ff47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->StudyRoomListComponent", factory7451c5364e65ee2d46bbf47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->RootComponent", factory264bfc4d4cb6b0629b40f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->SigninComponent", factory2882a056d84a613debccf47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->HomeComponent", factory67229cdf0f755562b2b1f47b58f8f304c97af4d5) - registerProviderFactory("^->AppComponent->StudyRoomDetailComponent", factorya36f40c25dcb280bae0ff47b58f8f304c97af4d5) - registerProviderFactory("^->AppComponent->StudyRoomListComponent", factory7451c5364e65ee2d46bbf47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->ApplyPageComponent", factory45f688c5d4c7f313fc8df47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->AuthenticationEmailComponent", factory8798d0becd9d2870112af47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->ChangePasswordComponent", factoryab7c4d87dab53e0a51b9f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->EnterInformationComponent", factory359a960501e79e833f64f47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->RemainApplyComponent", factory9615846346c92a2f8176f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->NoticeListComponent", factorye14e687c08985bdffcd0f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->NoticeDetailComponent", factory3db143c2f80d621d5a7ff47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->FindIDComponent", factory8dd2f9e0b545ead35ecaf47b58f8f304c97af4d5) diff --git a/Projects/Features/ApplyFeature/Project.swift b/Projects/Features/ApplyFeature/Project.swift index 36ff2cfe..0d2d255a 100644 --- a/Projects/Features/ApplyFeature/Project.swift +++ b/Projects/Features/ApplyFeature/Project.swift @@ -5,6 +5,8 @@ let project = Project.makeModule( name: "ApplyFeature", product: .staticFramework, dependencies: [ - .Project.Features.BaseFeature + .Project.Features.BaseFeature, + .Project.Features.StudyRoomFeature, + .Project.Features.RemainApplyFeature ] ) diff --git a/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageComponent.swift b/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageComponent.swift new file mode 100644 index 00000000..96368444 --- /dev/null +++ b/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageComponent.swift @@ -0,0 +1,19 @@ +import SwiftUI +import StudyRoomFeature +import RemainApplyFeature +import NeedleFoundation + +public protocol ApplyPageDependency: Dependency { + var studyRoomListComponent: StudyRoomListComponent { get } + var remainApplyComponent: RemainApplyComponent { get } +} + +public final class ApplyPageComponent: Component { + public func makeView() -> some View { + ApplyPageView( + viewModel: ApplyPageViewModel(), + studyRoomListComponent: dependency.studyRoomListComponent, + remainApplyComponent: dependency.remainApplyComponent + ) + } +} diff --git a/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageView.swift b/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageView.swift new file mode 100644 index 00000000..81c291ef --- /dev/null +++ b/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageView.swift @@ -0,0 +1,103 @@ +import StudyRoomFeature +import RemainApplyFeature +import SwiftUI + +struct ApplyPageView: View { + @AppStorage("StudyRoomState") var studyRoomState: String? + @AppStorage("RemainState") var remainState: String? + @StateObject var viewModel: ApplyPageViewModel + @Environment(\.tabbarHidden) var tabbarHidden + + private let studyRoomListComponent: StudyRoomListComponent + private let remainApplyComponent: RemainApplyComponent + + init( + viewModel: ApplyPageViewModel, + studyRoomListComponent: StudyRoomListComponent, + remainApplyComponent: RemainApplyComponent + ) { + _viewModel = StateObject(wrappedValue: viewModel) + self.studyRoomListComponent = studyRoomListComponent + self.remainApplyComponent = remainApplyComponent + } + + var body: some View { + NavigationView { + VStack { + Spacer() + .frame(height: 1) + + ScrollView(showsIndicators: false) { + VStack(spacing: 30) { + Spacer() + .frame(height: 5) + + applyListCellView( + name: "자습실", + content: """ + 자습실 사용이 필요한 경우, 자습실 신청을 통해서 원하는 자리를 신청해 보세요. + """, + buttonTitle: "자습실 신청하기", + applyState: studyRoomState, + onTapped: { + viewModel.isNavigateToStudy.toggle() + } + ) + + applyListCellView( + name: "잔류", + content: """ + 주말 기숙사 잔류 여부를 확인하고, 잔류 신청을 통해서 잔류 또는 귀가를 신청해 보세요. + """, + buttonTitle: "잔류 신청하기", + applyState: remainState, + onTapped: { + viewModel.isNavigateToRemain.toggle() + } + ) + } + .padding(.horizontal, 24) + } + } + .navigationTitle("신청") + .navigationBarTitleDisplayMode(.inline) + .dmsBackground() + .onChange(of: viewModel.isNavigateToStudy) { newValue in + withAnimation { + tabbarHidden.wrappedValue = newValue + } + } + .onChange(of: viewModel.isNavigateToRemain) { newValue in + withAnimation { + tabbarHidden.wrappedValue = newValue + } + } + .navigate( + to: studyRoomListComponent.makeView(), + when: $viewModel.isNavigateToStudy + ) + .navigate( + to: remainApplyComponent.makeView(), + when: $viewModel.isNavigateToRemain + ) + .navigationViewStyle(.stack) + } + } + + @ViewBuilder + func applyListCellView( + name: String, + content: String, + buttonTitle: String, + applyState: String?, + onTapped: @escaping () -> Void + ) -> some View { + ApplyListCellView( + name: name, + content: content, + buttonTitle: buttonTitle, + applyState: applyState, + onTapped: onTapped + ) + } +} diff --git a/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageViewModel.swift b/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageViewModel.swift new file mode 100644 index 00000000..5bb8fbb2 --- /dev/null +++ b/Projects/Features/ApplyFeature/Sources/ApplyPage/ApplyPageViewModel.swift @@ -0,0 +1,7 @@ +import BaseFeature +import Combine + +final class ApplyPageViewModel: BaseViewModel { + @Published var isNavigateToStudy: Bool = false + @Published var isNavigateToRemain: Bool = false +} diff --git a/Projects/Features/ApplyFeature/Sources/ApplyPage/Component/ApplyListCellView.swift b/Projects/Features/ApplyFeature/Sources/ApplyPage/Component/ApplyListCellView.swift new file mode 100644 index 00000000..1ddc6420 --- /dev/null +++ b/Projects/Features/ApplyFeature/Sources/ApplyPage/Component/ApplyListCellView.swift @@ -0,0 +1,67 @@ +import SwiftUI +import StudyRoomFeature +import RemainApplyFeature +import DesignSystem + +struct ApplyListCellView: View { + @AppStorage("StudyRoomState") var studyRoomState: String? + @AppStorage("RemainState") var remainState: String? + var name: String + var content: String + var buttonTitle: String + var applyState: String? + var onTapped: () -> Void + + init( + name: String, + content: String, + buttonTitle: String, + applyState: String?, + onTapped: @escaping () -> Void + ) { + self.name = name + self.content = content + self.buttonTitle = buttonTitle + self.applyState = applyState + self.onTapped = onTapped + } + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .center) { + Text(name) + .dmsFont(.title(.title2), color: .GrayScale.gray7) + .frame(height: 32) + .padding(.vertical, 20) + .padding(.leading, 20) + + Spacer() + + Text(applyState ?? "") + .dmsFont(.etc(.button), color: .PrimaryVariant.primary) + .frame(height: 22) + .padding(.vertical, 6) + .padding(.horizontal, 14) + .background(Color.PrimaryVariant.lighten2) + .cornerRadius(24) + .padding(.trailing, 16) + .padding(.top, -2) + } + + Text(content) + .dmsFont(.body(.body3), color: .GrayScale.gray9) + .multilineTextAlignment(.leading) + .padding(.horizontal, 20) + + DMSWideButton( + text: buttonTitle, + color: .PrimaryVariant.primary, + action: onTapped + ) + .padding(20) + } + .background(Color.System.surface) + .cornerRadius(10) + .dmsShadow(style: .surface) + } +} diff --git a/Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListView.swift b/Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListView.swift deleted file mode 100644 index a2098afb..00000000 --- a/Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListView.swift +++ /dev/null @@ -1,66 +0,0 @@ -import DesignSystem -import DomainModule -import Utility -import SwiftUI - -struct StudyRoomListView: View { - @StateObject var viewModel: StudyRoomListViewModel - private let studyRoomDetailComponent: StudyRoomDetailComponent - @Environment(\.tabbarHidden) var tabbarHidden - - init( - viewModel: StudyRoomListViewModel, - studyRoomDetailComponent: StudyRoomDetailComponent - ) { - _viewModel = StateObject(wrappedValue: viewModel) - self.studyRoomDetailComponent = studyRoomDetailComponent - } - - var body: some View { - NavigationView { - VStack { - ScrollView { - if viewModel.isStudyRoomTime { - StudyRoomNoticeView(text: viewModel.rangeString) - } - LazyVStack(spacing: 16) { - Spacer() - .frame(height: 10) - ForEach(viewModel.studyRoomList, id: \.self) { studyRoomList in - Button { - viewModel.isNavigateDetail.toggle() - viewModel.studyRoomDetail = studyRoomList - } label: { - StudyRoomListCellView(studyRoomEntity: studyRoomList) - .padding(.top, 5) - .padding(.bottom, 10) - } - } - .padding(.horizontal, 24) - - Spacer() - .frame(height: 110) - } - } - .navigationTitle("자습실 신청") - .navigationBarTitleDisplayMode(.inline) - .dmsBackground() - .dmsToast(isShowing: $viewModel.isErrorOcuured, message: viewModel.errorMessage, style: .error) - .onAppear { - viewModel.fetchStudyRoomList() - viewModel.fetchStudyAvailableTime() - } - .onChange(of: viewModel.isNavigateDetail) { newValue in - withAnimation { - tabbarHidden.wrappedValue = newValue - } - } - .navigate( - to: studyRoomDetailComponent.makeView(studyRoomEntity: viewModel.studyRoomDetail), - when: $viewModel.isNavigateDetail - ) - } - } - .navigationViewStyle(.stack) - } -} diff --git a/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift b/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift index 23f5babb..d4c126e6 100644 --- a/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift +++ b/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift @@ -7,7 +7,7 @@ import MyPageFeature public protocol MainTabDependency: Dependency { var homeComponent: HomeComponent { get } - var studyRoomListComponent: StudyRoomListComponent { get } + var applyPageComponent: ApplyPageComponent { get } var noticeListComponent: NoticeListComponent { get } var myPageComponent: MyPageComponent { get } } @@ -16,7 +16,7 @@ public final class MainTabComponent: Component { public func makeView() -> some View { MainTabView( homeComponent: dependency.homeComponent, - studyRoomListComponent: dependency.studyRoomListComponent, + applyPageComponent: dependency.applyPageComponent, noticeComponent: dependency.noticeListComponent, myPageComponent: dependency.myPageComponent ) diff --git a/Projects/Features/MainTabFeature/Sources/MainTabView.swift b/Projects/Features/MainTabFeature/Sources/MainTabView.swift index 0fbbe12c..90742cfb 100644 --- a/Projects/Features/MainTabFeature/Sources/MainTabView.swift +++ b/Projects/Features/MainTabFeature/Sources/MainTabView.swift @@ -26,18 +26,18 @@ struct MainTabView: View { } private let homeComponent: HomeComponent - private let studyRoomListComponent: StudyRoomListComponent + private let applyPageComponent: ApplyPageComponent private let noticeComponent: NoticeListComponent private let myPageComponent: MyPageComponent init( homeComponent: HomeComponent, - studyRoomListComponent: StudyRoomListComponent, + applyPageComponent: ApplyPageComponent, noticeComponent: NoticeListComponent, myPageComponent: MyPageComponent ) { self.homeComponent = homeComponent - self.studyRoomListComponent = studyRoomListComponent + self.applyPageComponent = applyPageComponent self.noticeComponent = noticeComponent self.myPageComponent = myPageComponent } @@ -49,7 +49,7 @@ struct MainTabView: View { .tag(TabFlow.home) if appState.features.studyRoomService { - studyRoomListComponent.makeView() + applyPageComponent.makeView() .tag(TabFlow.apply) } diff --git a/Projects/Features/MyPageFeature/Sources/RewardPointDetail/RewardPointDetailView.swift b/Projects/Features/MyPageFeature/Sources/RewardPointDetail/RewardPointDetailView.swift index 9579351d..ea67dac5 100644 --- a/Projects/Features/MyPageFeature/Sources/RewardPointDetail/RewardPointDetailView.swift +++ b/Projects/Features/MyPageFeature/Sources/RewardPointDetail/RewardPointDetailView.swift @@ -38,7 +38,7 @@ struct RewardPointDetailView: View { .padding(.horizontal, 24) .padding(.bottom, 24) - ScrollView { + ScrollView(showsIndicators: false) { Spacer() .frame(height: 10) LazyVStack { diff --git a/Projects/Features/NoticeFeature/Sources/Detail/NoticeDetailView.swift b/Projects/Features/NoticeFeature/Sources/Detail/NoticeDetailView.swift index 9db06721..b0e1062b 100644 --- a/Projects/Features/NoticeFeature/Sources/Detail/NoticeDetailView.swift +++ b/Projects/Features/NoticeFeature/Sources/Detail/NoticeDetailView.swift @@ -7,7 +7,7 @@ struct NoticeDetailView: View { @Environment(\.dismiss) var dismiss var body: some View { - ScrollView { + ScrollView(showsIndicators: false) { VStack { VStack(alignment: .leading, spacing: 0) { Text(viewModel.title) @@ -29,7 +29,7 @@ struct NoticeDetailView: View { .dmsFont(.body(.body2), color: .GrayScale.gray6) .padding(.top, 24) - Spacer() + Spacer(minLength: 83) } } .dmsToast(isShowing: $viewModel.isErrorOcuured, message: viewModel.errorMessage, style: .error) diff --git a/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift index ece31f7a..a3ea8898 100644 --- a/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift +++ b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift @@ -33,7 +33,7 @@ struct NoticeListView: View { } .padding(.top, 12) - ScrollView { + ScrollView(showsIndicators: false) { LazyVStack { Spacer() .frame(height: 10) diff --git a/Projects/Features/RemainApplyFeature/Derived/InfoPlists/RemainApplyFeature-Info.plist b/Projects/Features/RemainApplyFeature/Derived/InfoPlists/RemainApplyFeature-Info.plist new file mode 100644 index 00000000..323e5ecf --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Derived/InfoPlists/RemainApplyFeature-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Projects/Features/RemainApplyFeature/Derived/InfoPlists/RemainApplyFeatureTests-Info.plist b/Projects/Features/RemainApplyFeature/Derived/InfoPlists/RemainApplyFeatureTests-Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Derived/InfoPlists/RemainApplyFeatureTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Projects/Features/RemainApplyFeature/Project.swift b/Projects/Features/RemainApplyFeature/Project.swift new file mode 100644 index 00000000..775c91d8 --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Project.swift @@ -0,0 +1,9 @@ +import ProjectDescription +import ProjectDescriptionHelpers + +let project = Project.makeModule( + name: "RemainApplyFeature", + product: .staticFramework, + dependencies: [ + .Project.Features.BaseFeature + ]) diff --git a/Projects/Features/RemainApplyFeature/Sources/Component/RemainApplyListCellView.swift b/Projects/Features/RemainApplyFeature/Sources/Component/RemainApplyListCellView.swift new file mode 100644 index 00000000..2f510032 --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Sources/Component/RemainApplyListCellView.swift @@ -0,0 +1,125 @@ +import DesignSystem +import DomainModule +import SwiftUI + +struct RemainApplyListCellView: View { + @StateObject var viewModel: RemainApplyViewModel + + let dummy1 = RemainOptionEntity( + id: "0", + title: "금요 귀가", + description: """ + 금요일 일과가 모두 끝나고 + 8시 30분 이후부터9시 30분까지 귀가하고 + 일요일 6시 30분 부터 9시 30분까지 귀사해야 합니다. + + 혹시나 개인 일정으로 부득이하기 금요일이 아닌, 토요일 + 또는 일요일에 귀가해야 하는 학생들은 사감 선생님께 + 말씀부탁드립니다. + """ + ) + let dummy2 = RemainOptionEntity( + id: "1", + title: "토요 귀가", + description: """ + 금요일 일과가 모두 끝나고 + 8시 30분 이후부터9시 30분까지 귀가하고 + 일요일 6시 30분 부터 9시 30분까지 귀사해야 합니다. + + 혹시나 개인 일정으로 부득이하기 금요일이 아닌, 토요일 + 또는 일요일에 귀가해야 하는 학생들은 사감 선생님께 + 말씀부탁드립니다. + """ + ) + let dummy3 = RemainOptionEntity( + id: "2", + title: "잔류", + description: """ + 금요일 일과가 모두 끝나고 + 8시 30분 이후부터9시 30분까지 귀가하고 + 일요일 6시 30분 부터 9시 30분까지 귀사해야 합니다. + + 혹시나 개인 일정으로 부득이하기 금요일이 아닌, 토요일 + 또는 일요일에 귀가해야 하는 학생들은 사감 선생님께 + 말씀부탁드립니다. + """ + ) + + var body: some View { + VStack(spacing: 12) { + remainApplyListCellView(list: dummy1, applyType: .friday) + + remainApplyListCellView(list: dummy2, applyType: .saturday) + + remainApplyListCellView(list: dummy3, applyType: .stay) + } + } + + @ViewBuilder + // swiftlint:disable function_body_length + func remainApplyListCellView(list: RemainOptionEntity, applyType: ApplyType) -> some View { + VStack { + HStack(alignment: .center) { + Button(action: { + viewModel.selectedType = list.title + viewModel.selectedNum = Int(list.id) ?? 10 + }, label: { + HStack { + Text(list.title) + .dmsFont(.title(.title2), color: viewModel.selectedNum == applyType.rawValue + ? .System.primary : .GrayScale.gray7) + .frame(height: 32) + .padding(.vertical, 14) + .padding(.horizontal, 20) + + if viewModel.isAlreadyApplied == true && viewModel.appliedNum == applyType.rawValue { + Text("신청 완료") + .dmsFont(.etc(.button), color: .PrimaryVariant.primary) + .frame(height: 22) + .padding(.vertical, 6) + .padding(.horizontal, 14) + .background(Color.PrimaryVariant.lighten2) + .cornerRadius(24) + } + } + + Spacer() + + Image(systemName: viewModel.isDetailTapped == true && viewModel.selectedNum == applyType.rawValue + ? "chevron.up" : "chevron.down") + .foregroundColor(viewModel.selectedNum == applyType.rawValue + ? .System.primary : .GrayScale.gray7) + .padding(.trailing, 25) + .onTapGesture { + viewModel.selectedNum = applyType.rawValue + viewModel.selectedType = list.title + viewModel.isDetailTapped.toggle() + } + }) + } + if viewModel.isDetailTapped == true && viewModel.selectedNum == applyType.rawValue { + Text(list.description) + .multilineTextAlignment(.leading) + .dmsFont(.body(.body3), color: .GrayScale.gray9) + .padding([.horizontal, .bottom], 20) + } + } + .background(Color.System.surface) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .inset(by: 1) + .stroke(viewModel.selectedNum == applyType.rawValue ? + Color.System.primary : .clear, lineWidth: 1.5) + ) + .padding(.bottom, 12) + .dmsShadow(style: .surface) + } + // swiftlint:enable function_body_length +} + +enum ApplyType: Int { + case friday = 0 + case saturday + case stay +} diff --git a/Projects/Features/RemainApplyFeature/Sources/Component/RemainApplyNoticeView.swift b/Projects/Features/RemainApplyFeature/Sources/Component/RemainApplyNoticeView.swift new file mode 100644 index 00000000..6d92c4bb --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Sources/Component/RemainApplyNoticeView.swift @@ -0,0 +1,27 @@ +import DesignSystem +import DomainModule +import SwiftUI + +struct RemainApplyNoticeView: View { + let notice: String + + var body: some View { + HStack(spacing: 12) { + Image(systemName: "megaphone") + .foregroundColor(.PrimaryVariant.primary) + .padding(.top, 15) + .padding(.bottom, 16) + .padding(.leading, 19) + + Text(notice) + .dmsFont(.body(.body3), color: .GrayScale.gray6) + .padding(.trailing, 18) + } + .background(Color.System.surface) + .cornerRadius(100) + .padding(.top, 11) + .padding(.bottom, 25) + .dmsShadow(style: .surface) + .padding(.horizontal, 8) + } +} diff --git a/Projects/Features/RemainApplyFeature/Sources/RemainApplyComponent.swift b/Projects/Features/RemainApplyFeature/Sources/RemainApplyComponent.swift new file mode 100644 index 00000000..429d4471 --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Sources/RemainApplyComponent.swift @@ -0,0 +1,23 @@ +import SwiftUI +import DomainModule +import NeedleFoundation + +public protocol RemainApplyDependency: Dependency { + var fetchMyRemainApplicationItemsUseCase: any FetchMyRemainApplicationItemsUseCase { get } + var fetchRemainApplicationListUseCase: any FetchRemainApplicationListUseCase { get } + var fetchRemainsAvailableTimeUseCase: any FetchRemainsAvailableTimeUseCase { get } + var remainingApplicationsChangesUseCase: any RemainingApplicationsChangesUseCase { get } +} + +public final class RemainApplyComponent: Component { + public func makeView() -> some View { + RemainApplyView( + viewModel: .init( + fetchMyRemainApplicationItemsUseCase: dependency.fetchMyRemainApplicationItemsUseCase, + fetchRemainApplicationListUseCase: dependency.fetchRemainApplicationListUseCase, + fetchRemainsAvailableTimeUseCase: dependency.fetchRemainsAvailableTimeUseCase, + remainingApplicationsChangesUseCase: dependency.remainingApplicationsChangesUseCase + ) + ) + } +} diff --git a/Projects/Features/RemainApplyFeature/Sources/RemainApplyView.swift b/Projects/Features/RemainApplyFeature/Sources/RemainApplyView.swift new file mode 100644 index 00000000..14c27428 --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Sources/RemainApplyView.swift @@ -0,0 +1,67 @@ +import DesignSystem +import SwiftUI + +struct RemainApplyView: View { + @AppStorage("RemainState") var remainState: String? + @StateObject var viewModel: RemainApplyViewModel + @Environment(\.dismiss) var dismiss + + init( + viewModel: RemainApplyViewModel + ) { + _viewModel = StateObject(wrappedValue: viewModel) + } + + var body: some View { + VStack { + Spacer() + .frame(height: 1) + + ScrollView(showsIndicators: false) { + if viewModel.isApplicationTime { + RemainApplyNoticeView(notice: viewModel.rangeString) + } + RemainApplyListCellView(viewModel: viewModel) + .padding(.horizontal, 24) + } + + DMSWideButton( + text: { + if viewModel.isAlreadyApplied && (viewModel.selectedType == viewModel.appliedState) { + return "신청 완료" + } else if viewModel.isAlreadyApplied && (viewModel.selectedType != viewModel.appliedState) { + return viewModel.selectedType + "로 변경하기" + } else { + return viewModel.selectedType + " 신청하기" + } + }(), + style: .contained, + color: { + if viewModel.isAlreadyApplied && (viewModel.selectedType == viewModel.appliedState) { + return .System.primary.opacity(0.5) + } else if viewModel.selectedType.isEmpty { + return .clear + } else { + return .System.primary + } + }(), + action: { + viewModel.appliedState = viewModel.selectedType + viewModel.isAlreadyApplied = (viewModel.isErrorOcuured ? false : true) + viewModel.appliedNum = viewModel.selectedNum + remainState = viewModel.appliedState + }) + .padding(.bottom, 71) + .padding(.horizontal, 24) + } + .navigationTitle("잔류 신청") + .navigationBarTitleDisplayMode(.inline) + .dmsBackground() + .dmsBackButton(dismiss: dismiss) + .dmsToast( + isShowing: $viewModel.isErrorOcuured, + message: viewModel.toastMessage, + style: .error + ) + } +} diff --git a/Projects/Features/RemainApplyFeature/Sources/RemainApplyViewModel.swift b/Projects/Features/RemainApplyFeature/Sources/RemainApplyViewModel.swift new file mode 100644 index 00000000..ae6297e8 --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Sources/RemainApplyViewModel.swift @@ -0,0 +1,46 @@ +import BaseFeature +import DomainModule +import DataMappingModule +import Combine +import SwiftUI + +final class RemainApplyViewModel: BaseViewModel { + @Published var selectedNum: Int = 10 + @Published var selectedType: String = "" + @AppStorage("isApplied") var isAlreadyApplied: Bool = false + @Published var appliedState: String = "" + @AppStorage("appliedNum") var appliedNum: Int = 10 + @Published var isDetailTapped: Bool = false + + @Published var isApplicationTime = true + @Published var isShowingToast = false + @Published var toastMessage = "잔류 신청 시간이 아닙니다." + + var startTime = "화 18:00" + var endTime = "목 18:00" + var rangeString: String { + let text = "잔류 신청 시간은 " + startTime + + " ~ " + endTime + " 까지 입니다." + return text + } + + private let fetchMyRemainApplicationItemsUseCase: any FetchMyRemainApplicationItemsUseCase + private let fetchRemainApplicationListUseCase: any FetchRemainApplicationListUseCase + private let fetchRemainsAvailableTimeUseCase: any FetchRemainsAvailableTimeUseCase + private let remainingApplicationsChangesUseCase: any RemainingApplicationsChangesUseCase + + public init( + fetchMyRemainApplicationItemsUseCase: any FetchMyRemainApplicationItemsUseCase, + fetchRemainApplicationListUseCase: any FetchRemainApplicationListUseCase, + fetchRemainsAvailableTimeUseCase: any FetchRemainsAvailableTimeUseCase, + remainingApplicationsChangesUseCase: any RemainingApplicationsChangesUseCase + ) { + self.fetchMyRemainApplicationItemsUseCase = fetchMyRemainApplicationItemsUseCase + self.fetchRemainApplicationListUseCase = fetchRemainApplicationListUseCase + self.fetchRemainsAvailableTimeUseCase = fetchRemainsAvailableTimeUseCase + self.remainingApplicationsChangesUseCase = remainingApplicationsChangesUseCase + } + + func onAppear() { + } +} diff --git a/Projects/Features/RemainApplyFeature/Tests/TargetTests.swift b/Projects/Features/RemainApplyFeature/Tests/TargetTests.swift new file mode 100644 index 00000000..b1cf7940 --- /dev/null +++ b/Projects/Features/RemainApplyFeature/Tests/TargetTests.swift @@ -0,0 +1,17 @@ +import XCTest + +class TargetTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + XCTAssertEqual("A", "A") + } + +} diff --git a/Projects/Features/StudyRoomFeature/Derived/InfoPlists/StudyRoomFeature-Info.plist b/Projects/Features/StudyRoomFeature/Derived/InfoPlists/StudyRoomFeature-Info.plist new file mode 100644 index 00000000..323e5ecf --- /dev/null +++ b/Projects/Features/StudyRoomFeature/Derived/InfoPlists/StudyRoomFeature-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Projects/Features/StudyRoomFeature/Derived/InfoPlists/StudyRoomFeatureTests-Info.plist b/Projects/Features/StudyRoomFeature/Derived/InfoPlists/StudyRoomFeatureTests-Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Projects/Features/StudyRoomFeature/Derived/InfoPlists/StudyRoomFeatureTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Projects/Features/StudyRoomFeature/Project.swift b/Projects/Features/StudyRoomFeature/Project.swift new file mode 100644 index 00000000..8ccfbcd9 --- /dev/null +++ b/Projects/Features/StudyRoomFeature/Project.swift @@ -0,0 +1,9 @@ +import ProjectDescription +import ProjectDescriptionHelpers + +let project = Project.makeModule( + name: "StudyRoomFeature", + product: .staticFramework, + dependencies: [ + .Project.Features.BaseFeature + ]) diff --git a/Projects/Features/ApplyFeature/Sources/Component/StudyRoomListCellView.swift b/Projects/Features/StudyRoomFeature/Sources/Component/StudyRoomListCellView.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/Component/StudyRoomListCellView.swift rename to Projects/Features/StudyRoomFeature/Sources/Component/StudyRoomListCellView.swift diff --git a/Projects/Features/ApplyFeature/Sources/Component/StudyRoomNoticeView.swift b/Projects/Features/StudyRoomFeature/Sources/Component/StudyRoomNoticeView.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/Component/StudyRoomNoticeView.swift rename to Projects/Features/StudyRoomFeature/Sources/Component/StudyRoomNoticeView.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButton.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButton.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButton.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButton.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButtonStyle.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButtonStyle.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButtonStyle.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/Seat/DMSSeatButtonStyle.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatDetailView.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatDetailView.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatDetailView.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatDetailView.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatTypeView.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatTypeView.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatTypeView.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatTypeView.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatView.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatView.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatView.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/Component/StudyRoomSeatView.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailComponent.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailComponent.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailComponent.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailComponent.swift diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailView.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailView.swift similarity index 96% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailView.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailView.swift index 0b764d77..468a1d99 100644 --- a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailView.swift +++ b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailView.swift @@ -4,6 +4,7 @@ import DomainModule import SwiftUI struct StudyRoomDetailView: View { + @AppStorage("StudyRoomState") var studyRoomState: String? @StateObject var viewModel: StudyRoomDetailViewModel @Environment(\.dismiss) var dismiss @Environment(\.tabbarHidden) var tabbarHidden @@ -54,7 +55,7 @@ struct StudyRoomDetailView: View { .padding(.bottom, 6) HStack(spacing: 10) { - DMSWideButton(text: "취소", style: .contained, color: .GrayScale.gray4) { + DMSWideButton(text: "취소", style: .contained, color: .GrayScale.gray6) { viewModel.cancelStudyRoomSeat() } diff --git a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailViewModel.swift b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailViewModel.swift similarity index 97% rename from Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailViewModel.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailViewModel.swift index fb9af465..73cb9a87 100644 --- a/Projects/Features/ApplyFeature/Sources/StudyRoomDetail/StudyRoomDetailViewModel.swift +++ b/Projects/Features/StudyRoomFeature/Sources/StudyRoomDetail/StudyRoomDetailViewModel.swift @@ -3,6 +3,7 @@ import DomainModule import DataMappingModule import Combine import Foundation +import SwiftUI final class StudyRoomDetailViewModel: BaseViewModel { @Published var studyRoomDetail: DetailStudyRoomEntity? @@ -12,6 +13,7 @@ final class StudyRoomDetailViewModel: BaseViewModel { @Published var isShowingToast = false @Published var toastMessage = "" @Published var selectedSeat: SeatEntity? + @AppStorage("StudyRoomState") var studyRoomState: String? let studyRoomEntity: StudyRoomEntity @@ -103,6 +105,7 @@ final class StudyRoomDetailViewModel: BaseViewModel { self?.selectedSeat = nil } onReceiveError: { [weak self] error in self?.toastMessage = error.localizedDescription + self?.studyRoomState = nil } } @@ -113,6 +116,7 @@ final class StudyRoomDetailViewModel: BaseViewModel { self?.fetchDetailStudyRoom() self?.isShowingToast = true self?.toastMessage = "자습실 취소가 완료되었습니다." + self?.studyRoomState = nil } onReceiveError: { [weak self] error in self?.toastMessage = error.localizedDescription } diff --git a/Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListComponent.swift b/Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListComponent.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListComponent.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListComponent.swift diff --git a/Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListView.swift b/Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListView.swift new file mode 100644 index 00000000..79128e19 --- /dev/null +++ b/Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListView.swift @@ -0,0 +1,69 @@ +import DesignSystem +import DomainModule +import Utility +import SwiftUI + +struct StudyRoomListView: View { + @StateObject var viewModel: StudyRoomListViewModel + private let studyRoomDetailComponent: StudyRoomDetailComponent + @Environment(\.tabbarHidden) var tabbarHidden + @Environment(\.dismiss) var dismiss + + init( + viewModel: StudyRoomListViewModel, + studyRoomDetailComponent: StudyRoomDetailComponent + ) { + _viewModel = StateObject(wrappedValue: viewModel) + self.studyRoomDetailComponent = studyRoomDetailComponent + } + + var body: some View { + VStack { + Spacer() + .frame(height: 1) + + ScrollView { + if viewModel.isStudyRoomTime { + StudyRoomNoticeView(text: viewModel.rangeString) + } + LazyVStack(spacing: 16) { + Spacer() + .frame(height: 10) + ForEach(viewModel.studyRoomList, id: \.self) { studyRoomList in + Button { + viewModel.isNavigateDetail.toggle() + viewModel.studyRoomDetail = studyRoomList + } label: { + StudyRoomListCellView(studyRoomEntity: studyRoomList) + .padding(.top, 5) + .padding(.bottom, 10) + } + } + .padding(.horizontal, 24) + + Spacer() + .frame(height: 110) + } + } + .navigationTitle("자습실 신청") + .navigationBarTitleDisplayMode(.inline) + .dmsBackButton(dismiss: dismiss) + .dmsBackground() + .dmsToast(isShowing: $viewModel.isErrorOcuured, message: viewModel.errorMessage, style: .error) + .onAppear { + viewModel.fetchStudyRoomList() + viewModel.fetchStudyAvailableTime() + } + .onChange(of: viewModel.isNavigateDetail) { newValue in + withAnimation { + tabbarHidden.wrappedValue = newValue + } + } + .navigate( + to: studyRoomDetailComponent.makeView(studyRoomEntity: viewModel.studyRoomDetail), + when: $viewModel.isNavigateDetail + ) + .navigationViewStyle(.stack) + } + } +} diff --git a/Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListViewModel.swift b/Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListViewModel.swift similarity index 100% rename from Projects/Features/ApplyFeature/Sources/StudyroomApplication/StudyRoomListViewModel.swift rename to Projects/Features/StudyRoomFeature/Sources/StudyroomApplication/StudyRoomListViewModel.swift diff --git a/Projects/Features/StudyRoomFeature/Tests/TargetTests.swift b/Projects/Features/StudyRoomFeature/Tests/TargetTests.swift new file mode 100644 index 00000000..b1cf7940 --- /dev/null +++ b/Projects/Features/StudyRoomFeature/Tests/TargetTests.swift @@ -0,0 +1,17 @@ +import XCTest + +class TargetTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + XCTAssertEqual("A", "A") + } + +} diff --git a/graph.png b/graph.png index 6f0a2d54..a47cd62e 100644 Binary files a/graph.png and b/graph.png differ