diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 3623cb78..22901c41 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -143,6 +143,9 @@ private class InputInformationDependency7b32a8e7e8a8f0ab5466Provider: InputInfor var inputCertificateInfoBuildable: any InputCertificateInfoBuildable { return appComponent.inputCertificateInfoBuildable } + var inputLanguageInfoBuildable: any InputLanguageInfoBuildable { + return appComponent.inputLanguageInfoBuildable + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -243,6 +246,7 @@ extension InputInformationComponent: Registration { keyPathToName[\InputInformationDependency.inputWorkInfoBuildable] = "inputWorkInfoBuildable-any InputWorkInfoBuildable" keyPathToName[\InputInformationDependency.inputMilitaryInfoBuildable] = "inputMilitaryInfoBuildable-any InputMilitaryInfoBuildable" keyPathToName[\InputInformationDependency.inputCertificateInfoBuildable] = "inputCertificateInfoBuildable-any InputCertificateInfoBuildable" + keyPathToName[\InputInformationDependency.inputLanguageInfoBuildable] = "inputLanguageInfoBuildable-any InputLanguageInfoBuildable" } } extension InputCertificateInfoComponent: Registration { diff --git a/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift b/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift index 2a1ebf2f..380d20f3 100644 --- a/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift +++ b/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift @@ -5,6 +5,7 @@ import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface import InputMilitaryInfoFeatureInterface import InputCertificateInfoFeatureInterface +import InputLanguageInfoFeatureInterface import NeedleFoundation import SwiftUI @@ -14,6 +15,7 @@ public protocol InputInformationDependency: Dependency { var inputWorkInfoBuildable: any InputWorkInfoBuildable { get } var inputMilitaryInfoBuildable: any InputMilitaryInfoBuildable { get } var inputCertificateInfoBuildable: any InputCertificateInfoBuildable { get } + var inputLanguageInfoBuildable: any InputLanguageInfoBuildable { get } } public final class InputInformationComponent: @@ -34,6 +36,7 @@ public final class InputInformationComponent: inputWorkInfoBuildable: dependency.inputWorkInfoBuildable, inputMilitaryInfoBuildable: dependency.inputMilitaryInfoBuildable, inputCertificateInfoBuildable: dependency.inputCertificateInfoBuildable, + inputLanguageInfoBuildable: dependency.inputLanguageInfoBuildable, container: container ) } diff --git a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift index f2a4b64f..7f561108 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift @@ -4,6 +4,7 @@ import InputMilitaryInfoFeatureInterface import InputProfileInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface +import InputLanguageInfoFeatureInterface final class InputInformationIntent: InputInformationIntentProtocol { private weak var model: (any InputInformationActionProtocol)? @@ -58,3 +59,13 @@ extension InputInformationIntent: InputCertificateDelegate { model?.nextButtonDidTap() } } + +extension InputInformationIntent: InputLanguageDelegate { + func languagePrevButtonDidTap() { + model?.prevButtonDidTap() + } + + func completeToInputLanguage() { + // TODO: 전체 데이터 서버에 송신 + } +} diff --git a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift index f182ad66..74fdc1ff 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift @@ -4,10 +4,12 @@ import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface import InputMilitaryInfoFeatureInterface import InputCertificateInfoFeatureInterface +import InputLanguageInfoFeatureInterface protocol InputInformationIntentProtocol: InputProfileDelegate, InputSchoolLifeDelegate, InputWorkDelegate, InputMilitaryDelegate, - InputCertificateDelegate {} + InputCertificateDelegate, + InputLanguageDelegate {} diff --git a/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift b/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift index 7236b2f0..5ae83e21 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift @@ -5,6 +5,7 @@ import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface import InputMilitaryInfoFeatureInterface import InputCertificateInfoFeatureInterface +import InputLanguageInfoFeatureInterface import SwiftUI import ViewUtil @@ -18,6 +19,7 @@ struct InputInformationView: View { private let inputWorkInfoBuildable: any InputWorkInfoBuildable private let inputMilitaryInfoBuildable: any InputMilitaryInfoBuildable private let inputCertificateInfoBuildable: any InputCertificateInfoBuildable + private let inputLanguageInfoBuildable: any InputLanguageInfoBuildable init( inputProfileInfoBuildable: any InputProfileInfoBuildable, @@ -25,6 +27,7 @@ struct InputInformationView: View { inputWorkInfoBuildable: any InputWorkInfoBuildable, inputMilitaryInfoBuildable: any InputMilitaryInfoBuildable, inputCertificateInfoBuildable: any InputCertificateInfoBuildable, + inputLanguageInfoBuildable: any InputLanguageInfoBuildable, container: MVIContainer ) { self.inputProfileInfoBuildable = inputProfileInfoBuildable @@ -32,6 +35,7 @@ struct InputInformationView: View { self.inputWorkInfoBuildable = inputWorkInfoBuildable self.inputMilitaryInfoBuildable = inputMilitaryInfoBuildable self.inputCertificateInfoBuildable = inputCertificateInfoBuildable + self.inputLanguageInfoBuildable = inputLanguageInfoBuildable self._container = StateObject(wrappedValue: container) } @@ -62,7 +66,8 @@ struct InputInformationView: View { .eraseToAnyView() .tag(InformationPhase.certificate) - Text("B") + inputLanguageInfoBuildable.makeView(delegate: intent) + .eraseToAnyView() .tag(InformationPhase.language) } .animation(.default, value: state.phase) diff --git a/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageDelegate.swift b/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageDelegate.swift new file mode 100644 index 00000000..30741a40 --- /dev/null +++ b/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageDelegate.swift @@ -0,0 +1,4 @@ +public protocol InputLanguageDelegate: AnyObject { + func languagePrevButtonDidTap() + func completeToInputLanguage() +} diff --git a/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageInfoBuildable.swift b/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageInfoBuildable.swift index 3984fcc3..b44a6055 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageInfoBuildable.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Interface/InputLanguageInfoBuildable.swift @@ -2,5 +2,5 @@ import SwiftUI public protocol InputLanguageInfoBuildable { associatedtype ViewType: View - func makeView() -> ViewType + func makeView(delegate: InputLanguageDelegate) -> ViewType } diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/DI/InputLanguageInfoComponent.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/DI/InputLanguageInfoComponent.swift index 34edb2d7..e6a6becf 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/DI/InputLanguageInfoComponent.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/DI/InputLanguageInfoComponent.swift @@ -9,7 +9,14 @@ public final class InputLanguageInfoComponent: Component, InputLanguageInfoBuildable { - public func makeView() -> some View { - EmptyView() + public func makeView(delegate: InputLanguageDelegate) -> some View { + let model = InputLanguageInfoModel() + let intent = InputLanguageInfoIntent(model: model, languageDelegate: delegate) + let container = MVIContainer( + intent: intent as InputLanguageInfoIntentProtocol, + model: model as InputLanguageInfoStateProtocol, + modelChangePublisher: model.objectWillChange + ) + return InputLanguageInfoView(container: container) } } diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntent.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntent.swift index b2e14c88..ce324cc0 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntent.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntent.swift @@ -1,3 +1,39 @@ import Foundation +import InputLanguageInfoFeatureInterface -final class InputLanguageInfoIntent: InputLanguageInfoIntentProtocol {} +final class InputLanguageInfoIntent: InputLanguageInfoIntentProtocol { + private weak var model: (any InputLanguageInfoActionProtocol)? + private weak var languageDelegate: (any InputLanguageDelegate)? + + init( + model: any InputLanguageInfoActionProtocol, + languageDelegate: any InputLanguageDelegate + ) { + self.model = model + self.languageDelegate = languageDelegate + } + + func updateLanguageName(name: String, at index: Int) { + model?.updateLanguageName(name: name, at: index) + } + + func updateLanguageScore(score: String, at index: Int) { + model?.updateLanguageScore(score: score, at: index) + } + + func deleteLanguage(at index: Int) { + model?.deleteLanguage(at: index) + } + + func languageAppendButtonDidTap() { + model?.appendLanguage() + } + + func prevButtonDidTap() { + languageDelegate?.languagePrevButtonDidTap() + } + + func completeButtonDidTap() { + languageDelegate?.completeToInputLanguage() + } +} diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntentProtocol.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntentProtocol.swift index 4104fe25..7f4ed7e8 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntentProtocol.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Intent/InputLanguageInfoIntentProtocol.swift @@ -1,3 +1,10 @@ import Foundation -protocol InputLanguageInfoIntentProtocol {} +protocol InputLanguageInfoIntentProtocol { + func updateLanguageName(name: String, at index: Int) + func updateLanguageScore(score: String, at index: Int) + func deleteLanguage(at index: Int) + func languageAppendButtonDidTap() + func prevButtonDidTap() + func completeButtonDidTap() +} diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModel.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModel.swift index ee965e86..181e2031 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModel.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModel.swift @@ -1,5 +1,38 @@ import Foundation +import FoundationUtil -final class InputLanguageInfoModel: ObservableObject, InputLanguageInfoStateProtocol {} +final class InputLanguageInfoModel: ObservableObject, InputLanguageInfoStateProtocol { + @Published var languageList: [LanguageInputModel] = [ + .init(languageName: "", languageScore: "") + ] +} -extension InputLanguageInfoModel: InputLanguageInfoActionProtocol {} +extension InputLanguageInfoModel: InputLanguageInfoActionProtocol { + func updateLanguageName(name: String, at index: Int) { + guard let indexedLanguage = languageList[safe: index] else { return } + let newLanuageInputModel = LanguageInputModel( + languageName: name, + languageScore: indexedLanguage.languageScore + ) + languageList[index] = newLanuageInputModel + } + + func updateLanguageScore(score: String, at index: Int) { + guard let indexedLanguage = languageList[safe: index] else { return } + let newLanuageInputModel = LanguageInputModel( + languageName: indexedLanguage.languageName, + languageScore: score + ) + languageList[index] = newLanuageInputModel + } + + func deleteLanguage(at index: Int) { + languageList.remove(at: index) + } + + func appendLanguage() { + languageList.append( + .init(languageName: "", languageScore: "") + ) + } +} diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModelProtocol.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModelProtocol.swift index 87591af3..bd6694ba 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModelProtocol.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/InputLanguageInfoModelProtocol.swift @@ -1,5 +1,12 @@ import Foundation -protocol InputLanguageInfoStateProtocol {} +protocol InputLanguageInfoStateProtocol { + var languageList: [LanguageInputModel] { get } +} -protocol InputLanguageInfoActionProtocol: AnyObject {} +protocol InputLanguageInfoActionProtocol: AnyObject { + func updateLanguageName(name: String, at index: Int) + func updateLanguageScore(score: String, at index: Int) + func deleteLanguage(at index: Int) + func appendLanguage() +} diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift new file mode 100644 index 00000000..c6eefa74 --- /dev/null +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift @@ -0,0 +1,11 @@ +import Foundation + +struct LanguageInputModel: Equatable { + let languageName: String + let languageScore: String + + init(languageName: String, languageScore: String) { + self.languageName = languageName + self.languageScore = languageScore + } +} diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift index fd4492ec..2f8246a3 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift @@ -1,5 +1,8 @@ import BaseFeature +import DesignSystem +import FoundationUtil import SwiftUI +import ViewUtil struct InputLanguageInfoView: View { @StateObject var container: MVIContainer @@ -7,6 +10,94 @@ struct InputLanguageInfoView: View { var state: any InputLanguageInfoStateProtocol { container.model } var body: some View { - Text("Hello, World!") + GeometryReader { proxy in + SMSNavigationTitleView(title: "정보입력") { + Rectangle() + .fill(Color.sms(.neutral(.n10))) + .frame(maxWidth: .infinity) + .frame(height: 16) + + VStack(spacing: 32) { + pageTitleView() + + VStack(spacing: 8) { + languageListView(proxy: proxy) + .titleWrapper("외국어") + .aligned(.leading) + + SMSChip("추가") { + intent.languageAppendButtonDidTap() + } + .aligned(.leading) + } + .animation(.default, value: state.languageList.count) + + Spacer() + + HStack(spacing: 8) { + CTAButton(text: "이전", style: .outline) { + intent.prevButtonDidTap() + } + .frame(maxWidth: proxy.size.width / 3) + + CTAButton(text: "입력 완료") { + intent.completeButtonDidTap() + } + .frame(maxWidth: .infinity) + } + .padding(.bottom, 32) + } + .padding([.top, .horizontal], 20) + } + } + } + + @ViewBuilder + func pageTitleView() -> some View { + HStack(spacing: 4) { + Text("외국어") + .foregroundColor(.sms(.system(.black))) + + Text("*") + .foregroundColor(.sms(.sub(.s2))) + + Spacer() + + SMSPageControl(pageCount: 6, selectedPage: 5) + } + .smsFont(.title1) + } + + @ViewBuilder + func languageListView(proxy: GeometryProxy) -> some View { + VStack(spacing: 12) { + ForEach(state.languageList.indices, id: \.self) { index in + HStack(spacing: 16) { + SMSTextField( + "예) 토익", + text: Binding( + get: { state.languageList[safe: index]?.languageName ?? "" }, + set: { intent.updateLanguageName(name: $0, at: index) } + ) + ) + .frame(maxWidth: .infinity) + + SMSTextField( + "900", + text: Binding( + get: { state.languageList[safe: index]?.languageScore ?? "" }, + set: { intent.updateLanguageScore(score: $0, at: index) } + ) + ) + .frame(maxWidth: proxy.size.width / 4) + + Button { + intent.deleteLanguage(at: index) + } label: { + SMSIcon(.trash) + } + } + } + } } }