diff --git a/App/Sources/Application/DI/AppComponent.swift b/App/Sources/Application/DI/AppComponent.swift index 8e2d2a7a..56e5f14f 100644 --- a/App/Sources/Application/DI/AppComponent.swift +++ b/App/Sources/Application/DI/AppComponent.swift @@ -194,4 +194,8 @@ public extension AppComponent { var inputUniversityFactory: any InputUniversityFactory { InputUniversityComponent(parent: self) } + + var inputSchoolFactory: any InputSchoolFactory { + InputSchoolComponent(parent: self) + } } diff --git a/App/Sources/Application/NeedleGenerated.swift b/App/Sources/Application/NeedleGenerated.swift index 886f7708..4608d87d 100644 --- a/App/Sources/Application/NeedleGenerated.swift +++ b/App/Sources/Application/NeedleGenerated.swift @@ -535,6 +535,17 @@ private class WithdrawUserListDependencyc576fefb2eff9e703c66Provider: WithdrawUs private func factory4d07a7e30330c03d5d63f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return WithdrawUserListDependencyc576fefb2eff9e703c66Provider(appComponent: parent1(component) as! AppComponent) } +private class InputSchoolDependencye8d4bffe76e2533005e2Provider: InputSchoolDependency { + + + init() { + + } +} +/// ^->AppComponent->InputSchoolComponent +private func factorya02470c933733e398aeee3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { + return InputSchoolDependencye8d4bffe76e2533005e2Provider() +} private class FindPasswordDependency542eacce769b9dc25904Provider: FindPasswordDependency { var sendEmailCertificationLinkUseCase: any SendEmailCertificationLinkUseCase { return appComponent.sendEmailCertificationLinkUseCase @@ -723,6 +734,9 @@ private class SchoolListDependency96b276c3342c1aca3550Provider: SchoolListDepend var fetchSchoolListUseCase: any FetchSchoolListUseCase { return appComponent.fetchSchoolListUseCase } + var inputSchoolFactory: any InputSchoolFactory { + return appComponent.inputSchoolFactory + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -1149,6 +1163,11 @@ extension WithdrawUserListComponent: Registration { keyPathToName[\WithdrawUserListDependency.withdrawUserUseCase] = "withdrawUserUseCase-any WithdrawUserUseCase" } } +extension InputSchoolComponent: Registration { + public func registerItems() { + + } +} extension FindPasswordComponent: Registration { public func registerItems() { keyPathToName[\FindPasswordDependency.sendEmailCertificationLinkUseCase] = "sendEmailCertificationLinkUseCase-any SendEmailCertificationLinkUseCase" @@ -1220,6 +1239,7 @@ extension InquiryListComponent: Registration { extension SchoolListComponent: Registration { public func registerItems() { keyPathToName[\SchoolListDependency.fetchSchoolListUseCase] = "fetchSchoolListUseCase-any FetchSchoolListUseCase" + keyPathToName[\SchoolListDependency.inputSchoolFactory] = "inputSchoolFactory-any InputSchoolFactory" } } extension ActivityListComponent: Registration { @@ -1444,6 +1464,7 @@ extension AppComponent: Registration { localTable["universityListFactory-any UniversityListFactory"] = { [unowned self] in self.universityListFactory as Any } localTable["inputOrganizationFactory-any InputOrganizationFactory"] = { [unowned self] in self.inputOrganizationFactory as Any } localTable["inputUniversityFactory-any InputUniversityFactory"] = { [unowned self] in self.inputUniversityFactory as Any } + localTable["inputSchoolFactory-any InputSchoolFactory"] = { [unowned self] in self.inputSchoolFactory as Any } localTable["remoteWithdrawDataSource-any RemoteWithdrawDataSource"] = { [unowned self] in self.remoteWithdrawDataSource as Any } localTable["withdrawRepository-any WithdrawRepository"] = { [unowned self] in self.withdrawRepository as Any } localTable["fetchWithdrawUserListUseCase-any FetchWithdrawUserListUseCase"] = { [unowned self] in self.fetchWithdrawUserListUseCase as Any } @@ -1510,6 +1531,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi registerProviderFactory("^->AppComponent->ClubDetailComponent", factory1559652f8e80cfa88d06f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->SuccessSignUpComponent", factorybf219b153b668170161de3b0c44298fc1c149afb) registerProviderFactory("^->AppComponent->WithdrawUserListComponent", factory4d07a7e30330c03d5d63f47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->InputSchoolComponent", factorya02470c933733e398aeee3b0c44298fc1c149afb) registerProviderFactory("^->AppComponent->FindPasswordComponent", factory15775d8436b06b9741d1f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->LectureApplicantListComponent", factory78a87c10d14f7bbaaa9df47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->UserListComponent", factorycf08383b935d2e18f4c7f47b58f8f304c97af4d5) diff --git a/App/Sources/Feature/InputSchoolFeature/Interface/InputSchoolFactory.swift b/App/Sources/Feature/InputSchoolFeature/Interface/InputSchoolFactory.swift new file mode 100644 index 00000000..effe39ba --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Interface/InputSchoolFactory.swift @@ -0,0 +1,9 @@ +import SwiftUI + +public protocol InputSchoolFactory { + associatedtype SomeView: View + func makeView( + state: String, + schoolInfo: SchoolDetailInfoModel + ) -> SomeView +} diff --git a/App/Sources/Feature/InputSchoolFeature/Source/Component/ClubFormView.swift b/App/Sources/Feature/InputSchoolFeature/Source/Component/ClubFormView.swift new file mode 100644 index 00000000..4259df19 --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Source/Component/ClubFormView.swift @@ -0,0 +1,80 @@ +import SwiftUI + +struct ClubFormView: View { + let clubList: [ClubDetailModel] + let editAction: (Int) -> Void + let addClubAction: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + BitgouelText( + text: "동아리 목록", + font: .title3 + ) + + ScrollView { + LazyVStack(alignment: .leading, spacing: 16) { + ForEach(clubList, id: \.clubID) { club in + clubListRow( + clubID: club.clubID, + club: club.name + ) + } + + Button { + addClubAction() + } label: { + HStack { + Text("동아리 추가") + + Spacer() + + BitgouelAsset.Icons.add.swiftUIImage + .renderingMode(.template) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .foregroundColor(.bitgouel(.greyscale(.g10))) + .background(Color.bitgouel(.primary(.p5))) + .cornerRadius(8, corners: .allCorners) + } + } + } + } + } + + @ViewBuilder + func clubListRow( + clubID: Int, + club: String + ) -> some View { + HStack { + HStack { + BitgouelText( + text: club, + font: .text3 + ) + .padding(.horizontal, 20) + .padding(.vertical, 16) + + Spacer() + } + .overlay { + RoundedRectangle(cornerRadius: 8) + .stroke(Color.bitgouel(.greyscale(.g7))) + } + + Spacer() + + Button { + editAction(clubID) + } label: { + BitgouelAsset.Icons.penFill.swiftUIImage + .resizable() + .renderingMode(.template) + .foregroundColor(Color.bitgouel(.primary(.p5))) + .frame(width: 24, height: 24) + } + } + } +} diff --git a/App/Sources/Feature/InputSchoolFeature/Source/Component/InputDepartmentRow.swift b/App/Sources/Feature/InputSchoolFeature/Source/Component/InputDepartmentRow.swift new file mode 100644 index 00000000..46713983 --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Source/Component/InputDepartmentRow.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct InputDepartmentRow: View { + @Binding var text: String + let deleteAction: () -> Void + + var body: some View { + HStack(alignment: .center) { + BitgouelTextField( + "학과명 입력", + text: $text + ) + + Button { + deleteAction() + } label: { + BitgouelAsset.Icons.minusFill.swiftUIImage + .resizable() + .renderingMode(.template) + .foregroundColor(Color.bitgouel(.error(.e5))) + .frame(width: 24, height: 24) + } + } + } +} diff --git a/App/Sources/Feature/InputSchoolFeature/Source/Component/SchoolLineBottomSheet.swift b/App/Sources/Feature/InputSchoolFeature/Source/Component/SchoolLineBottomSheet.swift new file mode 100644 index 00000000..704a128f --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Source/Component/SchoolLineBottomSheet.swift @@ -0,0 +1,56 @@ +import SwiftUI +import Service + +public struct SchoolLineBottomSheet: View { + let selectedLine: LineType? + let lineList: [LineType] = LineType.allCases + let cancel: (Bool) -> Void + let selectLine: (LineType) -> Void + + public var body: some View { + VStack(alignment: .leading, spacing: 16) { + HStack { + BitgouelText( + text: "계열", + font: .title3 + ) + + Spacer() + + Button { + cancel(false) + } label: { + BitgouelAsset.Icons.cancel.swiftUIImage + .resizable() + .renderingMode(.template) + .foregroundColor(Color.bitgouel(.greyscale(.g4))) + .frame(width: 24, height: 24) + } + } + + ScrollView { + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(lineList, id: \.self) { line in + HStack { + BitgouelText( + text: line.display(), + font: .text2 + ) + + Spacer() + + BitgouelRadioButton( + isSelected: Binding( + get: { line == selectedLine }, + set: { _ in selectLine(line) } + ) + ) + } + .padding(.vertical, 24) + } + } + } + } + .padding(.horizontal, 28) + } +} diff --git a/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolComponent.swift b/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolComponent.swift new file mode 100644 index 00000000..e10d735b --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolComponent.swift @@ -0,0 +1,15 @@ +import NeedleFoundation +import SwiftUI + +public protocol InputSchoolDependency: Dependency {} + +public final class InputSchoolComponent: Component, InputSchoolFactory { + public func makeView(state: String, schoolInfo: SchoolDetailInfoModel) -> some View { + InputSchoolView( + viewModel: .init( + state: state, + schoolInfo: schoolInfo + ) + ) + } +} diff --git a/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolView.swift b/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolView.swift new file mode 100644 index 00000000..4b122c8f --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolView.swift @@ -0,0 +1,200 @@ +import SwiftUI +import NukeUI + +struct InputSchoolView: View { + @Environment(\.dismiss) var dismiss + @StateObject var viewModel: InputSchoolViewModel + + init(viewModel: InputSchoolViewModel) { + _viewModel = StateObject(wrappedValue: viewModel) + } + + var body: some View { + ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 20) { + VStack(alignment: .leading, spacing: 0) { + Button { + viewModel.updateIsShowingImagePicker(isShowing: true) + } label: { + LazyImage(source: viewModel.schoolInfo.logoImageURL) { state in + if let image = state.image { + image + .resizingMode(.aspectFit) + } else { + schoolLogoImage() + } + } + } + .frame(width: 80, height: 80) + + + BitgouelTextField( + "학교 이름 입력", + text: $viewModel.schoolName + ) + .padding(.top, 16) + + PickerTextField( + "학교 계열 선택", + text: (viewModel.selectedLine?.display() ?? "") + ) { + viewModel.updateIsShowingLineBottomSheet(isShowing: true) + } + } + .padding(.top, 16) + + BitgouelText( + text: "학과 목록", + font: .title3 + ) + + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(viewModel.departmentList.indices, id: \.self) { index in + InputDepartmentRow( + text: $viewModel.departmentList[index] + ) { + viewModel.deleteDepartment(index: index) + } + } + + Button { + viewModel.appendDepartment() + } label: { + HStack { + Text("학과 추가") + + Spacer() + + BitgouelAsset.Icons.add.swiftUIImage + .renderingMode(.template) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .foregroundColor(.bitgouel(.greyscale(.g10))) + .background(Color.bitgouel(.primary(.p5))) + .cornerRadius(8, corners: .allCorners) + } + } + + if viewModel.state == "수정" { + ClubFormView(clubList: viewModel.schoolInfo.clubList + ) { clubID in + viewModel.updateIsPresentedInputClubView(isPresented: true) + } addClubAction: { + viewModel.updateIsPresentedInputClubView(isPresented: true) + } + } + } + } + .overlay(alignment: .bottom) { + renderFormButton() + } + .padding(.horizontal, 28) + .navigationTitle("학교 \(viewModel.state)") + .sheet(isPresented: $viewModel.isShowingImagePicker, onDismiss: { viewModel.loadImage() }) { + ImagePicker(image: $viewModel.selectedUIImage) + } + .onAppear { + if viewModel.state == "수정" { + viewModel.onApper() + } + } + .bitgouelBottomSheet(isShowing: $viewModel.isShowingLineBottomSheet) { + SchoolLineBottomSheet( + selectedLine: viewModel.selectedLine + ) { cancel in + viewModel.updateIsShowingLineBottomSheet(isShowing: cancel) + } selectLine: { line in + viewModel.updateSelectedLine(line: line) + viewModel.updateIsShowingLineBottomSheet(isShowing: false) + } + } + .bitgouelAlert( + title: "학교를 삭제하시겠습니까?", + description: "", + isShowing: $viewModel.isShowingDeleteAlert, + alertActions: [ + .init( + text: "취소", + style: .cancel, + action: { viewModel.updateIsShowingDeleteAlert(isShowing: false) + } + ), + .init( + text: "삭제", + style: .error, + action: { + viewModel.deleteSchool { + viewModel.updateIsShowingDeleteAlert(isShowing: false) + dismiss() + } + } + ) + ] + ) + } + + @ViewBuilder + func renderFormButton() -> some View { + if viewModel.state == "수정" { + HStack(spacing: 8) { + DeactivateButton( + text: "학교 삭제", + buttonType: .minus + ) { + viewModel.updateIsShowingDeleteAlert(isShowing: true) + } + + ActivateButton( + text: "수정 완료", + buttonType: .check + ) { + viewModel.modifySchool { + dismiss() + } + } + } + } else { + BitgouelButton(text: "다음으로", style: .primary) { + viewModel.createdSchool { + viewModel.updateIsPresentedSuccessView(isPresented: true) + } + } + } + } + + @ViewBuilder + func schoolLogoImage() -> some View { + if let image = viewModel.image { + ZStack { + image + .resizable() + .frame(width: 80, height: 80) + .cornerRadius(8, corners: .allCorners) + .blur(radius: 1) + + BitgouelAsset.Icons.add.swiftUIImage + .resizable() + .renderingMode(.template) + .foregroundColor(.white) + .frame(width: 24, height: 24) + } + } else { + VStack(spacing: 4) { + BitgouelAsset.Icons.add.swiftUIImage + .resizable() + .renderingMode(.template) + .frame(width: 24, height: 24) + + BitgouelText( + text: "학교 로고", + font: .caption + ) + } + .foregroundColor(Color.bitgouel(.greyscale(.g4))) + .padding(.init(top: 20, leading: 14, bottom: 12, trailing: 14)) + .background(Color.bitgouel(.greyscale(.g9))) + .cornerRadius(8, corners: .allCorners) + } + } +} diff --git a/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolViewModel.swift b/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolViewModel.swift new file mode 100644 index 00000000..135db168 --- /dev/null +++ b/App/Sources/Feature/InputSchoolFeature/Source/InputSchoolViewModel.swift @@ -0,0 +1,88 @@ +import Foundation +import SwiftUI +import UIKit +import Service + +final class InputSchoolViewModel: BaseViewModel { + @Published var isShowingImagePicker: Bool = false + @Published var isShowingLineBottomSheet: Bool = false + @Published var isShowingDeleteAlert: Bool = false + @Published var isPresentedSuccessView: Bool = false + @Published var isPresentedInputClubView: Bool = false + @Published var selectedUIImage: UIImage? + @Published var image: Image? + @Published var schoolName: String = "" + @Published var selectedLine: LineType? + @Published var departmentList: [String] = [] + let state: String + let schoolInfo: SchoolDetailInfoModel + + init( + state: String, + schoolInfo: SchoolDetailInfoModel + ) { + self.state = state + self.schoolInfo = schoolInfo + } + + func loadImage() { + guard let selectedImage = selectedUIImage else { return } + image = Image(uiImage: selectedImage) + } + + func updateIsShowingImagePicker(isShowing: Bool) { + isShowingImagePicker = isShowing + } + + func updateIsShowingLineBottomSheet(isShowing: Bool) { + isShowingLineBottomSheet = isShowing + } + + func updateIsShowingDeleteAlert(isShowing: Bool) { + isShowingDeleteAlert = isShowing + } + + func updateIsPresentedSuccessView(isPresented: Bool) { + isPresentedSuccessView = isPresented + } + + func updateIsPresentedInputClubView(isPresented: Bool) { + isPresentedInputClubView = isPresented + } + + func updateSelectedLine(line: LineType) { + selectedLine = line + } + + func deleteDepartment(index: Int) { + departmentList.remove(at: index) + } + + func appendDepartment() { + departmentList.append("") + } + + func onApper() { + schoolName = schoolInfo.name + selectedLine = schoolInfo.line + departmentList = schoolInfo.departmentList + } + + @MainActor + func createdSchool(_ success: @escaping () -> Void) { + #warning("학교 생성 기능 추가") + success() + } + + @MainActor + func deleteSchool(_ success: @escaping () -> Void) { + #warning("학교 삭제 기능 추가") + success() + } + + @MainActor + func modifySchool(_ success: @escaping () -> Void) { + #warning("학교 수정 기능 추가") + success() + } +} diff --git a/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/BottomSheet/LineBottomSheet.swift b/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/BottomSheet/LectureLineBottomSheet.swift similarity index 98% rename from App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/BottomSheet/LineBottomSheet.swift rename to App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/BottomSheet/LectureLineBottomSheet.swift index 1c4690f8..eef6e1cf 100644 --- a/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/BottomSheet/LineBottomSheet.swift +++ b/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/BottomSheet/LectureLineBottomSheet.swift @@ -1,7 +1,7 @@ import Service import SwiftUI -struct LineBottomSheet: View { +struct LectureLineBottomSheet: View { var selectedLine: String @Binding var keyword: String @State var isSelected: Bool = false diff --git a/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/PickerTextField.swift b/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/PickerTextField.swift index d7f64fbb..f9fa61a9 100644 --- a/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/PickerTextField.swift +++ b/App/Sources/Feature/LectureDetailSettingFeature/Sources/Component/PickerTextField.swift @@ -24,13 +24,14 @@ struct PickerTextField: View { .bitgouelFont(.text3) .foregroundColor(Color.bitgouel(.greyscale(.g0))) .padding(.horizontal, 20) - + .padding(.vertical, 16) + Spacer() } - .padding(.vertical, 16) .overlay { RoundedRectangle(cornerRadius: 8) .stroke(Color.bitgouel(.greyscale(.g7))) + if text.isEmpty { HStack { Text(placeholder) diff --git a/App/Sources/Feature/LectureDetailSettingFeature/Sources/LectureDetailSettingView.swift b/App/Sources/Feature/LectureDetailSettingFeature/Sources/LectureDetailSettingView.swift index 602d3d23..60dfbbf6 100644 --- a/App/Sources/Feature/LectureDetailSettingFeature/Sources/LectureDetailSettingView.swift +++ b/App/Sources/Feature/LectureDetailSettingFeature/Sources/LectureDetailSettingView.swift @@ -188,7 +188,7 @@ struct LectureDetailSettingView: View { } } .bitgouelBottomSheet(isShowing: $viewModel.isShowingLineBottomSheet) { - LineBottomSheet( + LectureLineBottomSheet( selectedLine: viewModel.selectedLine, keyword: $viewModel.keyword, lineList: viewModel.lineList diff --git a/App/Sources/Feature/SchoolListFeature/Source/Model/SchoolDetailInfoModel.swift b/App/Sources/Feature/SchoolListFeature/Source/Model/SchoolDetailInfoModel.swift index 56938056..1d48917a 100644 --- a/App/Sources/Feature/SchoolListFeature/Source/Model/SchoolDetailInfoModel.swift +++ b/App/Sources/Feature/SchoolListFeature/Source/Model/SchoolDetailInfoModel.swift @@ -2,20 +2,26 @@ import Foundation import Service public struct SchoolDetailInfoModel: Equatable { + public let schoolID: Int public let logoImageURL: String public let name: String - public let line: String + public let line: LineType + public let departmentList: [String] public let clubList: [ClubDetailModel] public init( + schoolID: Int, logoImageURL: String, name: String, - line: String, + line: LineType, + departmentList: [String], clubList: [ClubDetailModel] ) { + self.schoolID = schoolID self.logoImageURL = logoImageURL self.name = name self.line = line + self.departmentList = departmentList self.clubList = clubList } } diff --git a/App/Sources/Feature/SchoolListFeature/Source/SchoolListComponent.swift b/App/Sources/Feature/SchoolListFeature/Source/SchoolListComponent.swift index a4cb727d..05cf63eb 100644 --- a/App/Sources/Feature/SchoolListFeature/Source/SchoolListComponent.swift +++ b/App/Sources/Feature/SchoolListFeature/Source/SchoolListComponent.swift @@ -4,6 +4,7 @@ import SwiftUI public protocol SchoolListDependency: Dependency { var fetchSchoolListUseCase: any FetchSchoolListUseCase { get } + var inputSchoolFactory: any InputSchoolFactory { get } } public final class SchoolListComponent: Component, SchoolListFactory { @@ -11,7 +12,8 @@ public final class SchoolListComponent: Component, SchoolL SchoolListView( viewModel: .init( fetchSchoolListUseCase: dependency.fetchSchoolListUseCase - ) + ), + inputSchoolFactory: dependency.inputSchoolFactory ) } } diff --git a/App/Sources/Feature/SchoolListFeature/Source/SchoolListView.swift b/App/Sources/Feature/SchoolListFeature/Source/SchoolListView.swift index 5be786c4..a1d9ca01 100644 --- a/App/Sources/Feature/SchoolListFeature/Source/SchoolListView.swift +++ b/App/Sources/Feature/SchoolListFeature/Source/SchoolListView.swift @@ -4,8 +4,14 @@ struct SchoolListView: View { @EnvironmentObject var adminPageState: AdminPageState @StateObject var viewModel: SchoolListViewModel - init(viewModel: SchoolListViewModel) { + private let inputSchoolFactory: any InputSchoolFactory + + init( + viewModel: SchoolListViewModel, + inputSchoolFactory: any InputSchoolFactory + ) { _viewModel = StateObject(wrappedValue: viewModel) + self.inputSchoolFactory = inputSchoolFactory } var body: some View { @@ -24,9 +30,11 @@ struct SchoolListView: View { .onTapGesture { viewModel.updateSchoolDetailInfo( info: .init( + schoolID: school.schoolID, logoImageURL: school.logoImageURL, name: school.schoolName, - line: school.line.display(), + line: school.line, + departmentList: school.departments, clubList: school.clubs.map { .init( clubID: $0.clubID, @@ -89,6 +97,10 @@ struct SchoolListView: View { viewModel.updateIsShowingAdminPageBottomSheet(isShowing: cancel) } } + .navigate( + to: inputSchoolFactory.makeView(state: viewModel.state, schoolInfo: viewModel.schoolInfo).eraseToAnyView(), + when: $viewModel.isPresentedInputSchoolInfoView + ) } @ViewBuilder diff --git a/App/Sources/Feature/SchoolListFeature/Source/SchoolListViewModel.swift b/App/Sources/Feature/SchoolListFeature/Source/SchoolListViewModel.swift index 48387c46..862e52aa 100644 --- a/App/Sources/Feature/SchoolListFeature/Source/SchoolListViewModel.swift +++ b/App/Sources/Feature/SchoolListFeature/Source/SchoolListViewModel.swift @@ -7,9 +7,11 @@ final class SchoolListViewModel: BaseViewModel { @Published var isPresentedInputSchoolInfoView: Bool = false @Published var selectedPage: AdminPageFlow = .school @Published var schoolInfo: SchoolDetailInfoModel = .init( + schoolID: 0, logoImageURL: "", name: "", - line: "", + line: .agriculturalLifeHealthCare, + departmentList: [], clubList: [.init(clubID: 0, name: "", field: .culture)] ) @Published var schoolList: [SchoolListEntity] = [] @@ -31,6 +33,7 @@ final class SchoolListViewModel: BaseViewModel { func updateIsPresentedInputSchoolInfoView(isPresented: Bool, state: String) { isPresentedInputSchoolInfoView = isPresented + self.state = state } func updateSchoolDetailInfo(info: SchoolDetailInfoModel) { diff --git a/App/Sources/Feature/SchoolListFeature/Source/View/SchoolDetailBottomSheet.swift b/App/Sources/Feature/SchoolListFeature/Source/View/SchoolDetailBottomSheet.swift index d8083712..5deb5eca 100644 --- a/App/Sources/Feature/SchoolListFeature/Source/View/SchoolDetailBottomSheet.swift +++ b/App/Sources/Feature/SchoolListFeature/Source/View/SchoolDetailBottomSheet.swift @@ -35,16 +35,28 @@ public struct SchoolDetailBottomSheet: View { font: .text1 ) - Text(schoolInfo.line) + Text(schoolInfo.line.display()) .bitgouelFont(.text3, color: .greyscale(.g4)) } + BitgouelText( + text: "학과 목록", + font: .text1 + ) + + LazyVStack(alignment: .leading, spacing: 16) { + ForEach(schoolInfo.departmentList, id: \.self) { department in + Text(department) + .bitgouelFont(.text3, color: .greyscale(.g4)) + } + } + BitgouelText( text: "동아리 목록", font: .text1 ) - LazyVStack(alignment: .leading, spacing: 8) { + LazyVStack(alignment: .leading, spacing: 18) { ForEach(schoolInfo.clubList, id: \.clubID) { club in clubListRow( clubName: club.name, @@ -82,6 +94,6 @@ public struct SchoolDetailBottomSheet: View { font: .caption ) } - .foregroundColor(Color.bitgouel(.greyscale(.g7))) + .foregroundColor(Color.bitgouel(.greyscale(.g4))) } } diff --git a/App/Sources/Utils/Image/ImagePicker.swift b/App/Sources/Utils/Image/ImagePicker.swift new file mode 100644 index 00000000..f7170d4f --- /dev/null +++ b/App/Sources/Utils/Image/ImagePicker.swift @@ -0,0 +1,37 @@ +import SwiftUI + +struct ImagePicker: UIViewControllerRepresentable { + @Binding var image: UIImage? + @Environment(\.presentationMode) var mode + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIViewController(context: Context) -> some UIViewController { + let picker = UIImagePickerController() + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + + } +} + +extension ImagePicker { + class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { + let parent: ImagePicker + + init(_ parent: ImagePicker) { + self.parent = parent + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + guard let image = info[.originalImage] as? UIImage else { return } + parent.image = image + parent.mode.wrappedValue.dismiss() + } + } +} + diff --git a/Service/Sources/Base/Enum/LineType.swift b/Service/Sources/Base/Enum/LineType.swift index 9d76ec63..260bc95c 100644 --- a/Service/Sources/Base/Enum/LineType.swift +++ b/Service/Sources/Base/Enum/LineType.swift @@ -1,19 +1,19 @@ import Foundation -public enum LineType: String, Codable { +public enum LineType: String, Codable, CaseIterable { + case customizedIndustrialDemand = "CUSTOMIZED_INDUSTRIAL_DEMAND" case industry = "INDUSTRY" case commerce = "COMMERCE" case agriculturalLifeHealthCare = "AGRICULTURAL_LIFE_HEALTH_CARE" - case customizedIndustrialDemand = "CUSTOMIZED_INDUSTRIAL_DEMAND" } public extension LineType { func display() -> String { switch self { - case .industry: return "공업계열" - case .commerce: return "상업계열" - case .agriculturalLifeHealthCare: return "농업생명/보건의료계열" - case .customizedIndustrialDemand: return "산업수요 맞춤형계열" + case .customizedIndustrialDemand: return "I. 산업수요 맞춤형" + case .industry: return "II. 공업계열" + case .commerce: return "III. 상업계열" + case .agriculturalLifeHealthCare: return "IV. 농업생명/보건의료계열" } } } diff --git a/Service/Sources/Data/DataSource/Club/RemoteClubDataSourceImpl.swift b/Service/Sources/Data/DataSource/Club/RemoteClubDataSourceImpl.swift index ed4f7745..193edb88 100644 --- a/Service/Sources/Data/DataSource/Club/RemoteClubDataSourceImpl.swift +++ b/Service/Sources/Data/DataSource/Club/RemoteClubDataSourceImpl.swift @@ -23,7 +23,7 @@ public final class RemoteClubDataSourceImpl: BaseRemoteDataSource, Remo ).toDomain() } - public func createdClub(schoolID: String, req: CreatedClubRequestDTO) async throws { + public func createdClub(schoolID: Int, req: CreatedClubRequestDTO) async throws { try await request(.createdClub(schoolID: schoolID, req: req)) } diff --git a/Service/Sources/Data/DataSource/School/RemoteSchoolDataSourceImpl.swift b/Service/Sources/Data/DataSource/School/RemoteSchoolDataSourceImpl.swift index c213caee..503a9fe6 100644 --- a/Service/Sources/Data/DataSource/School/RemoteSchoolDataSourceImpl.swift +++ b/Service/Sources/Data/DataSource/School/RemoteSchoolDataSourceImpl.swift @@ -13,11 +13,11 @@ public final class RemoteSchoolDataSourceImpl: BaseRemoteDataSource, try await request(.createdSchool(req: req)) } - public func modifySchool(schoolID: String, req: ModifySchoolRequestDTO) async throws { + public func modifySchool(schoolID: Int, req: ModifySchoolRequestDTO) async throws { try await request(.modifySchool(schoolID: schoolID, req: req)) } - public func deleteSchool(schoolID: String) async throws { + public func deleteSchool(schoolID: Int) async throws { try await request(.deleteSchool(schoolID: schoolID)) } } diff --git a/Service/Sources/Data/Repository/Club/ClubRepositoryImpl.swift b/Service/Sources/Data/Repository/Club/ClubRepositoryImpl.swift index e48f576a..dab19eb4 100644 --- a/Service/Sources/Data/Repository/Club/ClubRepositoryImpl.swift +++ b/Service/Sources/Data/Repository/Club/ClubRepositoryImpl.swift @@ -23,7 +23,7 @@ public struct ClubRepositoryImpl: ClubRepository { try await remoteClubDataSource.fetchStudentDetailByClub(clubID: clubID, studentID: studentID) } - public func createdClub(schoolID: String, req: CreatedClubRequestDTO) async throws { + public func createdClub(schoolID: Int, req: CreatedClubRequestDTO) async throws { try await remoteClubDataSource.createdClub(schoolID: schoolID, req: req) } diff --git a/Service/Sources/Data/Repository/School/SchoolRepositoryImpl.swift b/Service/Sources/Data/Repository/School/SchoolRepositoryImpl.swift index e0d4fe8b..b298e103 100644 --- a/Service/Sources/Data/Repository/School/SchoolRepositoryImpl.swift +++ b/Service/Sources/Data/Repository/School/SchoolRepositoryImpl.swift @@ -19,11 +19,11 @@ public struct SchoolRepositoryImpl: SchoolRepository { try await remoteSchoolDataSource.createdSchool(req: req) } - public func modifySchool(schoolID: String, req: ModifySchoolRequestDTO) async throws { + public func modifySchool(schoolID: Int, req: ModifySchoolRequestDTO) async throws { try await remoteSchoolDataSource.modifySchool(schoolID: schoolID, req: req) } - public func deleteSchool(schoolID: String) async throws { + public func deleteSchool(schoolID: Int) async throws { try await remoteSchoolDataSource.deleteSchool(schoolID: schoolID) } } diff --git a/Service/Sources/Data/UseCase/Club/CreatedClubUseCaseImpl.swift b/Service/Sources/Data/UseCase/Club/CreatedClubUseCaseImpl.swift index 1c5deddd..2a4e3fab 100644 --- a/Service/Sources/Data/UseCase/Club/CreatedClubUseCaseImpl.swift +++ b/Service/Sources/Data/UseCase/Club/CreatedClubUseCaseImpl.swift @@ -7,7 +7,7 @@ public struct CreatedClubUseCaseImpl: CreatedClubUseCase { self.clubRepository = clubRepository } - public func callAsFunction(schoolID: String, req: CreatedClubRequestDTO) async throws { + public func callAsFunction(schoolID: Int, req: CreatedClubRequestDTO) async throws { try await clubRepository.createdClub(schoolID: schoolID, req: req) } } diff --git a/Service/Sources/Data/UseCase/School/DeleteSchoolUseCaseImpl.swift b/Service/Sources/Data/UseCase/School/DeleteSchoolUseCaseImpl.swift index a71c90c2..53509217 100644 --- a/Service/Sources/Data/UseCase/School/DeleteSchoolUseCaseImpl.swift +++ b/Service/Sources/Data/UseCase/School/DeleteSchoolUseCaseImpl.swift @@ -7,7 +7,7 @@ public struct DeleteSchoolUseCaseImpl: DeleteSchoolUseCase { self.schoolRepository = schoolRepository } - public func callAsFunction(schoolID: String) async throws { + public func callAsFunction(schoolID: Int) async throws { try await schoolRepository.deleteSchool(schoolID: schoolID) } } diff --git a/Service/Sources/Data/UseCase/School/ModifySchoolUseCaseImpl.swift b/Service/Sources/Data/UseCase/School/ModifySchoolUseCaseImpl.swift index 53fb3e46..561a789d 100644 --- a/Service/Sources/Data/UseCase/School/ModifySchoolUseCaseImpl.swift +++ b/Service/Sources/Data/UseCase/School/ModifySchoolUseCaseImpl.swift @@ -7,7 +7,7 @@ public struct ModifySchoolUseCaseImpl: ModifySchoolUseCase { self.schoolRepository = schoolRepository } - public func callAsFunction(schoolID: String, req: ModifySchoolRequestDTO) async throws { + public func callAsFunction(schoolID: Int, req: ModifySchoolRequestDTO) async throws { try await schoolRepository.modifySchool(schoolID: schoolID, req: req) } } diff --git a/Service/Sources/Domain/ClubDomain/API/ClubAPI.swift b/Service/Sources/Domain/ClubDomain/API/ClubAPI.swift index b21ea473..5c10b068 100644 --- a/Service/Sources/Domain/ClubDomain/API/ClubAPI.swift +++ b/Service/Sources/Domain/ClubDomain/API/ClubAPI.swift @@ -6,7 +6,7 @@ public enum ClubAPI { case fetchClubDetail(clubID: Int) case fetchStudentListByClub case fetchStudentDetailByClub(clubID: Int, studentID: String) - case createdClub(schoolID: String, req: CreatedClubRequestDTO) + case createdClub(schoolID: Int, req: CreatedClubRequestDTO) case modifyClub(clubID: Int, req: ModifyClubRequestDTO) case deleteClub(clubID: Int) } diff --git a/Service/Sources/Domain/ClubDomain/DTO/Request/ModifyClubRequestDTO.swift b/Service/Sources/Domain/ClubDomain/DTO/Request/ModifyClubRequestDTO.swift index 13b53b40..7ea5579c 100644 --- a/Service/Sources/Domain/ClubDomain/DTO/Request/ModifyClubRequestDTO.swift +++ b/Service/Sources/Domain/ClubDomain/DTO/Request/ModifyClubRequestDTO.swift @@ -3,12 +3,12 @@ import Foundation public struct ModifyClubRequestDTO: Encodable { public let name: String public let field: FieldType - public let schoolID: String + public let schoolID: Int public init( name: String, field: FieldType, - schoolID: String + schoolID: Int ) { self.name = name self.field = field diff --git a/Service/Sources/Domain/ClubDomain/DataSource/RemoteClubDataSource.swift b/Service/Sources/Domain/ClubDomain/DataSource/RemoteClubDataSource.swift index 7164e79e..951354e7 100644 --- a/Service/Sources/Domain/ClubDomain/DataSource/RemoteClubDataSource.swift +++ b/Service/Sources/Domain/ClubDomain/DataSource/RemoteClubDataSource.swift @@ -5,7 +5,7 @@ public protocol RemoteClubDataSource: BaseRemoteDataSource { func fetchClubDetail(clubID: Int) async throws -> ClubDetailEntity func fetchStudentListByClub() async throws -> ClubDetailEntity func fetchStudentDetailByClub(clubID: Int, studentID: String) async throws -> StudentDetailByClubEntity - func createdClub(schoolID: String, req: CreatedClubRequestDTO) async throws + func createdClub(schoolID: Int, req: CreatedClubRequestDTO) async throws func modifyClub(clubID: Int, req: ModifyClubRequestDTO) async throws func deleteClub(clubID: Int) async throws } diff --git a/Service/Sources/Domain/ClubDomain/Repository/ClubRepository.swift b/Service/Sources/Domain/ClubDomain/Repository/ClubRepository.swift index e64f23d1..afef3195 100644 --- a/Service/Sources/Domain/ClubDomain/Repository/ClubRepository.swift +++ b/Service/Sources/Domain/ClubDomain/Repository/ClubRepository.swift @@ -5,7 +5,7 @@ public protocol ClubRepository { func fetchClubDetail(clubID: Int) async throws -> ClubDetailEntity func fetchStudentListByClub() async throws -> ClubDetailEntity func fetchStudentDetailByClub(clubID: Int, studentID: String) async throws -> StudentDetailByClubEntity - func createdClub(schoolID: String, req: CreatedClubRequestDTO) async throws + func createdClub(schoolID: Int, req: CreatedClubRequestDTO) async throws func modifyClub(clubID: Int, req: ModifyClubRequestDTO) async throws func deleteClub(clubID: Int) async throws } diff --git a/Service/Sources/Domain/ClubDomain/UseCase/CreatedClubUseCase.swift b/Service/Sources/Domain/ClubDomain/UseCase/CreatedClubUseCase.swift index 79fd0b72..6c50a526 100644 --- a/Service/Sources/Domain/ClubDomain/UseCase/CreatedClubUseCase.swift +++ b/Service/Sources/Domain/ClubDomain/UseCase/CreatedClubUseCase.swift @@ -1,5 +1,5 @@ import Foundation public protocol CreatedClubUseCase { - func callAsFunction(schoolID: String, req: CreatedClubRequestDTO) async throws + func callAsFunction(schoolID: Int, req: CreatedClubRequestDTO) async throws } diff --git a/Service/Sources/Domain/SchoolDoamin/API/SchoolAPI.swift b/Service/Sources/Domain/SchoolDoamin/API/SchoolAPI.swift index 474e6186..dddcbae5 100644 --- a/Service/Sources/Domain/SchoolDoamin/API/SchoolAPI.swift +++ b/Service/Sources/Domain/SchoolDoamin/API/SchoolAPI.swift @@ -5,8 +5,8 @@ public enum SchoolAPI { case fetchSchoolList case fetchAllSchoolName case createdSchool(req: CreatedSchoolRequestDTO) - case modifySchool(schoolID: String, req: ModifySchoolRequestDTO) - case deleteSchool(schoolID: String) + case modifySchool(schoolID: Int, req: ModifySchoolRequestDTO) + case deleteSchool(schoolID: Int) } extension SchoolAPI: BitgouelAPI { diff --git a/Service/Sources/Domain/SchoolDoamin/DTO/Response/SchoolListResponseDTO.swift b/Service/Sources/Domain/SchoolDoamin/DTO/Response/SchoolListResponseDTO.swift index 72e233e9..f999214e 100644 --- a/Service/Sources/Domain/SchoolDoamin/DTO/Response/SchoolListResponseDTO.swift +++ b/Service/Sources/Domain/SchoolDoamin/DTO/Response/SchoolListResponseDTO.swift @@ -10,7 +10,7 @@ public struct SchoolListResponseDTO: Decodable { enum CodingKeys: String, CodingKey { case schoolID = "id" - case schoolName + case schoolName = "name" case line case departments case logoImageURL = "logoImageUrl" diff --git a/Service/Sources/Domain/SchoolDoamin/DataSource/RemoteSchoolDataSource.swift b/Service/Sources/Domain/SchoolDoamin/DataSource/RemoteSchoolDataSource.swift index dcb643fe..e21d2188 100644 --- a/Service/Sources/Domain/SchoolDoamin/DataSource/RemoteSchoolDataSource.swift +++ b/Service/Sources/Domain/SchoolDoamin/DataSource/RemoteSchoolDataSource.swift @@ -4,6 +4,6 @@ public protocol RemoteSchoolDataSource: BaseRemoteDataSource { func fetchSchoolList() async throws -> [SchoolListEntity] func fetchAllSchoolName() async throws -> [String] func createdSchool(req: CreatedSchoolRequestDTO) async throws - func modifySchool(schoolID: String, req: ModifySchoolRequestDTO) async throws - func deleteSchool(schoolID: String) async throws + func modifySchool(schoolID: Int, req: ModifySchoolRequestDTO) async throws + func deleteSchool(schoolID: Int) async throws } diff --git a/Service/Sources/Domain/SchoolDoamin/Repository/SchoolRepository.swift b/Service/Sources/Domain/SchoolDoamin/Repository/SchoolRepository.swift index 41720869..d10c9885 100644 --- a/Service/Sources/Domain/SchoolDoamin/Repository/SchoolRepository.swift +++ b/Service/Sources/Domain/SchoolDoamin/Repository/SchoolRepository.swift @@ -2,6 +2,6 @@ public protocol SchoolRepository { func fetchSchoolList() async throws -> [SchoolListEntity] func fetchAllSchoolName() async throws -> [String] func createdSchool(req: CreatedSchoolRequestDTO) async throws - func modifySchool(schoolID: String, req: ModifySchoolRequestDTO) async throws - func deleteSchool(schoolID: String) async throws + func modifySchool(schoolID: Int, req: ModifySchoolRequestDTO) async throws + func deleteSchool(schoolID: Int) async throws } diff --git a/Service/Sources/Domain/SchoolDoamin/UseCase/DeleteSchoolUseCase.swift b/Service/Sources/Domain/SchoolDoamin/UseCase/DeleteSchoolUseCase.swift index 2ee39df5..42cdd484 100644 --- a/Service/Sources/Domain/SchoolDoamin/UseCase/DeleteSchoolUseCase.swift +++ b/Service/Sources/Domain/SchoolDoamin/UseCase/DeleteSchoolUseCase.swift @@ -1,5 +1,5 @@ import Foundation public protocol DeleteSchoolUseCase { - func callAsFunction(schoolID: String) async throws + func callAsFunction(schoolID: Int) async throws } diff --git a/Service/Sources/Domain/SchoolDoamin/UseCase/ModifySchoolUseCase.swift b/Service/Sources/Domain/SchoolDoamin/UseCase/ModifySchoolUseCase.swift index 1b3797ae..64f8a81a 100644 --- a/Service/Sources/Domain/SchoolDoamin/UseCase/ModifySchoolUseCase.swift +++ b/Service/Sources/Domain/SchoolDoamin/UseCase/ModifySchoolUseCase.swift @@ -1,5 +1,5 @@ import Foundation public protocol ModifySchoolUseCase { - func callAsFunction(schoolID: String, req: ModifySchoolRequestDTO) async throws + func callAsFunction(schoolID: Int, req: ModifySchoolRequestDTO) async throws }