From ff63415ed350d6322ad3f53427ad51dd325ab352 Mon Sep 17 00:00:00 2001 From: baegteun Date: Sun, 9 Jul 2023 12:07:44 +0900 Subject: [PATCH 01/28] :sparkles: : InputProjectInfoFeature module --- .../Dependency+Target.swift | 8 ++++++ .../ModulePaths.swift | 1 + Projects/App/Project.swift | 1 + .../InputInformationFeature/Project.swift | 1 + .../Demo/Resources/LaunchScreen.storyboard | 25 +++++++++++++++++++ .../Demo/Sources/AppDelegate.swift | 19 ++++++++++++++ .../Interface/Interface.swift | 1 + .../InputProjectInfoFeature/Project.swift | 12 +++++++++ .../Sources/Source.swift | 1 + .../Tests/InputProjectInfoFeatureTest.swift | 11 ++++++++ 10 files changed, 80 insertions(+) create mode 100644 Projects/Feature/InputProjectInfoFeature/Demo/Resources/LaunchScreen.storyboard create mode 100644 Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Project.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Source.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Tests/InputProjectInfoFeatureTest.swift diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift index 2df87d5a..681970e8 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift @@ -9,6 +9,14 @@ public extension TargetDependency { } public extension TargetDependency.Feature { + static let InputProjectInfoFeatureInterface = TargetDependency.project( + target: ModulePaths.Feature.InputProjectInfoFeature.targetName(type: .interface), + path: .relativeToFeature(ModulePaths.Feature.InputProjectInfoFeature.rawValue) + ) + static let InputProjectInfoFeature = TargetDependency.project( + target: ModulePaths.Feature.InputProjectInfoFeature.targetName(type: .sources), + path: .relativeToFeature(ModulePaths.Feature.InputProjectInfoFeature.rawValue) + ) static let SplashFeatureInterface = TargetDependency.project( target: ModulePaths.Feature.SplashFeature.targetName(type: .interface), path: .relativeToFeature(ModulePaths.Feature.SplashFeature.rawValue) diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift index f03f55d8..c05fc80d 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift @@ -10,6 +10,7 @@ public enum ModulePaths { public extension ModulePaths { enum Feature: String { + case InputProjectInfoFeature case SplashFeature case MainFeature case TechStackAppendFeature diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index bc5de8cd..8c7c3850 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -46,6 +46,7 @@ let targets: [Target] = [ .Feature.InputMilitaryInfoFeature, .Feature.InputCertificateInfoFeature, .Feature.InputLanguageInfoFeature, + .Feature.InputProjectInfoFeature, .Feature.MainFeature, .Feature.SplashFeature, .Feature.TechStackAppendFeature, diff --git a/Projects/Feature/InputInformationFeature/Project.swift b/Projects/Feature/InputInformationFeature/Project.swift index 1ec00038..7635d72a 100644 --- a/Projects/Feature/InputInformationFeature/Project.swift +++ b/Projects/Feature/InputInformationFeature/Project.swift @@ -14,6 +14,7 @@ let project = Project.makeModule( .Feature.InputMilitaryInfoFeatureInterface, .Feature.InputCertificateInfoFeatureInterface, .Feature.InputLanguageInfoFeatureInterface, + .Feature.InputProjectInfoFeatureInterface, .Domain.FileDomainInterface, .Domain.StudentDomainInterface, .Domain.UserDomainInterface diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Resources/LaunchScreen.storyboard b/Projects/Feature/InputProjectInfoFeature/Demo/Resources/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Resources/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift new file mode 100644 index 00000000..ef2bae04 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift @@ -0,0 +1,19 @@ +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + let viewController = UIViewController() + viewController.view.backgroundColor = .yellow + window?.rootViewController = viewController + window?.makeKeyAndVisible() + + return true + } +} diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift b/Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift new file mode 100644 index 00000000..b1853ce6 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift @@ -0,0 +1 @@ +// This is for Tuist diff --git a/Projects/Feature/InputProjectInfoFeature/Project.swift b/Projects/Feature/InputProjectInfoFeature/Project.swift new file mode 100644 index 00000000..d28da929 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Project.swift @@ -0,0 +1,12 @@ +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: ModulePaths.Feature.InputProjectInfoFeature.rawValue, + product: .staticLibrary, + targets: [.interface, .unitTest, .demo], + internalDependencies: [ + .Feature.InputInformationBaseFeature + ] +) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Source.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Source.swift new file mode 100644 index 00000000..b1853ce6 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Source.swift @@ -0,0 +1 @@ +// This is for Tuist diff --git a/Projects/Feature/InputProjectInfoFeature/Tests/InputProjectInfoFeatureTest.swift b/Projects/Feature/InputProjectInfoFeature/Tests/InputProjectInfoFeatureTest.swift new file mode 100644 index 00000000..be848628 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Tests/InputProjectInfoFeatureTest.swift @@ -0,0 +1,11 @@ +import XCTest + +final class InputProjectInfoFeatureTests: XCTestCase { + override func setUpWithError() throws {} + + override func tearDownWithError() throws {} + + func testExample() { + XCTAssertEqual(1, 1) + } +} From 24017fa05a6907fe4e505bafa0cf3c0463a20974 Mon Sep 17 00:00:00 2001 From: baegteun Date: Sun, 9 Jul 2023 21:01:19 +0900 Subject: [PATCH 02/28] =?UTF-8?q?:lipstick:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=9E=85=EB=A0=A5=20=EC=A4=91=EA=B0=84=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=EA=B9=8C=EC=A7=80=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Demo/Sources/AppDelegate.swift | 22 +-- .../Interface/InputProjectInfoBuildable.swift | 6 + .../Interface/Interface.swift | 1 - .../DI/InputProjectInfoComponent.swift | 12 ++ .../Sources/Scene/InputProjectInfoView.swift | 135 ++++++++++++++++++ .../Sources/Source.swift | 1 - 6 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift delete mode 100644 Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift delete mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Source.swift diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift index ef2bae04..794af21e 100644 --- a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift @@ -1,19 +1,11 @@ -import UIKit +import SwiftUI +@testable import InputProjectInfoFeature @main -final class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { - window = UIWindow(frame: UIScreen.main.bounds) - let viewController = UIViewController() - viewController.view.backgroundColor = .yellow - window?.rootViewController = viewController - window?.makeKeyAndVisible() - - return true +struct InputProjectInfoApp: App { + var body: some Scene { + WindowGroup { + InputProjectInfoView() + } } } diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift new file mode 100644 index 00000000..4bc8b8ea --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol InputProjectInfoBuildable { + associatedtype ViewType: View + func makeView() -> ViewType +} diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift b/Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift deleted file mode 100644 index b1853ce6..00000000 --- a/Projects/Feature/InputProjectInfoFeature/Interface/Interface.swift +++ /dev/null @@ -1 +0,0 @@ -// This is for Tuist diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift new file mode 100644 index 00000000..0fb7e9ca --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift @@ -0,0 +1,12 @@ +import SwiftUI +import NeedleFoundation + +protocol InputProjectInfoDependency: Dependency {} + +final class InputProjectInfoComponent: + Component, + InputProjectInfoBuildable { + func makeView() -> some View { + EmptyView() + } +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift new file mode 100644 index 00000000..c90c0393 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -0,0 +1,135 @@ +import DesignSystem +import InputInformationBaseFeature +import SwiftUI +import ViewUtil + +struct InputProjectInfoView: View { + var body: some View { + SMSNavigationTitleView(title: "프로젝트") { + ScrollView(showsIndicators: true) { + SMSSeparator() + + VStack(spacing: 32) { + InputInformationPageTitleView(title: "프로젝트", isRequired: false, pageCount: 7, selectedPage: 6) + + projectListRowView() + + SMSChip("추가") {} + .foregroundColor(.sms(.system(.black))) + .aligned(.trailing) + } + .padding([.top, .horizontal], 20) + } + } + } + + @ViewBuilder + func projectListRowView() -> some View { + VStack(alignment: .leading, spacing: 24) { + HStack(spacing: 16) { + SMSText("프로젝트", font: .title1) + .foregroundColor(.sms(.system(.black))) + + Spacer() + + SMSIcon(.downChevron) + + SMSIcon(.xmarkOutline) + } + .padding(.bottom, 8) + + projectName() + + projectIcon() + + projectPreviewImageList() + + projectContentTextEditor() + + projectTechStack() + } + } +} + +// MARK: - View Section +private extension InputProjectInfoView { + @ViewBuilder + func projectName() -> some View { + SMSTextField("프로젝트 이름 입력", text: .constant("")) + .titleWrapper("이름") + } + + @ViewBuilder + func projectIcon() -> some View { + imagePlaceholder(size: 108) + .overlay { + SMSIcon(.photo) + } + .titleWrapper("아이콘") + } + + @ViewBuilder + func projectPreviewImageList() -> some View { + LazyHStack(spacing: 8) { + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + + SMSText("0/4", font: .body2) + .foregroundColor(.sms(.system(.black))) + } + } + } + .titleWrapper("미리보기 사진") + } + + @ViewBuilder + func projectContentTextEditor() -> some View { + TextEditor(text: .constant("")) + .smsFont(.body1, color: .system(.black)) + .colorMultiply(.sms(.neutral(.n10))) + .frame(minHeight: 48) + .cornerRadius(8) + .roundedStroke(cornerRadius: 8, color: .sms(.primary(.p1)), lineWidth: 1) + .overlay(alignment: .topLeading) { + ConditionView(true) { + SMSText("프로젝트 내용 입력", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) + .padding([.top, .leading], 12) + } + } + .titleWrapper("내용") + } + + @ViewBuilder + func projectTechStack() -> some View { + HStack(spacing: 8) { + SMSIcon(.magnifyingglass) + + SMSText("찾고 싶은 세부 스택 입력", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) + + Spacer() + } + .padding(12) + .background { + Color.sms(.neutral(.n10)) + } + .clipShape(RoundedRectangle(cornerRadius: 8)) + .buttonWrapper {} + .titleWrapper("사용 기술") + } + + +} + +// MARK: - Reusable View +private extension InputProjectInfoView { + @ViewBuilder + func imagePlaceholder(size: CGFloat) -> some View { + RoundedRectangle(cornerRadius: 8) + .fill(Color.sms(.neutral(.n10))) + .frame(width: size, height: size) + } +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Source.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Source.swift deleted file mode 100644 index b1853ce6..00000000 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Source.swift +++ /dev/null @@ -1 +0,0 @@ -// This is for Tuist From 71bd26c0f55c5e056fd3de9c9e0e4b8861a45832 Mon Sep 17 00:00:00 2001 From: baegteun Date: Sun, 9 Jul 2023 21:23:29 +0900 Subject: [PATCH 03/28] =?UTF-8?q?:lipstick:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=9E=85=EB=A0=A5=20-=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=EA=B8=B0=EA=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Calendar.imageset/Calendar.svg | 5 +++ .../Calendar.imageset/Contents.json | 12 ++++++ .../WaterWave.imageset/Contents.json | 12 ++++++ .../WaterWave.imageset/WaterWave.svg | 3 ++ .../DesignSystem/Sources/Icon/SMSIcon.swift | 8 ++++ .../DI/InputProjectInfoComponent.swift | 3 +- .../Sources/Scene/InputProjectInfoView.swift | 38 ++++++++++++++++++- 7 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Calendar.svg create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Contents.json create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/Contents.json create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/WaterWave.svg diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Calendar.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Calendar.svg new file mode 100644 index 00000000..e1845b20 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Calendar.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Contents.json b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Contents.json new file mode 100644 index 00000000..4d8f4402 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Calendar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Calendar.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/Contents.json b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/Contents.json new file mode 100644 index 00000000..d1ee1269 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "WaterWave.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/WaterWave.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/WaterWave.svg new file mode 100644 index 00000000..67b2235b --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/WaterWave.imageset/WaterWave.svg @@ -0,0 +1,3 @@ + + + diff --git a/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift b/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift index 0da94534..8520184a 100644 --- a/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift +++ b/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift @@ -20,6 +20,7 @@ public struct SMSIcon: View { public enum Icon: CaseIterable { case book + case calendar case camera case check case checkmark @@ -39,6 +40,7 @@ public struct SMSIcon: View { case smallPlus case upArrow case magnifyingglass + case waterWave case xmark case xmarkOutline } @@ -56,6 +58,9 @@ public struct SMSIcon: View { case .book: return DesignSystemAsset.Icons.book.swiftUIImage + case .calendar: + return DesignSystemAsset.Icons.calendar.swiftUIImage + case .camera: return DesignSystemAsset.Icons.camera.swiftUIImage @@ -113,6 +118,9 @@ public struct SMSIcon: View { case .magnifyingglass: return DesignSystemAsset.Icons.magnifyingglass.swiftUIImage + case .waterWave: + return DesignSystemAsset.Icons.waterWave.swiftUIImage + case .xmark: return DesignSystemAsset.Icons.xmark.swiftUIImage diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift index 0fb7e9ca..c327bf4d 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift @@ -1,5 +1,6 @@ -import SwiftUI +import InputProjectInfoFeatureInterface import NeedleFoundation +import SwiftUI protocol InputProjectInfoDependency: Dependency {} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index c90c0393..e641af52 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -47,6 +47,8 @@ struct InputProjectInfoView: View { projectContentTextEditor() projectTechStack() + + projectDuration() } } } @@ -121,7 +123,21 @@ private extension InputProjectInfoView { .titleWrapper("사용 기술") } - + @ViewBuilder + func projectDuration() -> some View { + HStack(spacing: 8) { + datePickerField { + } + .frame(maxWidth: .infinity) + + SMSIcon(.waterWave) + + datePickerField { + } + .frame(maxWidth: .infinity) + } + .titleWrapper("진행 기간") + } } // MARK: - Reusable View @@ -132,4 +148,24 @@ private extension InputProjectInfoView { .fill(Color.sms(.neutral(.n10))) .frame(width: size, height: size) } + + @ViewBuilder + func datePickerField(action: @escaping () -> Void) -> some View { + SMSTextField( + "yyyy.mm", + text: .constant(""), + isOnClear: false + ) + .disabled(true) + .overlay(alignment: .trailing) { + SMSIcon(.calendar) + .padding(.trailing, 12) + } + .simultaneousGesture( + TapGesture() + .onEnded { + action() + } + ) + } } From ce5d0487bb5c06c9796f6d2f0e23296807b551e8 Mon Sep 17 00:00:00 2001 From: baegteun Date: Sun, 9 Jul 2023 21:54:01 +0900 Subject: [PATCH 04/28] =?UTF-8?q?:lipstick:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=9E=85=EB=A0=A5=20-=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A7=81=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/InputProjectInfoView.swift | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index e641af52..f7edaba4 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -4,27 +4,33 @@ import SwiftUI import ViewUtil struct InputProjectInfoView: View { + @FocusState var projectContentIsFocused: Bool var body: some View { - SMSNavigationTitleView(title: "프로젝트") { - ScrollView(showsIndicators: true) { - SMSSeparator() + GeometryReader { geometry in + SMSNavigationTitleView(title: "프로젝트") { + ScrollView(showsIndicators: true) { + SMSSeparator() - VStack(spacing: 32) { - InputInformationPageTitleView(title: "프로젝트", isRequired: false, pageCount: 7, selectedPage: 6) + VStack(spacing: 32) { + InputInformationPageTitleView(title: "프로젝트", isRequired: false, pageCount: 7, selectedPage: 6) - projectListRowView() + projectListRowView(geometry: geometry) - SMSChip("추가") {} - .foregroundColor(.sms(.system(.black))) - .aligned(.trailing) + SMSSeparator(height: 1) + + SMSChip("추가") { + } + .foregroundColor(.sms(.system(.black))) + .aligned(.trailing) + } + .padding([.top, .horizontal], 20) } - .padding([.top, .horizontal], 20) } } } @ViewBuilder - func projectListRowView() -> some View { + func projectListRowView(geometry: GeometryProxy) -> some View { VStack(alignment: .leading, spacing: 24) { HStack(spacing: 16) { SMSText("프로젝트", font: .title1) @@ -49,6 +55,8 @@ struct InputProjectInfoView: View { projectTechStack() projectDuration() + + projectRelatedLink(geometry: geometry) } } } @@ -90,15 +98,23 @@ private extension InputProjectInfoView { func projectContentTextEditor() -> some View { TextEditor(text: .constant("")) .smsFont(.body1, color: .system(.black)) + .focused($projectContentIsFocused) .colorMultiply(.sms(.neutral(.n10))) .frame(minHeight: 48) .cornerRadius(8) - .roundedStroke(cornerRadius: 8, color: .sms(.primary(.p1)), lineWidth: 1) + .roundedStroke( + cornerRadius: 8, + color: projectContentIsFocused ? .sms(.primary(.p1)) : .clear, + lineWidth: projectContentIsFocused ? 1 : 0 + ) .overlay(alignment: .topLeading) { ConditionView(true) { SMSText("프로젝트 내용 입력", font: .body1) .foregroundColor(.sms(.neutral(.n30))) .padding([.top, .leading], 12) + .onTapGesture { + projectContentIsFocused = true + } } } .titleWrapper("내용") @@ -138,6 +154,36 @@ private extension InputProjectInfoView { } .titleWrapper("진행 기간") } + + @ViewBuilder + func projectRelatedLink(geometry: GeometryProxy) -> some View { + VStack(spacing: 8) { + ForEach(1..<2, id: \.self) { index in + HStack(spacing: 16) { + SMSTextField( + "이름", + text: .constant("") + ) + .frame(maxWidth: geometry.size.width / 4) + + SMSTextField( + "URL", + text: .constant("") + ) + .frame(maxWidth: .infinity) + + Button { + } label: { + SMSIcon(.trash) + } + } + } + + SMSChip("추가") {} + .aligned(.leading) + } + .titleWrapper("관련 링크") + } } // MARK: - Reusable View From ae3d0e4588d9bf7a26852dbd9603a3469b34ce7c Mon Sep 17 00:00:00 2001 From: baegteun Date: Sun, 9 Jul 2023 22:35:26 +0900 Subject: [PATCH 05/28] =?UTF-8?q?:sparkles:=20::=20Filter=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=98=84=EC=9E=AC=20=EC=9C=A0=EC=A0=80=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20Model=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/NeedleGenerated.swift | 5 +++ .../Sources/DI/FilterComponent.swift | 11 +++--- .../Sources/Intent/FilterIntent.swift | 12 +++++-- .../Sources/Scene/FilterView.swift | 4 +-- .../Intent/InputProjectInfoIntent.swift | 4 +++ .../InputProjectInfoIntentProtocol.swift | 4 +++ .../Sources/Model/InputProjectInfoModel.swift | 9 +++++ .../Model/InputProjectInfoModelProtocol.swift | 35 +++++++++++++++++++ 8 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 389bbd72..5e1d73e4 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -19,6 +19,7 @@ import InputMilitaryInfoFeature import InputMilitaryInfoFeatureInterface import InputProfileInfoFeature import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeature import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeature @@ -142,6 +143,9 @@ private class FilterDependencya3adf5d0affb84ca15efProvider: FilterDependency { var majorDomainBuildable: any MajorDomainBuildable { return appComponent.majorDomainBuildable } + var userDomainBuildable: any UserDomainBuildable { + return appComponent.userDomainBuildable + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -425,6 +429,7 @@ extension FilterComponent: Registration { public func registerItems() { keyPathToName[\FilterDependency.techStackAppendBuildable] = "techStackAppendBuildable-any TechStackAppendBuildable" keyPathToName[\FilterDependency.majorDomainBuildable] = "majorDomainBuildable-any MajorDomainBuildable" + keyPathToName[\FilterDependency.userDomainBuildable] = "userDomainBuildable-any UserDomainBuildable" } } extension RootComponent: Registration { diff --git a/Projects/Feature/FilterFeature/Sources/DI/FilterComponent.swift b/Projects/Feature/FilterFeature/Sources/DI/FilterComponent.swift index aed7f9be..1992a3f3 100644 --- a/Projects/Feature/FilterFeature/Sources/DI/FilterComponent.swift +++ b/Projects/Feature/FilterFeature/Sources/DI/FilterComponent.swift @@ -1,13 +1,15 @@ -import SwiftUI -import FilterFeatureInterface -import TechStackAppendFeatureInterface import BaseFeature +import FilterFeatureInterface import MajorDomainInterface import NeedleFoundation +import SwiftUI +import TechStackAppendFeatureInterface +import UserDomainInterface public protocol FilterDependency: Dependency { var techStackAppendBuildable: any TechStackAppendBuildable { get } var majorDomainBuildable: any MajorDomainBuildable { get } + var userDomainBuildable: any UserDomainBuildable { get } } public final class FilterComponent: Component, FilterBuildable { @@ -16,7 +18,8 @@ public final class FilterComponent: Component, FilterBuildable let intent = FilterIntent( model: model, filterDelegate: delegate, - fetchMajorListUseCase: dependency.majorDomainBuildable.fetchMajorListUseCase + fetchMajorListUseCase: dependency.majorDomainBuildable.fetchMajorListUseCase, + loadUserRoleUseCase: dependency.userDomainBuildable.loadUserRoleUseCase ) let container = MVIContainer( intent: intent as FilterIntentProtocol, diff --git a/Projects/Feature/FilterFeature/Sources/Intent/FilterIntent.swift b/Projects/Feature/FilterFeature/Sources/Intent/FilterIntent.swift index 344fc5e2..2a4a88b8 100644 --- a/Projects/Feature/FilterFeature/Sources/Intent/FilterIntent.swift +++ b/Projects/Feature/FilterFeature/Sources/Intent/FilterIntent.swift @@ -1,25 +1,31 @@ import Combine -import SwiftUI import FilterFeatureInterface -import StudentDomainInterface import MajorDomainInterface +import StudentDomainInterface +import SwiftUI +import UserDomainInterface final class FilterIntent: FilterIntentProtocol { private weak var model: (any FilterActionProtocol)? private weak var filterDelegate: (any FilterDelegate)? private let fetchMajorListUseCase: any FetchMajorListUseCase + private let loadUserRoleUseCase: any LoadUserRoleUseCase init( model: any FilterActionProtocol, filterDelegate: any FilterDelegate, - fetchMajorListUseCase: any FetchMajorListUseCase + fetchMajorListUseCase: any FetchMajorListUseCase, + loadUserRoleUseCase: any LoadUserRoleUseCase ) { self.filterDelegate = filterDelegate self.model = model self.fetchMajorListUseCase = fetchMajorListUseCase + self.loadUserRoleUseCase = loadUserRoleUseCase } func onAppear() { + let userRole = loadUserRoleUseCase.execute() + model?.updateUserRole(role: userRole) Task { let majorList = try await fetchMajorListUseCase.execute() model?.updpateMajorList(majorList: majorList) diff --git a/Projects/Feature/FilterFeature/Sources/Scene/FilterView.swift b/Projects/Feature/FilterFeature/Sources/Scene/FilterView.swift index b2dd786a..736a47d7 100644 --- a/Projects/Feature/FilterFeature/Sources/Scene/FilterView.swift +++ b/Projects/Feature/FilterFeature/Sources/Scene/FilterView.swift @@ -32,7 +32,7 @@ struct FilterView: View { .padding(.bottom, 20) VStack(alignment: .leading, spacing: 40) { - ConditionView(state.userRole == .student) { + ConditionView(state.userRole == .student || state.userRole == .teacher) { gradeSection() classSection() @@ -42,7 +42,7 @@ struct FilterView: View { majorSection() - ConditionView(state.userRole == .student) { + ConditionView(state.userRole == .student || state.userRole == .teacher) { ConditionView(state.userRole == .teacher) { formOfEmploymentSetcion() diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift new file mode 100644 index 00000000..b12b7289 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -0,0 +1,4 @@ +import Foundation + +final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift new file mode 100644 index 00000000..31992867 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -0,0 +1,4 @@ +import Foundation + +protocol InputProjectInfoIntentProtocol { +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift new file mode 100644 index 00000000..3b8d30aa --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -0,0 +1,9 @@ +import Foundation + +final class InputProjectInfoModel: InputProjectInfoStateProtocol { + @Published var projectList: [ProjectInfo] = [] + @Published var projectErrorSetList: [Set] = [] +} + +extension InputProjectInfoModel: InputProjectInfoActionProtocol { +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift new file mode 100644 index 00000000..a67a063d --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -0,0 +1,35 @@ +import Foundation + +struct ProjectInfo { + let name: String + let iconImage: Data + let previewImages: [Data] + let content: String + let techStacks: [String] + let mainTask: String + let startAt: Date + let endAt: Date? + let relatedLinks: [RelatedLink] +} + +extension ProjectInfo { + struct RelatedLink { + let name: String + let url: String + } +} + +enum InputProjectInfoErrorField: Hashable { + case name + case content + case mainTask +} + +protocol InputProjectInfoStateProtocol { + var projectList: [ProjectInfo] { get } + var projectErrorSetList: [Set] { get } +} + +protocol InputProjectInfoActionProtocol: AnyObject { + +} From 080487fdea38f7d7dded0c7fdf5a7ef3a467c0fc Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 10 Jul 2023 00:56:05 +0900 Subject: [PATCH 06/28] :sparkles: :: Intent, Model --- .../Demo/Sources/AppDelegate.swift | 12 +++- .../DI/InputProjectInfoComponent.swift | 12 +++- .../Intent/InputProjectInfoIntent.swift | 53 ++++++++++++++ .../InputProjectInfoIntentProtocol.swift | 12 ++++ .../Sources/Model/InputProjectInfoModel.swift | 71 ++++++++++++++++++- .../Model/InputProjectInfoModelProtocol.swift | 35 +++++---- .../Sources/Scene/InputProjectInfoView.swift | 12 ++++ 7 files changed, 192 insertions(+), 15 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift index 794af21e..1d21d8f0 100644 --- a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift @@ -1,3 +1,4 @@ +import BaseFeature import SwiftUI @testable import InputProjectInfoFeature @@ -5,7 +6,16 @@ import SwiftUI struct InputProjectInfoApp: App { var body: some Scene { WindowGroup { - InputProjectInfoView() + let model = InputProjectInfoModel() + let intent = InputProjectInfoIntent( + model: model + ) + let container = MVIContainer( + intent: intent as InputProjectInfoIntentProtocol, + model: model as InputProjectInfoStateProtocol, + modelChangePublisher: model.objectWillChange + ) + InputProjectInfoView(container: container) } } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift index c327bf4d..a7d7ef5c 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift @@ -1,3 +1,4 @@ +import BaseFeature import InputProjectInfoFeatureInterface import NeedleFoundation import SwiftUI @@ -8,6 +9,15 @@ final class InputProjectInfoComponent: Component, InputProjectInfoBuildable { func makeView() -> some View { - EmptyView() + let model = InputProjectInfoModel() + let intent = InputProjectInfoIntent( + model: model + ) + let container = MVIContainer( + intent: intent as InputProjectInfoIntentProtocol, + model: model as InputProjectInfoStateProtocol, + modelChangePublisher: model.objectWillChange + ) + return InputProjectInfoView(container: container) } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index b12b7289..58fbb51b 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -1,4 +1,57 @@ import Foundation final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { + private weak var model: (any InputProjectInfoActionProtocol)? + + init(model: any InputProjectInfoActionProtocol) { + self.model = model + } + + func updateProjectName(index: Int, name: String) { + model?.updateProjectName(index: index, name: name) + } + + func updateIconImage(index: Int, data: Data) { + model?.updateIconImage(index: index, data: data) + } + + func appendPreviewImage(index: Int, data: Data) { + model?.appendPreviewImage(index: index, data: data) + } + + func removePreviewImageDidTap(index: Int, previewIndex: Int) { + model?.removePreviewImage(index: index, previewIndex: previewIndex) + } + + func updateProjectContent(index: Int, content: String) { + model?.updateProjectContent(index: index, content: content) + } + + func techStacksDidSelect(index: Int, techStacks: [String]) { + model?.updateProjectTechStacks(index: index, techStacks: techStacks) + } + + func updateProjectMainTask(index: Int, mainTask: String) { + model?.updateProjectMainTask(index: index, mainTask: mainTask) + } + + func projectStartAtDidSelect(index: Int, startAt: Date) { + model?.updateProjectStartAt(index: index, startAt: startAt) + } + + func projectEndAtDidSelect(index: Int, endAt: Date) { + model?.updateProjectEndAt(index: index, endAt: endAt) + } + + func updateProjectLinkName(index: Int, linkIndex: Int, name: String) { + model?.updateProjectLinkName(index: index, linkIndex: linkIndex, name: name) + } + + func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) { + model?.updateProjectLinkURL(index: index, linkIndex: linkIndex, url: url) + } + + func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) { + model?.removeProjectRelatedLink(index: index, linkIndex: linkIndex) + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 31992867..98c70d4b 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -1,4 +1,16 @@ import Foundation protocol InputProjectInfoIntentProtocol { + func updateProjectName(index: Int, name: String) + func updateIconImage(index: Int, data: Data) + func appendPreviewImage(index: Int, data: Data) + func removePreviewImageDidTap(index: Int, previewIndex: Int) + func updateProjectContent(index: Int, content: String) + func techStacksDidSelect(index: Int, techStacks: [String]) + func updateProjectMainTask(index: Int, mainTask: String) + func projectStartAtDidSelect(index: Int, startAt: Date) + func projectEndAtDidSelect(index: Int, endAt: Date) + func updateProjectLinkName(index: Int, linkIndex: Int, name: String) + func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) + func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 3b8d30aa..16d6ead1 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -1,9 +1,78 @@ import Foundation +import FoundationUtil -final class InputProjectInfoModel: InputProjectInfoStateProtocol { +final class InputProjectInfoModel: ObservableObject , InputProjectInfoStateProtocol { @Published var projectList: [ProjectInfo] = [] @Published var projectErrorSetList: [Set] = [] } extension InputProjectInfoModel: InputProjectInfoActionProtocol { + func updateProjectName(index: Int, name: String) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].name = name + } + + func updateIconImage(index: Int, data: Data) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].iconImage = data + } + + func appendPreviewImage(index: Int, data: Data) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].previewImages.append(data) + } + + func removePreviewImage(index: Int, previewIndex: Int) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].previewImages.remove(at: previewIndex) + } + + func updateProjectContent(index: Int, content: String) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].content = content + } + + func updateProjectTechStacks(index: Int, techStacks: [String]) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].techStacks = techStacks + } + + func updateProjectMainTask(index: Int, mainTask: String) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].mainTask = mainTask + } + + func updateProjectStartAt(index: Int, startAt: Date) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].startAt = startAt + } + + func updateProjectEndAt(index: Int, endAt: Date) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].endAt = endAt + } + + func updateProjectLinkName(index: Int, linkIndex: Int, name: String) { + guard + let project = projectList[safe: index], + project.relatedLinks[safe: linkIndex] != nil + else { return } + self.projectList[index].relatedLinks[linkIndex].name = name + } + + func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) { + guard + let project = projectList[safe: index], + project.relatedLinks[safe: linkIndex] != nil + else { return } + self.projectList[index].relatedLinks[linkIndex].url = url + } + + func removeProjectRelatedLink(index: Int, linkIndex: Int) { + guard + let project = projectList[safe: index], + project.relatedLinks[safe: linkIndex] != nil + else { return } + self.projectList[index].relatedLinks.remove(at: linkIndex) + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index a67a063d..254b30ad 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -1,21 +1,21 @@ import Foundation struct ProjectInfo { - let name: String - let iconImage: Data - let previewImages: [Data] - let content: String - let techStacks: [String] - let mainTask: String - let startAt: Date - let endAt: Date? - let relatedLinks: [RelatedLink] + var name: String + var iconImage: Data + var previewImages: [Data] + var content: String + var techStacks: [String] + var mainTask: String + var startAt: Date + var endAt: Date? + var relatedLinks: [RelatedLink] } extension ProjectInfo { struct RelatedLink { - let name: String - let url: String + var name: String + var url: String } } @@ -31,5 +31,16 @@ protocol InputProjectInfoStateProtocol { } protocol InputProjectInfoActionProtocol: AnyObject { - + func updateProjectName(index: Int, name: String) + func updateIconImage(index: Int, data: Data) + func appendPreviewImage(index: Int, data: Data) + func removePreviewImage(index: Int, previewIndex: Int) + func updateProjectContent(index: Int, content: String) + func updateProjectTechStacks(index: Int, techStacks: [String]) + func updateProjectMainTask(index: Int, mainTask: String) + func updateProjectStartAt(index: Int, startAt: Date) + func updateProjectEndAt(index: Int, endAt: Date) + func updateProjectLinkName(index: Int, linkIndex: Int, name: String) + func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) + func removeProjectRelatedLink(index: Int, linkIndex: Int) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index f7edaba4..5e19f9ce 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -1,3 +1,4 @@ +import BaseFeature import DesignSystem import InputInformationBaseFeature import SwiftUI @@ -5,6 +6,17 @@ import ViewUtil struct InputProjectInfoView: View { @FocusState var projectContentIsFocused: Bool + + @StateObject var container: MVIContainer + var intent: any InputProjectInfoIntentProtocol { container.intent } + var state: any InputProjectInfoStateProtocol { container.model } + + init( + container: MVIContainer + ) { + self._container = StateObject(wrappedValue: container) + } + var body: some View { GeometryReader { geometry in SMSNavigationTitleView(title: "프로젝트") { From a7f7f618317837bcb76367ce74e48e23e1b666a0 Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 10 Jul 2023 01:27:39 +0900 Subject: [PATCH 07/28] =?UTF-8?q?:sparkles:=20::=20View=20to=20Intent=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/InputProjectInfoIntent.swift | 12 +- .../InputProjectInfoIntentProtocol.swift | 2 + .../Sources/Model/InputProjectInfoModel.swift | 21 ++- .../Model/InputProjectInfoModelProtocol.swift | 12 +- .../Sources/Scene/InputProjectInfoView.swift | 164 ++++++++++++------ 5 files changed, 152 insertions(+), 59 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index 58fbb51b..bcfa21a3 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -46,12 +46,20 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { func updateProjectLinkName(index: Int, linkIndex: Int, name: String) { model?.updateProjectLinkName(index: index, linkIndex: linkIndex, name: name) } - + func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) { model?.updateProjectLinkURL(index: index, linkIndex: linkIndex, url: url) } - + + func relatedLinkAppendButtonDidTap(index: Int) { + model?.appendEmptyRelatedLink(index: index) + } + func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) { model?.removeProjectRelatedLink(index: index, linkIndex: linkIndex) } + + func projectAppendButtonDidTap() { + model?.appendEmptyProject() + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 98c70d4b..4f4f7d8e 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -12,5 +12,7 @@ protocol InputProjectInfoIntentProtocol { func projectEndAtDidSelect(index: Int, endAt: Date) func updateProjectLinkName(index: Int, linkIndex: Int, name: String) func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) + func relatedLinkAppendButtonDidTap(index: Int) func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) + func projectAppendButtonDidTap() } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 16d6ead1..4ff818ca 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -1,7 +1,7 @@ import Foundation import FoundationUtil -final class InputProjectInfoModel: ObservableObject , InputProjectInfoStateProtocol { +final class InputProjectInfoModel: ObservableObject, InputProjectInfoStateProtocol { @Published var projectList: [ProjectInfo] = [] @Published var projectErrorSetList: [Set] = [] } @@ -68,6 +68,12 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { self.projectList[index].relatedLinks[linkIndex].url = url } + func appendEmptyRelatedLink(index: Int) { + guard projectList[safe: index] != nil else { return } + let relatedLink = ProjectInfo.RelatedLink(name: "", url: "") + self.projectList[index].relatedLinks.append(relatedLink) + } + func removeProjectRelatedLink(index: Int, linkIndex: Int) { guard let project = projectList[safe: index], @@ -75,4 +81,17 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { else { return } self.projectList[index].relatedLinks.remove(at: linkIndex) } + + func appendEmptyProject() { + let newProject = ProjectInfo( + name: "", + previewImages: [], + content: "", + techStacks: [], + mainTask: "", + startAt: Date(), + relatedLinks: [] + ) + self.projectList.append(newProject) + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index 254b30ad..23166d2d 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -1,8 +1,9 @@ +import DateUtil import Foundation struct ProjectInfo { var name: String - var iconImage: Data + var iconImage: Data? var previewImages: [Data] var content: String var techStacks: [String] @@ -10,6 +11,13 @@ struct ProjectInfo { var startAt: Date var endAt: Date? var relatedLinks: [RelatedLink] + + var startAtString: String { + startAt.toStringCustomFormat(format: "yyyy.MM") + } + var endAtString: String { + endAt?.toStringCustomFormat(format: "yyyy.MM") ?? "" + } } extension ProjectInfo { @@ -42,5 +50,7 @@ protocol InputProjectInfoActionProtocol: AnyObject { func updateProjectEndAt(index: Int, endAt: Date) func updateProjectLinkName(index: Int, linkIndex: Int, name: String) func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) + func appendEmptyRelatedLink(index: Int) func removeProjectRelatedLink(index: Int, linkIndex: Int) + func appendEmptyProject() } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 5e19f9ce..a07b9701 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -1,5 +1,6 @@ import BaseFeature import DesignSystem +import FoundationUtil import InputInformationBaseFeature import SwiftUI import ViewUtil @@ -26,11 +27,16 @@ struct InputProjectInfoView: View { VStack(spacing: 32) { InputInformationPageTitleView(title: "프로젝트", isRequired: false, pageCount: 7, selectedPage: 6) - projectListRowView(geometry: geometry) + ForEach(state.projectList.indices, id: \.self) { index in + projectListRowView(index: index, geometry: geometry) + } SMSSeparator(height: 1) SMSChip("추가") { + withAnimation { + intent.projectAppendButtonDidTap() + } } .foregroundColor(.sms(.system(.black))) .aligned(.trailing) @@ -42,7 +48,7 @@ struct InputProjectInfoView: View { } @ViewBuilder - func projectListRowView(geometry: GeometryProxy) -> some View { + func projectListRowView(index: Int, geometry: GeometryProxy) -> some View { VStack(alignment: .leading, spacing: 24) { HStack(spacing: 16) { SMSText("프로젝트", font: .title1) @@ -56,19 +62,19 @@ struct InputProjectInfoView: View { } .padding(.bottom, 8) - projectName() + projectName(index: index) - projectIcon() + projectIcon(index: index) - projectPreviewImageList() + projectPreviewImageList(index: index) - projectContentTextEditor() + projectContentTextEditor(index: index) - projectTechStack() + projectTechStack(index: index) - projectDuration() + projectDuration(index: index) - projectRelatedLink(geometry: geometry) + projectRelatedLink(index: index, geometry: geometry) } } } @@ -76,64 +82,97 @@ struct InputProjectInfoView: View { // MARK: - View Section private extension InputProjectInfoView { @ViewBuilder - func projectName() -> some View { - SMSTextField("프로젝트 이름 입력", text: .constant("")) - .titleWrapper("이름") + func projectName(index: Int) -> some View { + SMSTextField( + "프로젝트 이름 입력", + text: Binding( + get: { state.projectList[safe: index]?.name ?? "" }, + set: { intent.updateProjectName(index: index, name: $0) } + ) + ) + .titleWrapper("이름") } @ViewBuilder - func projectIcon() -> some View { - imagePlaceholder(size: 108) - .overlay { - SMSIcon(.photo) - } - .titleWrapper("아이콘") + func projectIcon(index: Int) -> some View { + if let iconData = state.projectList[safe: index]?.iconImage { + Image(uiImage: UIImage(data: iconData) ?? .init()) + .resizable() + .frame(width: 108, height: 108) + .cornerRadius(8) + } else { + imagePlaceholder(size: 108) + .overlay { + SMSIcon(.photo) + } + .titleWrapper("아이콘") + } } @ViewBuilder - func projectPreviewImageList() -> some View { + func projectPreviewImageList(index: Int) -> some View { LazyHStack(spacing: 8) { + let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] imagePlaceholder(size: 132) .overlay { VStack(spacing: 4) { SMSIcon(.photo) - SMSText("0/4", font: .body2) - .foregroundColor(.sms(.system(.black))) + + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) } } + .buttonWrapper { + } + + ForEach(projectPreviewImages, id: \.self) { previewImageData in + Image(uiImage: UIImage(data: previewImageData) ?? .init()) + .resizable() + .frame(width: 132, height: 132) + .cornerRadius(8) + } } .titleWrapper("미리보기 사진") } @ViewBuilder - func projectContentTextEditor() -> some View { - TextEditor(text: .constant("")) - .smsFont(.body1, color: .system(.black)) - .focused($projectContentIsFocused) - .colorMultiply(.sms(.neutral(.n10))) - .frame(minHeight: 48) - .cornerRadius(8) - .roundedStroke( - cornerRadius: 8, - color: projectContentIsFocused ? .sms(.primary(.p1)) : .clear, - lineWidth: projectContentIsFocused ? 1 : 0 + func projectContentTextEditor(index: Int) -> some View { + let projectContent = state.projectList[safe: index]?.content ?? "" + TextEditor( + text: Binding( + get: { projectContent }, + set: { intent.updateProjectContent(index: index, content: $0) } ) - .overlay(alignment: .topLeading) { - ConditionView(true) { - SMSText("프로젝트 내용 입력", font: .body1) - .foregroundColor(.sms(.neutral(.n30))) - .padding([.top, .leading], 12) - .onTapGesture { - projectContentIsFocused = true - } - } + ) + .smsFont(.body1, color: .system(.black)) + .focused($projectContentIsFocused) + .colorMultiply(.sms(.neutral(.n10))) + .frame(minHeight: 48) + .cornerRadius(8) + .roundedStroke( + cornerRadius: 8, + color: projectContentIsFocused ? .sms(.primary(.p1)) : .clear, + lineWidth: projectContentIsFocused ? 1 : 0 + ) + .overlay(alignment: .topLeading) { + ConditionView(projectContent.isEmpty) { + SMSText("프로젝트 내용 입력", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) + .padding([.top, .leading], 12) + .onTapGesture { + projectContentIsFocused = true + } } - .titleWrapper("내용") + } + .titleWrapper("내용") } @ViewBuilder - func projectTechStack() -> some View { + func projectTechStack(index: Int) -> some View { HStack(spacing: 8) { SMSIcon(.magnifyingglass) @@ -152,15 +191,18 @@ private extension InputProjectInfoView { } @ViewBuilder - func projectDuration() -> some View { + func projectDuration(index: Int) -> some View { HStack(spacing: 8) { - datePickerField { + let project = state.projectList[safe: index] + datePickerField(dateText: project?.startAtString ?? "") { + intent.projectStartAtDidSelect(index: index, startAt: $0) } .frame(maxWidth: .infinity) SMSIcon(.waterWave) - datePickerField { + datePickerField(dateText: project?.endAtString ?? "") { + intent.projectEndAtDidSelect(index: index, endAt: $0) } .frame(maxWidth: .infinity) } @@ -168,19 +210,26 @@ private extension InputProjectInfoView { } @ViewBuilder - func projectRelatedLink(geometry: GeometryProxy) -> some View { + func projectRelatedLink(index: Int, geometry: GeometryProxy) -> some View { VStack(spacing: 8) { - ForEach(1..<2, id: \.self) { index in + let relatedLinks = state.projectList[safe: index]?.relatedLinks ?? [] + ForEach(relatedLinks.indices, id: \.self) { relatedIndex in HStack(spacing: 16) { SMSTextField( "이름", - text: .constant("") + text: Binding( + get: { relatedLinks[relatedIndex].name }, + set: { intent.updateProjectLinkName(index: index, linkIndex: relatedIndex, name: $0) } + ) ) .frame(maxWidth: geometry.size.width / 4) SMSTextField( "URL", - text: .constant("") + text: Binding( + get: { relatedLinks[relatedIndex].url }, + set: { intent.updateProjectLinkURL(index: index, linkIndex: relatedIndex, url: $0) } + ) ) .frame(maxWidth: .infinity) @@ -191,8 +240,10 @@ private extension InputProjectInfoView { } } - SMSChip("추가") {} - .aligned(.leading) + SMSChip("추가") { + intent.relatedLinkAppendButtonDidTap(index: index) + } + .aligned(.leading) } .titleWrapper("관련 링크") } @@ -208,10 +259,13 @@ private extension InputProjectInfoView { } @ViewBuilder - func datePickerField(action: @escaping () -> Void) -> some View { + func datePickerField(dateText: String, action: @escaping (Date) -> Void) -> some View { SMSTextField( "yyyy.mm", - text: .constant(""), + text: Binding( + get: { dateText }, + set: { _ in } + ), isOnClear: false ) .disabled(true) @@ -222,7 +276,7 @@ private extension InputProjectInfoView { .simultaneousGesture( TapGesture() .onEnded { - action() + action(.init()) } ) } From 749a7ca1b0843a8945cc3dd768f19ca6c54255ab Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 10 Jul 2023 23:38:46 +0900 Subject: [PATCH 08/28] :sparkles: :: DatePicker --- .../BottomSheetYearMonthDatePickerView.swift | 73 +++++++++++ .../Intent/InputProjectInfoIntent.swift | 54 +++++++- .../InputProjectInfoIntentProtocol.swift | 15 ++- .../Sources/Model/InputProjectInfoModel.swift | 34 ++++- .../Model/InputProjectInfoModelProtocol.swift | 19 ++- .../Sources/Scene/InputProjectInfoView.swift | 119 +++++++++++++----- 6 files changed, 267 insertions(+), 47 deletions(-) create mode 100644 Projects/Core/DesignSystem/Sources/DatePicker/BottomSheetYearMonthDatePickerView.swift diff --git a/Projects/Core/DesignSystem/Sources/DatePicker/BottomSheetYearMonthDatePickerView.swift b/Projects/Core/DesignSystem/Sources/DatePicker/BottomSheetYearMonthDatePickerView.swift new file mode 100644 index 00000000..099776a4 --- /dev/null +++ b/Projects/Core/DesignSystem/Sources/DatePicker/BottomSheetYearMonthDatePickerView.swift @@ -0,0 +1,73 @@ +import DateUtil +import SwiftUI +import ViewUtil + +public extension View { + func datePicker( + isShowing: Binding, + completion: @escaping (Date) -> Void + ) -> some View { + self.smsBottomSheet(isShowing: isShowing) { + BottomSheetYearMonthDatePickerView(isShowing: isShowing, completion: completion) + } + } +} + +public struct BottomSheetYearMonthDatePickerView: View { + @Binding var isShowing: Bool + @Environment(\.dismiss) var dismiss + @State var selectedYear = 0 + @State var selectedMonth = 0 + var completion: (Date) -> Void + var currentYear: Int { + Date().year + } + + public init( + isShowing: Binding, + completion: @escaping (Date) -> Void + ) { + self._isShowing = isShowing + self.completion = completion + } + + public var body: some View { + VStack(spacing: 16) { + HStack { + SMSText("날짜선택", font: .title2) + + Spacer() + + SMSText("완료", font: .body2) + .foregroundColor(.sms(.primary(.p2))) + .buttonWrapper { + let year = selectedYear + currentYear - 10 + let month = selectedMonth + 1 + guard let date = DateComponents(calendar: .current, year: year, month: month).date else { return } + self.isShowing = false + self.completion(date) + } + } + + HStack(spacing: 0) { + Picker(selection: $selectedYear) { + ForEach(currentYear - 10 ..< currentYear + 1) { year in + SMSText("\(year)", font: .title1) + } + } label: { + EmptyView() + } + + Picker(selection: $selectedMonth) { + ForEach(1..<13) { month in + SMSText("\(month)", font: .title1) + } + } label: { + EmptyView() + } + } + .pickerStyle(.wheel) + } + .padding(.horizontal, 20) + } +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index bcfa21a3..1da397dd 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -1,3 +1,4 @@ +import DesignSystem import Foundation final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { @@ -11,12 +12,17 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.updateProjectName(index: index, name: name) } - func updateIconImage(index: Int, data: Data) { - model?.updateIconImage(index: index, data: data) + func updateIconImage(index: Int, image: PickedImageResult) { + model?.updateIconImage(index: index, image: image) } - func appendPreviewImage(index: Int, data: Data) { - model?.appendPreviewImage(index: index, data: data) + func appendPreviewImageButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedPreviewImagePicker(isPresented: true) + } + + func appendPreviewImage(index: Int, image: PickedImageResult) { + model?.appendPreviewImage(index: index, image: image) } func removePreviewImageDidTap(index: Int, previewIndex: Int) { @@ -35,6 +41,16 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.updateProjectMainTask(index: index, mainTask: mainTask) } + func projectStartAtButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedStartAtDatePicker(isPresented: true) + } + + func projectEndAtButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedEndAtDatePicker(isPresented: true) + } + func projectStartAtDidSelect(index: Int, startAt: Date) { model?.updateProjectStartAt(index: index, startAt: startAt) } @@ -62,4 +78,34 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { func projectAppendButtonDidTap() { model?.appendEmptyProject() } + + func iconImagePickerIsRequired() { + model?.updateIsPresentedImagePicker(isPresented: true) + } + + func iconImagePickerDismissed() { + model?.updateIsPresentedImagePicker(isPresented: false) + } + + func previewImagePickerDismissed() { + model?.updateIsPresentedPreviewImagePicker(isPresented: false) + } + + func startAtButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedStartAtDatePicker(isPresented: true) + } + + func endAtButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedEndAtDatePicker(isPresented: true) + } + + func startAtDatePickerDismissed() { + model?.updateIsPresentedStartAtDatePicker(isPresented: false) + } + + func endAtDatePickerDismissed() { + model?.updateIsPresentedEndAtDatePicker(isPresented: false) + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 4f4f7d8e..ec063b1c 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -1,13 +1,17 @@ +import DesignSystem import Foundation protocol InputProjectInfoIntentProtocol { func updateProjectName(index: Int, name: String) - func updateIconImage(index: Int, data: Data) - func appendPreviewImage(index: Int, data: Data) + func updateIconImage(index: Int, image: PickedImageResult) + func appendPreviewImageButtonDidTap(index: Int) + func appendPreviewImage(index: Int, image: PickedImageResult) func removePreviewImageDidTap(index: Int, previewIndex: Int) func updateProjectContent(index: Int, content: String) func techStacksDidSelect(index: Int, techStacks: [String]) func updateProjectMainTask(index: Int, mainTask: String) + func projectStartAtButtonDidTap(index: Int) + func projectEndAtButtonDidTap(index: Int) func projectStartAtDidSelect(index: Int, startAt: Date) func projectEndAtDidSelect(index: Int, endAt: Date) func updateProjectLinkName(index: Int, linkIndex: Int, name: String) @@ -15,4 +19,11 @@ protocol InputProjectInfoIntentProtocol { func relatedLinkAppendButtonDidTap(index: Int) func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) func projectAppendButtonDidTap() + func iconImagePickerIsRequired() + func iconImagePickerDismissed() + func previewImagePickerDismissed() + func startAtButtonDidTap(index: Int) + func endAtButtonDidTap(index: Int) + func startAtDatePickerDismissed() + func endAtDatePickerDismissed() } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 4ff818ca..aa17933f 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -1,9 +1,15 @@ +import DesignSystem import Foundation import FoundationUtil final class InputProjectInfoModel: ObservableObject, InputProjectInfoStateProtocol { @Published var projectList: [ProjectInfo] = [] @Published var projectErrorSetList: [Set] = [] + @Published var isPresentedImagePicker: Bool = false + @Published var isPresentedPreviewImagePicker: Bool = false + @Published var isPresentedStartAtDatePicker: Bool = false + @Published var isPresentedEndAtDatePicker: Bool = false + var focusedProjectIndex: Int = 0 } extension InputProjectInfoModel: InputProjectInfoActionProtocol { @@ -12,14 +18,14 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { self.projectList[index].name = name } - func updateIconImage(index: Int, data: Data) { + func updateIconImage(index: Int, image: PickedImageResult) { guard projectList[safe: index] != nil else { return } - self.projectList[index].iconImage = data + self.projectList[index].iconImage = image } - func appendPreviewImage(index: Int, data: Data) { + func appendPreviewImage(index: Int, image: PickedImageResult) { guard projectList[safe: index] != nil else { return } - self.projectList[index].previewImages.append(data) + self.projectList[index].previewImages.append(image) } func removePreviewImage(index: Int, previewIndex: Int) { @@ -94,4 +100,24 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { ) self.projectList.append(newProject) } + + func updateFocusedProjectIndex(index: Int) { + self.focusedProjectIndex = index + } + + func updateIsPresentedImagePicker(isPresented: Bool) { + self.isPresentedImagePicker = isPresented + } + + func updateIsPresentedPreviewImagePicker(isPresented: Bool) { + self.isPresentedPreviewImagePicker = isPresented + } + + func updateIsPresentedStartAtDatePicker(isPresented: Bool) { + self.isPresentedStartAtDatePicker = isPresented + } + + func updateIsPresentedEndAtDatePicker(isPresented: Bool) { + self.isPresentedEndAtDatePicker = isPresented + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index 23166d2d..924a85f0 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -1,10 +1,11 @@ import DateUtil +import DesignSystem import Foundation struct ProjectInfo { var name: String - var iconImage: Data? - var previewImages: [Data] + var iconImage: PickedImageResult? + var previewImages: [PickedImageResult] var content: String var techStacks: [String] var mainTask: String @@ -36,12 +37,17 @@ enum InputProjectInfoErrorField: Hashable { protocol InputProjectInfoStateProtocol { var projectList: [ProjectInfo] { get } var projectErrorSetList: [Set] { get } + var focusedProjectIndex: Int { get } + var isPresentedImagePicker: Bool { get } + var isPresentedPreviewImagePicker: Bool { get } + var isPresentedStartAtDatePicker: Bool { get } + var isPresentedEndAtDatePicker: Bool { get } } protocol InputProjectInfoActionProtocol: AnyObject { func updateProjectName(index: Int, name: String) - func updateIconImage(index: Int, data: Data) - func appendPreviewImage(index: Int, data: Data) + func updateIconImage(index: Int, image: PickedImageResult) + func appendPreviewImage(index: Int, image: PickedImageResult) func removePreviewImage(index: Int, previewIndex: Int) func updateProjectContent(index: Int, content: String) func updateProjectTechStacks(index: Int, techStacks: [String]) @@ -53,4 +59,9 @@ protocol InputProjectInfoActionProtocol: AnyObject { func appendEmptyRelatedLink(index: Int) func removeProjectRelatedLink(index: Int, linkIndex: Int) func appendEmptyProject() + func updateFocusedProjectIndex(index: Int) + func updateIsPresentedImagePicker(isPresented: Bool) + func updateIsPresentedPreviewImagePicker(isPresented: Bool) + func updateIsPresentedStartAtDatePicker(isPresented: Bool) + func updateIsPresentedEndAtDatePicker(isPresented: Bool) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index a07b9701..23e4c4bc 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -19,8 +19,8 @@ struct InputProjectInfoView: View { } var body: some View { - GeometryReader { geometry in - SMSNavigationTitleView(title: "프로젝트") { + SMSNavigationTitleView(title: "프로젝트") { + GeometryReader { geometry in ScrollView(showsIndicators: true) { SMSSeparator() @@ -45,6 +45,35 @@ struct InputProjectInfoView: View { } } } + .imagePicker( + isShow: Binding( + get: { state.isPresentedPreviewImagePicker }, + set: { _ in intent.previewImagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { + guard let image = $0 else { return } + intent.appendPreviewImage(index: state.focusedProjectIndex, image: image) + } + ) + ) + .datePicker( + isShowing: Binding( + get: { state.isPresentedStartAtDatePicker }, + set: { _ in intent.startAtDatePickerDismissed() } + ) + ) { date in + intent.projectStartAtDidSelect(index: state.focusedProjectIndex, startAt: date) + } + .datePicker( + isShowing: Binding( + get: { state.isPresentedEndAtDatePicker }, + set: { _ in intent.endAtDatePickerDismissed() } + ) + ) { date in + intent.projectEndAtDidSelect(index: state.focusedProjectIndex, endAt: date) + } } @ViewBuilder @@ -95,45 +124,69 @@ private extension InputProjectInfoView { @ViewBuilder func projectIcon(index: Int) -> some View { - if let iconData = state.projectList[safe: index]?.iconImage { - Image(uiImage: UIImage(data: iconData) ?? .init()) - .resizable() - .frame(width: 108, height: 108) - .cornerRadius(8) - } else { - imagePlaceholder(size: 108) - .overlay { - SMSIcon(.photo) - } - .titleWrapper("아이콘") + Group { + if let iconData = state.projectList[safe: index]?.iconImage { + Image(uiImage: iconData.uiImage) + .resizable() + .frame(width: 108, height: 108) + .cornerRadius(8) + } else { + imagePlaceholder(size: 108) + .overlay { + SMSIcon(.photo) + } + .titleWrapper("아이콘") + } } + .imagePicker( + isShow: Binding( + get: { state.isPresentedImagePicker && state.focusedProjectIndex == index }, + set: { _ in intent.iconImagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { state.projectList[safe: index]?.iconImage }, + set: { + guard let image = $0 else { return } + intent.updateIconImage(index: index, image: image) + } + ) + ) } @ViewBuilder func projectPreviewImageList(index: Int) -> some View { LazyHStack(spacing: 8) { let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] - imagePlaceholder(size: 132) - .overlay { - VStack(spacing: 4) { - SMSIcon(.photo) - - - SMSText( - "\(projectPreviewImages.count)/4", - font: .body2 - ) - .foregroundColor(.sms(.system(.black))) + ConditionView(projectPreviewImages.count < 4) { + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) + } } - } - .buttonWrapper { - } + .buttonWrapper { + intent.appendPreviewImageButtonDidTap(index: index) + } + } - ForEach(projectPreviewImages, id: \.self) { previewImageData in - Image(uiImage: UIImage(data: previewImageData) ?? .init()) + ForEach(projectPreviewImages.indices, id: \.self) { previewIndex in + Image(uiImage: projectPreviewImages[previewIndex].uiImage) .resizable() .frame(width: 132, height: 132) .cornerRadius(8) + .overlay(alignment: .topTrailing) { + SMSIcon(.xmark) + .padding(4) + .buttonWrapper { + intent.removePreviewImageDidTap(index: index, previewIndex: previewIndex) + } + } } } .titleWrapper("미리보기 사진") @@ -195,14 +248,14 @@ private extension InputProjectInfoView { HStack(spacing: 8) { let project = state.projectList[safe: index] datePickerField(dateText: project?.startAtString ?? "") { - intent.projectStartAtDidSelect(index: index, startAt: $0) + intent.startAtButtonDidTap(index: index) } .frame(maxWidth: .infinity) SMSIcon(.waterWave) datePickerField(dateText: project?.endAtString ?? "") { - intent.projectEndAtDidSelect(index: index, endAt: $0) + intent.endAtButtonDidTap(index: index) } .frame(maxWidth: .infinity) } @@ -259,7 +312,7 @@ private extension InputProjectInfoView { } @ViewBuilder - func datePickerField(dateText: String, action: @escaping (Date) -> Void) -> some View { + func datePickerField(dateText: String, action: @escaping () -> Void) -> some View { SMSTextField( "yyyy.mm", text: Binding( @@ -276,7 +329,7 @@ private extension InputProjectInfoView { .simultaneousGesture( TapGesture() .onEnded { - action(.init()) + action() } ) } From 8081a99f4344bfcff2efef218c147902f7315f5e Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 10 Jul 2023 23:42:48 +0900 Subject: [PATCH 09/28] :sparkles: :: DatePick and icon pick --- .../Sources/Intent/InputProjectInfoIntent.swift | 3 ++- .../Sources/Intent/InputProjectInfoIntentProtocol.swift | 2 +- .../Sources/Model/InputProjectInfoModel.swift | 3 +++ .../Sources/Scene/InputProjectInfoView.swift | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index 1da397dd..32cee2e2 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -79,7 +79,8 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.appendEmptyProject() } - func iconImagePickerIsRequired() { + func iconImageButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) model?.updateIsPresentedImagePicker(isPresented: true) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index ec063b1c..990a8ec0 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -19,7 +19,7 @@ protocol InputProjectInfoIntentProtocol { func relatedLinkAppendButtonDidTap(index: Int) func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) func projectAppendButtonDidTap() - func iconImagePickerIsRequired() + func iconImageButtonDidTap(index: Int) func iconImagePickerDismissed() func previewImagePickerDismissed() func startAtButtonDidTap(index: Int) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index aa17933f..245f1a2e 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -56,6 +56,9 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { func updateProjectEndAt(index: Int, endAt: Date) { guard projectList[safe: index] != nil else { return } self.projectList[index].endAt = endAt + if endAt <= projectList[index].startAt { + self.projectList[index].startAt = endAt + } } func updateProjectLinkName(index: Int, linkIndex: Int, name: String) { diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 23e4c4bc..8a5b7f4a 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -138,6 +138,9 @@ private extension InputProjectInfoView { .titleWrapper("아이콘") } } + .buttonWrapper { + intent.iconImageButtonDidTap(index: index) + } .imagePicker( isShow: Binding( get: { state.isPresentedImagePicker && state.focusedProjectIndex == index }, From 2608d413f1ba89260fddea726d2cf584a9397f31 Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 10 Jul 2023 23:43:48 +0900 Subject: [PATCH 10/28] =?UTF-8?q?:lipstick:=20::=20titleWrapper=EB=A5=BC?= =?UTF-8?q?=20buttonWrapper=EC=9D=98=20=EC=98=81=ED=96=A5=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=EB=B6=80=ED=84=B0=20=EB=B6=84=EB=A6=AC=EC=8B=9C?= =?UTF-8?q?=ED=82=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/InputProjectInfoView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 8a5b7f4a..5109ce56 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -135,12 +135,12 @@ private extension InputProjectInfoView { .overlay { SMSIcon(.photo) } - .titleWrapper("아이콘") } } .buttonWrapper { intent.iconImageButtonDidTap(index: index) } + .titleWrapper("아이콘") .imagePicker( isShow: Binding( get: { state.isPresentedImagePicker && state.focusedProjectIndex == index }, From 46923cf4e8e229d5e1b8de8789702560ebec7721 Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 10 Jul 2023 23:50:21 +0900 Subject: [PATCH 11/28] =?UTF-8?q?:lipstick:=20::=20=EC=9D=B4=EC=A0=84,=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/InputProjectInfoView.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 5109ce56..657da666 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -40,6 +40,17 @@ struct InputProjectInfoView: View { } .foregroundColor(.sms(.system(.black))) .aligned(.trailing) + + HStack(spacing: 8) { + CTAButton(text: "이전", style: .outline) { + } + .frame(maxWidth: geometry.size.width / 3) + + CTAButton(text: "다음") { + } + .frame(maxWidth: .infinity) + } + .padding(.bottom, 32) } .padding([.top, .horizontal], 20) } From da01671ba0527d576a666320288abbaa9b3abcef Mon Sep 17 00:00:00 2001 From: baegteun Date: Tue, 11 Jul 2023 00:03:46 +0900 Subject: [PATCH 12/28] =?UTF-8?q?:sparkles:=20::=20project=20=EC=A0=91?= =?UTF-8?q?=EA=B8=B0!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/InputProjectInfoIntent.swift | 8 +++++ .../InputProjectInfoIntentProtocol.swift | 2 ++ .../Sources/Model/InputProjectInfoModel.swift | 13 +++++++ .../Model/InputProjectInfoModelProtocol.swift | 3 ++ .../Sources/Scene/InputProjectInfoView.swift | 34 +++++++++++++------ 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index 32cee2e2..ba252624 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -8,6 +8,10 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { self.model = model } + func projectToggleButtonDidTap(index: Int) { + model?.toggleCollapsedProject(index: index) + } + func updateProjectName(index: Int, name: String) { model?.updateProjectName(index: index, name: name) } @@ -79,6 +83,10 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.appendEmptyProject() } + func projectRemoveButtonDidTap(index: Int) { + model?.removeProject(index: index) + } + func iconImageButtonDidTap(index: Int) { model?.updateFocusedProjectIndex(index: index) model?.updateIsPresentedImagePicker(isPresented: true) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 990a8ec0..cdbae4d0 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -2,6 +2,7 @@ import DesignSystem import Foundation protocol InputProjectInfoIntentProtocol { + func projectToggleButtonDidTap(index: Int) func updateProjectName(index: Int, name: String) func updateIconImage(index: Int, image: PickedImageResult) func appendPreviewImageButtonDidTap(index: Int) @@ -19,6 +20,7 @@ protocol InputProjectInfoIntentProtocol { func relatedLinkAppendButtonDidTap(index: Int) func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) func projectAppendButtonDidTap() + func projectRemoveButtonDidTap(index: Int) func iconImageButtonDidTap(index: Int) func iconImagePickerDismissed() func previewImagePickerDismissed() diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 245f1a2e..9c38995d 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -4,6 +4,7 @@ import FoundationUtil final class InputProjectInfoModel: ObservableObject, InputProjectInfoStateProtocol { @Published var projectList: [ProjectInfo] = [] + @Published var collapsedProject: [Bool] = [] @Published var projectErrorSetList: [Set] = [] @Published var isPresentedImagePicker: Bool = false @Published var isPresentedPreviewImagePicker: Bool = false @@ -13,6 +14,11 @@ final class InputProjectInfoModel: ObservableObject, InputProjectInfoStateProtoc } extension InputProjectInfoModel: InputProjectInfoActionProtocol { + func toggleCollapsedProject(index: Int) { + guard collapsedProject[safe: index] != nil else { return } + self.collapsedProject[index].toggle() + } + func updateProjectName(index: Int, name: String) { guard projectList[safe: index] != nil else { return } self.projectList[index].name = name @@ -102,6 +108,13 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { relatedLinks: [] ) self.projectList.append(newProject) + self.collapsedProject.append(false) + } + + func removeProject(index: Int) { + guard projectList[safe: index] != nil, collapsedProject[safe: index] != nil else { return } + self.projectList.remove(at: index) + self.collapsedProject.remove(at: index) } func updateFocusedProjectIndex(index: Int) { diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index 924a85f0..6d61d2cb 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -36,6 +36,7 @@ enum InputProjectInfoErrorField: Hashable { protocol InputProjectInfoStateProtocol { var projectList: [ProjectInfo] { get } + var collapsedProject: [Bool] { get } var projectErrorSetList: [Set] { get } var focusedProjectIndex: Int { get } var isPresentedImagePicker: Bool { get } @@ -45,6 +46,7 @@ protocol InputProjectInfoStateProtocol { } protocol InputProjectInfoActionProtocol: AnyObject { + func toggleCollapsedProject(index: Int) func updateProjectName(index: Int, name: String) func updateIconImage(index: Int, image: PickedImageResult) func appendPreviewImage(index: Int, image: PickedImageResult) @@ -59,6 +61,7 @@ protocol InputProjectInfoActionProtocol: AnyObject { func appendEmptyRelatedLink(index: Int) func removeProjectRelatedLink(index: Int, linkIndex: Int) func appendEmptyProject() + func removeProject(index: Int) func updateFocusedProjectIndex(index: Int) func updateIsPresentedImagePicker(isPresented: Bool) func updateIsPresentedPreviewImagePicker(isPresented: Bool) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 657da666..dc3d2ba6 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -27,11 +27,13 @@ struct InputProjectInfoView: View { VStack(spacing: 32) { InputInformationPageTitleView(title: "프로젝트", isRequired: false, pageCount: 7, selectedPage: 6) - ForEach(state.projectList.indices, id: \.self) { index in - projectListRowView(index: index, geometry: geometry) - } + VStack(spacing: 24) { + ForEach(state.projectList.indices, id: \.self) { index in + projectListRowView(index: index, geometry: geometry) - SMSSeparator(height: 1) + SMSSeparator(height: 1) + } + } SMSChip("추가") { withAnimation { @@ -89,6 +91,7 @@ struct InputProjectInfoView: View { @ViewBuilder func projectListRowView(index: Int, geometry: GeometryProxy) -> some View { + let collapsed = state.collapsedProject[safe: index] ?? false VStack(alignment: .leading, spacing: 24) { HStack(spacing: 16) { SMSText("프로젝트", font: .title1) @@ -97,24 +100,33 @@ struct InputProjectInfoView: View { Spacer() SMSIcon(.downChevron) + .rotationEffect(collapsed ? .degrees(90) : .degrees(0)) + .buttonWrapper { + intent.projectToggleButtonDidTap(index: index) + } SMSIcon(.xmarkOutline) + .buttonWrapper { + intent.projectRemoveButtonDidTap(index: index) + } } .padding(.bottom, 8) - projectName(index: index) + ConditionView(!collapsed) { + projectName(index: index) - projectIcon(index: index) + projectIcon(index: index) - projectPreviewImageList(index: index) + projectPreviewImageList(index: index) - projectContentTextEditor(index: index) + projectContentTextEditor(index: index) - projectTechStack(index: index) + projectTechStack(index: index) - projectDuration(index: index) + projectDuration(index: index) - projectRelatedLink(index: index, geometry: geometry) + projectRelatedLink(index: index, geometry: geometry) + } } } } From 898b489622102e001973953f0efe1761ffcf2873 Mon Sep 17 00:00:00 2001 From: baegteun Date: Tue, 11 Jul 2023 18:06:54 +0900 Subject: [PATCH 13/28] =?UTF-8?q?:bug:=20::=20=EA=B7=BC=EB=AC=B4=20?= =?UTF-8?q?=EC=A7=80=EC=97=AD=20=EB=B2=84=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/InputWorkInfoIntent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/InputWorkInfoFeature/Sources/Intent/InputWorkInfoIntent.swift b/Projects/Feature/InputWorkInfoFeature/Sources/Intent/InputWorkInfoIntent.swift index f7b9be71..a78d249a 100644 --- a/Projects/Feature/InputWorkInfoFeature/Sources/Intent/InputWorkInfoIntent.swift +++ b/Projects/Feature/InputWorkInfoFeature/Sources/Intent/InputWorkInfoIntent.swift @@ -22,7 +22,7 @@ final class InputWorkInfoIntent: InputWorkInfoIntentProtocol { func updateWorkRegion(region: String, at index: Int) { let regexValidator = RegexValidator(pattern: "^[가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9]$") - guard regexValidator.validate(region) || region.isEmpty else { return } + guard regexValidator.validate(region), region.count <= 10 else { return } model?.updateWorkRegion(region: region, at: index) } From 7101e2bfd4800a7150ed39f2be3ecbaa7611699c Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:23:53 +0900 Subject: [PATCH 14/28] :sparkles: :: InputProjectInfo DI --- .../Sources/Application/DI/AppComponent.swift | 10 +++++-- .../Demo/Sources/AppDelegate.swift | 9 +++++- .../Interface/InputProjectInfoBuildable.swift | 2 +- .../Interface/InputProjectInfoDelegate.swift | 30 +++++++++++++++++++ .../DI/InputProjectInfoComponent.swift | 9 +++--- .../Intent/InputProjectInfoIntent.swift | 8 ++++- 6 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift diff --git a/Projects/App/Sources/Application/DI/AppComponent.swift b/Projects/App/Sources/Application/DI/AppComponent.swift index 6359218c..7a19250d 100644 --- a/Projects/App/Sources/Application/DI/AppComponent.swift +++ b/Projects/App/Sources/Application/DI/AppComponent.swift @@ -3,6 +3,8 @@ import AuthDomainInterface import BaseDomain import FileDomain import FileDomainInterface +import FilterFeature +import FilterFeatureInterface import InputCertificateInfoFeature import InputCertificateInfoFeatureInterface import InputInformationFeature @@ -13,12 +15,12 @@ import InputMilitaryInfoFeature import InputMilitaryInfoFeatureInterface import InputProfileInfoFeature import InputProfileInfoFeatureInterface +import InputProjectInfoFeature +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeature import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeature import InputWorkInfoFeatureInterface -import FilterFeature -import FilterFeatureInterface import JwtStore import JwtStoreInterface import KeychainModule @@ -86,6 +88,10 @@ final class AppComponent: BootstrapComponent { InputLanguageInfoComponent(parent: self) } + var inputProjectInfoBuildable: any InputProjectInfoBuildable { + InputProjectInfoComponent(parent: self) + } + var mainBuildable: any MainBuildable { MainComponent(parent: self) } diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift index 1d21d8f0..b1ce4dab 100644 --- a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift @@ -1,14 +1,21 @@ import BaseFeature import SwiftUI +import InputProjectInfoFeatureInterface @testable import InputProjectInfoFeature +final class DummyInputProjectInfoDelegate: InputProjectInfoDelegate { + func projectInfoPrevButtonDidTap() {} + func completeToInputProjectInfo(input: InputProjectInfoObject) {} +} + @main struct InputProjectInfoApp: App { var body: some Scene { WindowGroup { let model = InputProjectInfoModel() let intent = InputProjectInfoIntent( - model: model + model: model, + delegate: DummyInputProjectInfoDelegate() ) let container = MVIContainer( intent: intent as InputProjectInfoIntentProtocol, diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift index 4bc8b8ea..cb42e736 100644 --- a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift +++ b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoBuildable.swift @@ -2,5 +2,5 @@ import SwiftUI public protocol InputProjectInfoBuildable { associatedtype ViewType: View - func makeView() -> ViewType + func makeView(delegate: any InputProjectInfoDelegate) -> ViewType } diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift new file mode 100644 index 00000000..ca0efd65 --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift @@ -0,0 +1,30 @@ +import Foundation + +public protocol InputProjectInfoDelegate: AnyObject { + func projectInfoPrevButtonDidTap() + func completeToInputProjectInfo(input: InputProjectInfoObject) +} + +public struct InputProjectInfoObject { + public let name: String + public let iconImage: ImageFile? + public let previewImages: [ImageFile] + public let content: String + public let techStacks: [String] + public let mainTask: String + public let startAt: Date + public let endAt: Date? + public let relatedLinks: [RelatedLink] +} + +public extension InputProjectInfoObject { + struct ImageFile { + public let name: String + public let data: Data + } + + struct RelatedLink { + public let name: String + public let url: String + } +} diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift index a7d7ef5c..40031cf1 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift @@ -3,15 +3,16 @@ import InputProjectInfoFeatureInterface import NeedleFoundation import SwiftUI -protocol InputProjectInfoDependency: Dependency {} +public protocol InputProjectInfoDependency: Dependency {} -final class InputProjectInfoComponent: +public final class InputProjectInfoComponent: Component, InputProjectInfoBuildable { - func makeView() -> some View { + public func makeView(delegate: any InputProjectInfoDelegate) -> some View { let model = InputProjectInfoModel() let intent = InputProjectInfoIntent( - model: model + model: model, + delegate: delegate ) let container = MVIContainer( intent: intent as InputProjectInfoIntentProtocol, diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index ba252624..f44a21a1 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -1,11 +1,17 @@ import DesignSystem import Foundation +import InputProjectInfoFeatureInterface final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { private weak var model: (any InputProjectInfoActionProtocol)? + private weak var delegate: (any InputProjectInfoDelegate)? - init(model: any InputProjectInfoActionProtocol) { + init( + model: any InputProjectInfoActionProtocol, + delegate: any InputProjectInfoDelegate + ) { self.model = model + self.delegate = delegate } func projectToggleButtonDidTap(index: Int) { From ee692263fc6213faea2d66089c5bfb29446c519d Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:35:09 +0900 Subject: [PATCH 15/28] =?UTF-8?q?:sparkles:=20::=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=8D=BC=EB=84=90=EC=97=90=20ProjectInfo?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/NeedleGenerated.swift | 22 +++++++++++++++++++ .../Scene/InputCertificateInfoView.swift | 2 +- .../DI/InputInformationComponent.swift | 5 ++++- .../Intent/InputInformationIntent.swift | 17 +++++++++++--- .../InputInformationIntentProtocol.swift | 10 +++++---- .../Sources/Model/InputInformationModel.swift | 6 +++++ .../Model/InputInformationModelProtocol.swift | 4 ++++ .../Sources/Scene/InputInformationView.swift | 14 +++++++++--- .../Sources/Scene/InputLanguageInfoView.swift | 2 +- .../Sources/Scene/InputMilitaryInfoView.swift | 2 +- .../Sources/Scene/InputProfileInfoView.swift | 2 +- .../Interface/InputProjectInfoDelegate.swift | 2 +- .../Scene/InputSchoolLifeInfoView.swift | 2 +- .../Sources/Scene/InputWorkInfoView.swift | 2 +- 14 files changed, 74 insertions(+), 18 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 5e1d73e4..34cbaea3 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -19,6 +19,7 @@ import InputMilitaryInfoFeature import InputMilitaryInfoFeatureInterface import InputProfileInfoFeature import InputProfileInfoFeatureInterface +import InputProjectInfoFeature import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeature import InputSchoolLifeInfoFeatureInterface @@ -89,6 +90,17 @@ private class SplashDependencye0cb7136f2ec3edfd60aProvider: SplashDependency { private func factoryace9f05f51d68f4c0677f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return SplashDependencye0cb7136f2ec3edfd60aProvider(appComponent: parent1(component) as! AppComponent) } +private class InputProjectInfoDependencye065c7f60c5c520999a0Provider: InputProjectInfoDependency { + + + init() { + + } +} +/// ^->AppComponent->InputProjectInfoComponent +private func factory2378736e5949c5e8e9f4e3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { + return InputProjectInfoDependencye065c7f60c5c520999a0Provider() +} private class InputWorkInfoDependency74441f61366e4e5af9a2Provider: InputWorkInfoDependency { @@ -247,6 +259,9 @@ private class InputInformationDependency7b32a8e7e8a8f0ab5466Provider: InputInfor var inputLanguageInfoBuildable: any InputLanguageInfoBuildable { return appComponent.inputLanguageInfoBuildable } + var inputProjectInfoBuildable: any InputProjectInfoBuildable { + return appComponent.inputProjectInfoBuildable + } var fileDomainBuildable: any FileDomainBuildable { return appComponent.fileDomainBuildable } @@ -406,6 +421,11 @@ extension SplashComponent: Registration { keyPathToName[\SplashDependency.authDomainBuildable] = "authDomainBuildable-any AuthDomainBuildable" } } +extension InputProjectInfoComponent: Registration { + public func registerItems() { + + } +} extension InputWorkInfoComponent: Registration { public func registerItems() { @@ -469,6 +489,7 @@ extension InputInformationComponent: Registration { keyPathToName[\InputInformationDependency.inputMilitaryInfoBuildable] = "inputMilitaryInfoBuildable-any InputMilitaryInfoBuildable" keyPathToName[\InputInformationDependency.inputCertificateInfoBuildable] = "inputCertificateInfoBuildable-any InputCertificateInfoBuildable" keyPathToName[\InputInformationDependency.inputLanguageInfoBuildable] = "inputLanguageInfoBuildable-any InputLanguageInfoBuildable" + keyPathToName[\InputInformationDependency.inputProjectInfoBuildable] = "inputProjectInfoBuildable-any InputProjectInfoBuildable" keyPathToName[\InputInformationDependency.fileDomainBuildable] = "fileDomainBuildable-any FileDomainBuildable" keyPathToName[\InputInformationDependency.studentDomainBuildable] = "studentDomainBuildable-any StudentDomainBuildable" } @@ -540,6 +561,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->SplashComponent", factoryace9f05f51d68f4c0677f47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->InputProjectInfoComponent", factory2378736e5949c5e8e9f4e3b0c44298fc1c149afb) registerProviderFactory("^->AppComponent->InputWorkInfoComponent", factoryfff86bd7854b30412216e3b0c44298fc1c149afb) registerProviderFactory("^->AppComponent->MainComponent", factoryc9274e46e78e70f29c54f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->InputSchoolLifeInfoComponent", factorydc1feebed8f042db375fe3b0c44298fc1c149afb) diff --git a/Projects/Feature/InputCertificateInfoFeature/Sources/Scene/InputCertificateInfoView.swift b/Projects/Feature/InputCertificateInfoFeature/Sources/Scene/InputCertificateInfoView.swift index cacb4a7e..0a300f0e 100644 --- a/Projects/Feature/InputCertificateInfoFeature/Sources/Scene/InputCertificateInfoView.swift +++ b/Projects/Feature/InputCertificateInfoFeature/Sources/Scene/InputCertificateInfoView.swift @@ -20,7 +20,7 @@ struct InputCertificateInfoView: View { InputInformationPageTitleView( title: "자격증", isRequired: false, - pageCount: 6, + pageCount: 7, selectedPage: 4 ) diff --git a/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift b/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift index 08a5132a..5dcc33b0 100644 --- a/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift +++ b/Projects/Feature/InputInformationFeature/Sources/DI/InputInformationComponent.swift @@ -5,11 +5,12 @@ import InputInformationFeatureInterface import InputLanguageInfoFeatureInterface import InputMilitaryInfoFeatureInterface import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface import NeedleFoundation -import SwiftUI import StudentDomainInterface +import SwiftUI public protocol InputInformationDependency: Dependency { var inputProfileInfoBuildable: any InputProfileInfoBuildable { get } @@ -18,6 +19,7 @@ public protocol InputInformationDependency: Dependency { var inputMilitaryInfoBuildable: any InputMilitaryInfoBuildable { get } var inputCertificateInfoBuildable: any InputCertificateInfoBuildable { get } var inputLanguageInfoBuildable: any InputLanguageInfoBuildable { get } + var inputProjectInfoBuildable: any InputProjectInfoBuildable { get } var fileDomainBuildable: any FileDomainBuildable { get } var studentDomainBuildable: any StudentDomainBuildable { get } } @@ -47,6 +49,7 @@ public final class InputInformationComponent: inputMilitaryInfoBuildable: dependency.inputMilitaryInfoBuildable, inputCertificateInfoBuildable: dependency.inputCertificateInfoBuildable, inputLanguageInfoBuildable: dependency.inputLanguageInfoBuildable, + inputProjectInfoBuildable: dependency.inputProjectInfoBuildable, container: container ) } diff --git a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift index 31bccce0..269dd1cc 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift @@ -1,13 +1,14 @@ +import FileDomainInterface import Foundation import InputCertificateInfoFeatureInterface +import InputInformationFeatureInterface +import InputLanguageInfoFeatureInterface import InputMilitaryInfoFeatureInterface import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface -import InputLanguageInfoFeatureInterface -import InputInformationFeatureInterface import StudentDomainInterface -import FileDomainInterface final class InputInformationIntent: InputInformationIntentProtocol { private weak var model: (any InputInformationActionProtocol)? @@ -147,6 +148,16 @@ extension InputInformationIntent: InputLanguageDelegate { let languageCertificates: [InputStudentInformationRequestDTO.LanguageCertificate] = languages .map { .init(languageCertificateName: $0.name, score: $0.score) } model?.updateLanguages(languages: languageCertificates) + } +} + +extension InputInformationIntent: InputProjectInfoDelegate { + func projectInfoPrevButtonDidTap() { + model?.prevButtonDidTap() + } + + func completeToInputProjectInfo(input: [InputProjectInfoObject]) { + model?.updateProjects(projects: input) model?.updateIsCompleteToInputAllInfo(isComplete: true) } } diff --git a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift index ac18696e..cac0fd6b 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntentProtocol.swift @@ -1,10 +1,11 @@ import Foundation +import InputCertificateInfoFeatureInterface +import InputLanguageInfoFeatureInterface +import InputMilitaryInfoFeatureInterface import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface -import InputMilitaryInfoFeatureInterface -import InputCertificateInfoFeatureInterface -import InputLanguageInfoFeatureInterface protocol InputInformationIntentProtocol: InputProfileDelegate, @@ -12,7 +13,8 @@ protocol InputInformationIntentProtocol: InputWorkDelegate, InputMilitaryDelegate, InputCertificateDelegate, - InputLanguageDelegate { + InputLanguageDelegate, + InputProjectInfoDelegate { func completeToInputAllInfo(state: any InputInformationStateProtocol) func errorAlertDismissed() } diff --git a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift index f447826a..0be9d04d 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift @@ -1,5 +1,6 @@ import Foundation import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface import StudentDomainInterface @@ -15,6 +16,7 @@ final class InputInformationModel: ObservableObject, InputInformationStateProtoc var certificates: [String] = [] var militaryServiceType: MilitaryServiceType? var languages: [InputStudentInformationRequestDTO.LanguageCertificate] = [] + var projects: [InputProjectInfoObject] = [] @Published var isCompleteToInputAllInfo: Bool = false } @@ -57,6 +59,10 @@ extension InputInformationModel: InputInformationActionProtocol { self.languages = languages } + func updateProjects(projects: [InputProjectInfoObject]) { + self.projects = projects + } + func updateIsCompleteToInputAllInfo(isComplete: Bool) { self.isCompleteToInputAllInfo = isComplete } diff --git a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift index c6cac5fe..54492a52 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift @@ -1,5 +1,6 @@ import Foundation import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface import StudentDomainInterface @@ -11,6 +12,7 @@ enum InformationPhase: CaseIterable { case military case certificate case language + case project } protocol InputInformationStateProtocol { @@ -24,6 +26,7 @@ protocol InputInformationStateProtocol { var certificates: [String] { get } var militaryServiceType: MilitaryServiceType? { get } var languages: [InputStudentInformationRequestDTO.LanguageCertificate] { get } + var projects: [InputProjectInfoObject] { get } var isCompleteToInputAllInfo: Bool { get } } @@ -36,6 +39,7 @@ protocol InputInformationActionProtocol: AnyObject { func updateCertificates(certificates: [String]) func updateMilitaryServiceType(type: MilitaryServiceType) func updateLanguages(languages: [InputStudentInformationRequestDTO.LanguageCertificate]) + func updateProjects(projects: [InputProjectInfoObject]) func updateIsCompleteToInputAllInfo(isComplete: Bool) func updateIsLoading(isLoading: Bool) func updateIsError(isError: Bool) diff --git a/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift b/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift index 33f0c177..ff511ca1 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Scene/InputInformationView.swift @@ -1,11 +1,12 @@ import BaseFeature import DesignSystem +import InputCertificateInfoFeatureInterface +import InputLanguageInfoFeatureInterface +import InputMilitaryInfoFeatureInterface import InputProfileInfoFeatureInterface +import InputProjectInfoFeatureInterface import InputSchoolLifeInfoFeatureInterface import InputWorkInfoFeatureInterface -import InputMilitaryInfoFeatureInterface -import InputCertificateInfoFeatureInterface -import InputLanguageInfoFeatureInterface import SwiftUI import ViewUtil @@ -20,6 +21,7 @@ struct InputInformationView: View { private let inputMilitaryInfoBuildable: any InputMilitaryInfoBuildable private let inputCertificateInfoBuildable: any InputCertificateInfoBuildable private let inputLanguageInfoBuildable: any InputLanguageInfoBuildable + private let inputProjectInfoBuildable: any InputProjectInfoBuildable init( inputProfileInfoBuildable: any InputProfileInfoBuildable, @@ -28,6 +30,7 @@ struct InputInformationView: View { inputMilitaryInfoBuildable: any InputMilitaryInfoBuildable, inputCertificateInfoBuildable: any InputCertificateInfoBuildable, inputLanguageInfoBuildable: any InputLanguageInfoBuildable, + inputProjectInfoBuildable: any InputProjectInfoBuildable, container: MVIContainer ) { self.inputProfileInfoBuildable = inputProfileInfoBuildable @@ -36,6 +39,7 @@ struct InputInformationView: View { self.inputMilitaryInfoBuildable = inputMilitaryInfoBuildable self.inputCertificateInfoBuildable = inputCertificateInfoBuildable self.inputLanguageInfoBuildable = inputLanguageInfoBuildable + self.inputProjectInfoBuildable = inputProjectInfoBuildable self._container = StateObject(wrappedValue: container) } @@ -69,6 +73,10 @@ struct InputInformationView: View { inputLanguageInfoBuildable.makeView(delegate: intent) .eraseToAnyView() .tag(InformationPhase.language) + + inputProjectInfoBuildable.makeView(delegate: intent) + .eraseToAnyView() + .tag(InformationPhase.project) } .ignoresSafeArea(.container, edges: .top) .smsAlert( diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift index 384dc575..fb0ac0fb 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift @@ -20,7 +20,7 @@ struct InputLanguageInfoView: View { InputInformationPageTitleView( title: "외국어", isRequired: false, - pageCount: 6, + pageCount: 7, selectedPage: 5 ) diff --git a/Projects/Feature/InputMilitaryInfoFeature/Sources/Scene/InputMilitaryInfoView.swift b/Projects/Feature/InputMilitaryInfoFeature/Sources/Scene/InputMilitaryInfoView.swift index 62655c9f..f07ea644 100644 --- a/Projects/Feature/InputMilitaryInfoFeature/Sources/Scene/InputMilitaryInfoView.swift +++ b/Projects/Feature/InputMilitaryInfoFeature/Sources/Scene/InputMilitaryInfoView.swift @@ -15,7 +15,7 @@ struct InputMilitaryInfoView: View { SMSSeparator() VStack(spacing: 32) { - InputInformationPageTitleView(title: "병역", pageCount: 6, selectedPage: 3) + InputInformationPageTitleView(title: "병역", pageCount: 7, selectedPage: 3) VStack(spacing: 24) { SMSTextField( diff --git a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift index 2e8e6147..5a16b2fc 100644 --- a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift +++ b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift @@ -34,7 +34,7 @@ struct InputProfileInfoView: View { SMSSeparator() VStack(spacing: 32) { - InputInformationPageTitleView(title: "프로필", pageCount: 6, selectedPage: 0) + InputInformationPageTitleView(title: "프로필", pageCount: 7, selectedPage: 0) VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 8) { diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift index ca0efd65..6eed2baf 100644 --- a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift @@ -2,7 +2,7 @@ import Foundation public protocol InputProjectInfoDelegate: AnyObject { func projectInfoPrevButtonDidTap() - func completeToInputProjectInfo(input: InputProjectInfoObject) + func completeToInputProjectInfo(input: [InputProjectInfoObject]) } public struct InputProjectInfoObject { diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift index 5f963864..41dc5d6b 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift @@ -15,7 +15,7 @@ struct InputSchoolLifeInfoView: View { SMSSeparator() VStack(spacing: 32) { - InputInformationPageTitleView(title: "학교 생활", pageCount: 6, selectedPage: 1) + InputInformationPageTitleView(title: "학교 생활", pageCount: 7, selectedPage: 1) VStack(spacing: 24) { SMSTextField( diff --git a/Projects/Feature/InputWorkInfoFeature/Sources/Scene/InputWorkInfoView.swift b/Projects/Feature/InputWorkInfoFeature/Sources/Scene/InputWorkInfoView.swift index bf9222b9..fdec1951 100644 --- a/Projects/Feature/InputWorkInfoFeature/Sources/Scene/InputWorkInfoView.swift +++ b/Projects/Feature/InputWorkInfoFeature/Sources/Scene/InputWorkInfoView.swift @@ -18,7 +18,7 @@ struct InputWorkInfoView: View { SMSSeparator() VStack(spacing: 32) { - InputInformationPageTitleView(title: "근무 조건", pageCount: 6, selectedPage: 2) + InputInformationPageTitleView(title: "근무 조건", pageCount: 7, selectedPage: 2) VStack(spacing: 24) { SMSTextField( From 62fbb3c87ecb5083b5597a4de86410aad2f271be Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:42:55 +0900 Subject: [PATCH 16/28] :sparkles: :: InputProject intnet action --- .../Interface/InputProjectInfoDelegate.swift | 32 +++++++++++++++ .../Intent/InputProjectInfoIntent.swift | 39 +++++++++++++++++++ .../InputProjectInfoIntentProtocol.swift | 2 + 3 files changed, 73 insertions(+) diff --git a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift index 6eed2baf..e61a0ac8 100644 --- a/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Interface/InputProjectInfoDelegate.swift @@ -15,16 +15,48 @@ public struct InputProjectInfoObject { public let startAt: Date public let endAt: Date? public let relatedLinks: [RelatedLink] + + public init( + name: String, + iconImage: ImageFile?, + previewImages: [ImageFile], + content: String, + techStacks: [String], + mainTask: String, + startAt: Date, + endAt: Date?, + relatedLinks: [RelatedLink] + ) { + self.name = name + self.iconImage = iconImage + self.previewImages = previewImages + self.content = content + self.techStacks = techStacks + self.mainTask = mainTask + self.startAt = startAt + self.endAt = endAt + self.relatedLinks = relatedLinks + } } public extension InputProjectInfoObject { struct ImageFile { public let name: String public let data: Data + + public init(name: String, data: Data) { + self.name = name + self.data = data + } } struct RelatedLink { public let name: String public let url: String + + public init(name: String, url: String) { + self.name = name + self.url = url + } } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index f44a21a1..e11cab1f 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -14,6 +14,45 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { self.delegate = delegate } + func prevButtonDidTap() { + delegate?.projectInfoPrevButtonDidTap() + } + + func nextButtonDidTap(projects: [ProjectInfo]) { + let projectInfoObjects = projects.map { + let iconImage: InputProjectInfoObject.ImageFile? + if let unwrapIconImage = $0.iconImage { + iconImage = .init( + name: unwrapIconImage.fileName, + data: unwrapIconImage.uiImage.jpegData(compressionQuality: 0.2) ?? .init() + ) + } else { + iconImage = nil + } + let previewImages = $0.previewImages.map { + InputProjectInfoObject.ImageFile( + name: $0.fileName, + data: $0.uiImage.jpegData(compressionQuality: 0.2) ?? .init() + ) + } + let relatedLinks = $0.relatedLinks.map { + InputProjectInfoObject.RelatedLink(name: $0.name, url: $0.url) + } + return InputProjectInfoObject( + name: $0.name, + iconImage: iconImage, + previewImages: previewImages, + content: $0.content, + techStacks: $0.techStacks, + mainTask: $0.mainTask, + startAt: $0.startAt, + endAt: $0.endAt, + relatedLinks: relatedLinks + ) + } + delegate?.completeToInputProjectInfo(input: projectInfoObjects) + } + func projectToggleButtonDidTap(index: Int) { model?.toggleCollapsedProject(index: index) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index cdbae4d0..f179258a 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -2,6 +2,8 @@ import DesignSystem import Foundation protocol InputProjectInfoIntentProtocol { + func prevButtonDidTap() + func nextButtonDidTap(projects: [ProjectInfo]) func projectToggleButtonDidTap(index: Int) func updateProjectName(index: Int, name: String) func updateIconImage(index: Int, image: PickedImageResult) From 39511567f3b02e94d6a08ec1f5a0601074d8817b Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:43:46 +0900 Subject: [PATCH 17/28] =?UTF-8?q?:sparkles:=20::=20ProjectInfo=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84,=20=EB=8B=A4=EC=9D=8C=20View=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/InputProjectInfoView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index dc3d2ba6..c3985575 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -45,10 +45,12 @@ struct InputProjectInfoView: View { HStack(spacing: 8) { CTAButton(text: "이전", style: .outline) { + intent.prevButtonDidTap() } .frame(maxWidth: geometry.size.width / 3) CTAButton(text: "다음") { + intent.nextButtonDidTap(projects: state.projectList) } .frame(maxWidth: .infinity) } From 8d0daca08e442e4ae5baf77e43ba54631325327e Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:54:20 +0900 Subject: [PATCH 18/28] =?UTF-8?q?:sparkles:=20::=20=ED=85=8C=ED=81=AC?= =?UTF-8?q?=EC=8A=A4=ED=8E=99=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/InputProjectInfoIntent.swift | 6 +- .../InputProjectInfoIntentProtocol.swift | 1 + .../Sources/Model/InputProjectInfoModel.swift | 7 ++- .../Model/InputProjectInfoModelProtocol.swift | 3 +- .../Sources/Scene/InputProjectInfoView.swift | 57 ++++++++++++++----- 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index e11cab1f..f20109db 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -43,7 +43,7 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { iconImage: iconImage, previewImages: previewImages, content: $0.content, - techStacks: $0.techStacks, + techStacks: Array($0.techStacks), mainTask: $0.mainTask, startAt: $0.startAt, endAt: $0.endAt, @@ -86,6 +86,10 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.updateProjectTechStacks(index: index, techStacks: techStacks) } + func removeProjectTechStackButtonDidTap(index: Int, techStack: String) { + model?.removeProjectTechStack(index: index, techStacks: techStacks) + } + func updateProjectMainTask(index: Int, mainTask: String) { model?.updateProjectMainTask(index: index, mainTask: mainTask) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index f179258a..2f690554 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -12,6 +12,7 @@ protocol InputProjectInfoIntentProtocol { func removePreviewImageDidTap(index: Int, previewIndex: Int) func updateProjectContent(index: Int, content: String) func techStacksDidSelect(index: Int, techStacks: [String]) + func removeProjectTechStackButtonDidTap(index: Int, techStack: String) func updateProjectMainTask(index: Int, mainTask: String) func projectStartAtButtonDidTap(index: Int) func projectEndAtButtonDidTap(index: Int) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 9c38995d..636da8ef 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -46,7 +46,12 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { func updateProjectTechStacks(index: Int, techStacks: [String]) { guard projectList[safe: index] != nil else { return } - self.projectList[index].techStacks = techStacks + self.projectList[index].techStacks = Set(techStacks.prefix(20)) + } + + func removeProjectTechStacks(index: Int, techStack: String) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].techStacks.remove(techStack) } func updateProjectMainTask(index: Int, mainTask: String) { diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index 6d61d2cb..e8fc9714 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -7,7 +7,7 @@ struct ProjectInfo { var iconImage: PickedImageResult? var previewImages: [PickedImageResult] var content: String - var techStacks: [String] + var techStacks: Set var mainTask: String var startAt: Date var endAt: Date? @@ -53,6 +53,7 @@ protocol InputProjectInfoActionProtocol: AnyObject { func removePreviewImage(index: Int, previewIndex: Int) func updateProjectContent(index: Int, content: String) func updateProjectTechStacks(index: Int, techStacks: [String]) + func removeProjectTechStack(index: Int, techStack: String) func updateProjectMainTask(index: Int, mainTask: String) func updateProjectStartAt(index: Int, startAt: Date) func updateProjectEndAt(index: Int, endAt: Date) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index c3985575..df9ea498 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -3,6 +3,7 @@ import DesignSystem import FoundationUtil import InputInformationBaseFeature import SwiftUI +import TagLayoutView import ViewUtil struct InputProjectInfoView: View { @@ -123,7 +124,7 @@ struct InputProjectInfoView: View { projectContentTextEditor(index: index) - projectTechStack(index: index) + projectTechStack(geometry: geometry, index: index) projectDuration(index: index) @@ -253,21 +254,47 @@ private extension InputProjectInfoView { } @ViewBuilder - func projectTechStack(index: Int) -> some View { - HStack(spacing: 8) { - SMSIcon(.magnifyingglass) - - SMSText("찾고 싶은 세부 스택 입력", font: .body1) - .foregroundColor(.sms(.neutral(.n30))) - - Spacer() - } - .padding(12) - .background { - Color.sms(.neutral(.n10)) + func projectTechStack(geometry: GeometryProxy, index: Int) -> some View { + VStack(spacing: 8) { + HStack(spacing: 8) { + SMSIcon(.magnifyingglass) + + SMSText("찾고 싶은 세부 스택 입력", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) + + Spacer() + } + .padding(12) + .background { + Color.sms(.neutral(.n10)) + } + .clipShape(RoundedRectangle(cornerRadius: 8)) + .buttonWrapper {} + + TagLayoutView( + Array(state.projectList[safe: index]?.techStacks ?? []), + tagFont: UIFont( + font: DesignSystemFontFamily.Pretendard.regular, + size: 24 + ) ?? .init(), + padding: 20, + parentWidth: geometry.size.width + ) { techStack in + HStack { + SMSText(techStack, font: .body2) + + SMSIcon(.xmarkOutline, width: 20, height: 20) + .buttonWrapper { + intent.removeProjectTechStackButtonDidTap(index: index, techStack: techStack) + } + } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background(Color.sms(.neutral(.n10))) + .fixedSize() + .clipShape(RoundedRectangle(cornerRadius: 4)) + } } - .clipShape(RoundedRectangle(cornerRadius: 8)) - .buttonWrapper {} .titleWrapper("사용 기술") } From 3f3e526996fe7873db17300ad2f966bced9ffa40 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:55:13 +0900 Subject: [PATCH 19/28] :heavy_plus_sign: :: TechStackAppendFeature -> InputProjectInfoFeature --- Projects/Feature/InputProjectInfoFeature/Project.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Project.swift b/Projects/Feature/InputProjectInfoFeature/Project.swift index d28da929..e81434bb 100644 --- a/Projects/Feature/InputProjectInfoFeature/Project.swift +++ b/Projects/Feature/InputProjectInfoFeature/Project.swift @@ -7,6 +7,7 @@ let project = Project.makeModule( product: .staticLibrary, targets: [.interface, .unitTest, .demo], internalDependencies: [ - .Feature.InputInformationBaseFeature + .Feature.InputInformationBaseFeature, + .Feature.TechStackAppendFeatureInterface ] ) From fddeb4b4975006995c72f2f6cbf555e7d50bc6d2 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:55:30 +0900 Subject: [PATCH 20/28] =?UTF-8?q?:art:=20::=20=EC=96=B4=EC=83=89=ED=95=9C?= =?UTF-8?q?=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/InputProjectInfoView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index df9ea498..425b320c 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -8,7 +8,6 @@ import ViewUtil struct InputProjectInfoView: View { @FocusState var projectContentIsFocused: Bool - @StateObject var container: MVIContainer var intent: any InputProjectInfoIntentProtocol { container.intent } var state: any InputProjectInfoStateProtocol { container.model } From dc07254415175060c2a0ddb133a2f9a32634bda4 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 10:57:35 +0900 Subject: [PATCH 21/28] =?UTF-8?q?:pencil2:=20::=20techStack,=20techStacks?= =?UTF-8?q?=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/InputProjectInfoIntent.swift | 2 +- .../Sources/Model/InputProjectInfoModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index f20109db..d132344a 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -87,7 +87,7 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { } func removeProjectTechStackButtonDidTap(index: Int, techStack: String) { - model?.removeProjectTechStack(index: index, techStacks: techStacks) + model?.removeProjectTechStack(index: index, techStack: techStack) } func updateProjectMainTask(index: Int, mainTask: String) { diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 636da8ef..267f6382 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -49,7 +49,7 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { self.projectList[index].techStacks = Set(techStacks.prefix(20)) } - func removeProjectTechStacks(index: Int, techStack: String) { + func removeProjectTechStack(index: Int, techStack: String) { guard projectList[safe: index] != nil else { return } self.projectList[index].techStacks.remove(techStack) } From 078d1e229c2b0aa339f6e3aac422ed0aa3c4954a Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 11:36:33 +0900 Subject: [PATCH 22/28] =?UTF-8?q?:sparkles:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=A7=84=ED=96=89=EC=9D=BC=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=EC=A4=91=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/InputInformationIntent.swift | 1 + .../Sources/Scene/InputLanguageInfoView.swift | 2 +- .../Demo/Sources/AppDelegate.swift | 2 +- .../Intent/InputProjectInfoIntent.swift | 6 ++- .../InputProjectInfoIntentProtocol.swift | 1 + .../Sources/Model/InputProjectInfoModel.swift | 6 +++ .../Model/InputProjectInfoModelProtocol.swift | 8 +++- .../Sources/Scene/InputProjectInfoView.swift | 45 ++++++++++++++----- 8 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift index 269dd1cc..e5088594 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift @@ -148,6 +148,7 @@ extension InputInformationIntent: InputLanguageDelegate { let languageCertificates: [InputStudentInformationRequestDTO.LanguageCertificate] = languages .map { .init(languageCertificateName: $0.name, score: $0.score) } model?.updateLanguages(languages: languageCertificates) + model?.nextButtonDidTap() } } diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift index fb0ac0fb..7d9281ee 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Scene/InputLanguageInfoView.swift @@ -36,7 +36,7 @@ struct InputLanguageInfoView: View { } .frame(maxWidth: proxy.size.width / 3) - CTAButton(text: "입력 완료") { + CTAButton(text: "다음") { intent.completeButtonDidTap(languages: state.languageList) } .frame(maxWidth: .infinity) diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift index b1ce4dab..8b0f97e7 100644 --- a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift @@ -5,7 +5,7 @@ import InputProjectInfoFeatureInterface final class DummyInputProjectInfoDelegate: InputProjectInfoDelegate { func projectInfoPrevButtonDidTap() {} - func completeToInputProjectInfo(input: InputProjectInfoObject) {} + func completeToInputProjectInfo(input: [InputProjectInfoObject]) {} } @main diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index d132344a..e2f03ead 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -46,7 +46,7 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { techStacks: Array($0.techStacks), mainTask: $0.mainTask, startAt: $0.startAt, - endAt: $0.endAt, + endAt: $0.isInProgress ? nil : $0.endAt, relatedLinks: relatedLinks ) } @@ -104,6 +104,10 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.updateIsPresentedEndAtDatePicker(isPresented: true) } + func projectIsInProgressButtonDidTap(index: Int, isInProgress: Bool) { + model?.updateIsInProgress(index: index, isInProgress: isInProgress) + } + func projectStartAtDidSelect(index: Int, startAt: Date) { model?.updateProjectStartAt(index: index, startAt: startAt) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 2f690554..2b88a5e7 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -18,6 +18,7 @@ protocol InputProjectInfoIntentProtocol { func projectEndAtButtonDidTap(index: Int) func projectStartAtDidSelect(index: Int, startAt: Date) func projectEndAtDidSelect(index: Int, endAt: Date) + func projectIsInProgressButtonDidTap(index: Int, isInProgress: Bool) func updateProjectLinkName(index: Int, linkIndex: Int, name: String) func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) func relatedLinkAppendButtonDidTap(index: Int) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 267f6382..09241494 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -72,6 +72,11 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { } } + func updateIsInProgress(index: Int, isInProgress: Bool) { + guard projectList[safe: index] != nil else { return } + self.projectList[index].isInProgress = isInProgress + } + func updateProjectLinkName(index: Int, linkIndex: Int, name: String) { guard let project = projectList[safe: index], @@ -110,6 +115,7 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { techStacks: [], mainTask: "", startAt: Date(), + isInProgress: false, relatedLinks: [] ) self.projectList.append(newProject) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index e8fc9714..da8c7784 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -2,7 +2,11 @@ import DateUtil import DesignSystem import Foundation -struct ProjectInfo { +struct ProjectInfo: Equatable { + static func == (lhs: ProjectInfo, rhs: ProjectInfo) -> Bool { + lhs.name == rhs.name + } + var name: String var iconImage: PickedImageResult? var previewImages: [PickedImageResult] @@ -11,6 +15,7 @@ struct ProjectInfo { var mainTask: String var startAt: Date var endAt: Date? + var isInProgress: Bool var relatedLinks: [RelatedLink] var startAtString: String { @@ -57,6 +62,7 @@ protocol InputProjectInfoActionProtocol: AnyObject { func updateProjectMainTask(index: Int, mainTask: String) func updateProjectStartAt(index: Int, startAt: Date) func updateProjectEndAt(index: Int, endAt: Date) + func updateIsInProgress(index: Int, isInProgress: Bool) func updateProjectLinkName(index: Int, linkIndex: Int, name: String) func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) func appendEmptyRelatedLink(index: Int) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 425b320c..370f27cf 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -257,10 +257,10 @@ private extension InputProjectInfoView { VStack(spacing: 8) { HStack(spacing: 8) { SMSIcon(.magnifyingglass) - + SMSText("찾고 싶은 세부 스택 입력", font: .body1) .foregroundColor(.sms(.neutral(.n30))) - + Spacer() } .padding(12) @@ -299,19 +299,41 @@ private extension InputProjectInfoView { @ViewBuilder func projectDuration(index: Int) -> some View { - HStack(spacing: 8) { - let project = state.projectList[safe: index] - datePickerField(dateText: project?.startAtString ?? "") { - intent.startAtButtonDidTap(index: index) + VStack(spacing: 8) { + HStack(spacing: 8) { + let project = state.projectList[safe: index] + datePickerField(dateText: project?.startAtString ?? "") { + intent.startAtButtonDidTap(index: index) + } + .frame(maxWidth: .infinity) + + if !(project?.isInProgress ?? false) { + SMSIcon(.waterWave) + + datePickerField(dateText: project?.endAtString ?? "") { + intent.endAtButtonDidTap(index: index) + } + .frame(maxWidth: .infinity) + } } - .frame(maxWidth: .infinity) + .animation(.spring(blendDuration: 0.3), value: state.projectList.map(\.isInProgress)) - SMSIcon(.waterWave) + HStack(spacing: 8) { + SMSCheckbox( + isSelected: Binding( + get: { state.projectList[safe: index]?.isInProgress ?? false }, + set: { isInProgress in + withAnimation { + intent.projectIsInProgressButtonDidTap(index: index, isInProgress: isInProgress) + } + } + ) + ) - datePickerField(dateText: project?.endAtString ?? "") { - intent.endAtButtonDidTap(index: index) + SMSText("진행중", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) + .aligned(.leading) } - .frame(maxWidth: .infinity) } .titleWrapper("진행 기간") } @@ -341,6 +363,7 @@ private extension InputProjectInfoView { .frame(maxWidth: .infinity) Button { + intent.removeProjectRelatedLinkDidTap(index: index, linkIndex: relatedIndex) } label: { SMSIcon(.trash) } From a6e2360a7f93898cda4697f30d6bb9845d21df8e Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 11:39:08 +0900 Subject: [PATCH 23/28] =?UTF-8?q?:sparkles:=20::=20=EC=8A=A4=ED=83=9D=20?= =?UTF-8?q?=EC=B5=9C=EB=8C=80=20=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/InputProfileInfoIntent.swift | 2 +- .../Sources/Scene/InputProfileInfoView.swift | 2 +- .../Sources/Scene/InputProjectInfoView.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift b/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift index de9752ab..5b4ae559 100644 --- a/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift +++ b/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift @@ -44,7 +44,7 @@ final class InputProfileInfoIntent: InputProfileInfoIntentProtocol { } func techStackAppendDidComplete(techStacks: [String]) { - model?.updateTeckStacks(techStacks: techStacks) + model?.updateTeckStacks(techStacks: Array(techStacks.prefix(5))) } func removeTechStack(techStack: String) { diff --git a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift index 5a16b2fc..ba6e9e60 100644 --- a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift +++ b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/InputProfileInfoView.swift @@ -176,7 +176,7 @@ struct InputProfileInfoView: View { .clipShape(RoundedRectangle(cornerRadius: 4)) } } - .titleWrapper("세부스택") + .titleWrapper("세부스택 (최대 5개)") } CTAButton(text: "다음") { diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 370f27cf..391d6376 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -294,7 +294,7 @@ private extension InputProjectInfoView { .clipShape(RoundedRectangle(cornerRadius: 4)) } } - .titleWrapper("사용 기술") + .titleWrapper("사용 기술 (최대 20개)") } @ViewBuilder From 02e1aa20d85fce2cead9e1b79c2c3a07db515cb7 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 11:51:27 +0900 Subject: [PATCH 24/28] =?UTF-8?q?:recycle:=20::=20View=20Component=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=B9=88=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=EB=AC=BC=20=EA=B1=B0=EB=A5=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/InputProjectInfoIntent.swift | 7 ++-- .../Sources/Scene/InputProjectInfoView.swift | 27 +------------- .../Sources/Scene/View/DatePickerField.swift | 37 +++++++++++++++++++ 3 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index e2f03ead..a06d0251 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -1,5 +1,6 @@ import DesignSystem import Foundation +import FoundationUtil import InputProjectInfoFeatureInterface final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { @@ -35,9 +36,9 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { data: $0.uiImage.jpegData(compressionQuality: 0.2) ?? .init() ) } - let relatedLinks = $0.relatedLinks.map { - InputProjectInfoObject.RelatedLink(name: $0.name, url: $0.url) - } + let relatedLinks = $0.relatedLinks + .map { InputProjectInfoObject.RelatedLink(name: $0.name, url: $0.url) } + .filter { $0.name.isNotEmpty && $0.url.isNotEmpty } return InputProjectInfoObject( name: $0.name, iconImage: iconImage, diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 391d6376..539d8044 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -302,7 +302,7 @@ private extension InputProjectInfoView { VStack(spacing: 8) { HStack(spacing: 8) { let project = state.projectList[safe: index] - datePickerField(dateText: project?.startAtString ?? "") { + DatePickerField(dateText: project?.startAtString ?? "") { intent.startAtButtonDidTap(index: index) } .frame(maxWidth: .infinity) @@ -310,7 +310,7 @@ private extension InputProjectInfoView { if !(project?.isInProgress ?? false) { SMSIcon(.waterWave) - datePickerField(dateText: project?.endAtString ?? "") { + DatePickerField(dateText: project?.endAtString ?? "") { intent.endAtButtonDidTap(index: index) } .frame(maxWidth: .infinity) @@ -387,27 +387,4 @@ private extension InputProjectInfoView { .fill(Color.sms(.neutral(.n10))) .frame(width: size, height: size) } - - @ViewBuilder - func datePickerField(dateText: String, action: @escaping () -> Void) -> some View { - SMSTextField( - "yyyy.mm", - text: Binding( - get: { dateText }, - set: { _ in } - ), - isOnClear: false - ) - .disabled(true) - .overlay(alignment: .trailing) { - SMSIcon(.calendar) - .padding(.trailing, 12) - } - .simultaneousGesture( - TapGesture() - .onEnded { - action() - } - ) - } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift new file mode 100644 index 00000000..bf44884c --- /dev/null +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift @@ -0,0 +1,37 @@ +import DesignSystem +import SwiftUI + +struct DatePickerField: View { + let dateText: String + let action: () -> Void + + init( + dateText: String, + action: @escaping () -> Void + ) { + self.dateText = dateText + self.action = action + } + + var body: some View { + SMSTextField( + "yyyy.mm", + text: Binding( + get: { dateText }, + set: { _ in } + ), + isOnClear: false + ) + .disabled(true) + .overlay(alignment: .trailing) { + SMSIcon(.calendar) + .padding(.trailing, 12) + } + .simultaneousGesture( + TapGesture() + .onEnded { + action() + } + ) + } +} From 616bb4bf78880d8d1c4aa9f769305d7028060f87 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 11:54:10 +0900 Subject: [PATCH 25/28] :bug: :: DateUtil -> DesignSystem --- Projects/Core/DesignSystem/Project.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Projects/Core/DesignSystem/Project.swift b/Projects/Core/DesignSystem/Project.swift index e84d6827..77e744ef 100644 --- a/Projects/Core/DesignSystem/Project.swift +++ b/Projects/Core/DesignSystem/Project.swift @@ -11,7 +11,8 @@ let project = Project.makeModule( .SPM.Lottie ], internalDependencies: [ - .Shared.ViewUtil + .Shared.ViewUtil, + .Shared.DateUtil ], resources: ["Resources/**"], resourceSynthesizers: .default + [ From fc072920445034419dededc9dcd8c67475b08e3a Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 13:13:13 +0900 Subject: [PATCH 26/28] =?UTF-8?q?:sparkles:=20::=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EA=B8=B0=EC=88=A0=20=EC=B6=94=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DI/InputProjectInfoComponent.swift | 10 ++++++-- .../Intent/InputProjectInfoIntent.swift | 9 ++++++++ .../InputProjectInfoIntentProtocol.swift | 2 ++ .../Sources/Model/InputProjectInfoModel.swift | 5 ++++ .../Model/InputProjectInfoModelProtocol.swift | 2 ++ .../Sources/Scene/InputProjectInfoView.swift | 23 ++++++++++++++++++- 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift index 40031cf1..de7ed1fb 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/DI/InputProjectInfoComponent.swift @@ -2,8 +2,11 @@ import BaseFeature import InputProjectInfoFeatureInterface import NeedleFoundation import SwiftUI +import TechStackAppendFeatureInterface -public protocol InputProjectInfoDependency: Dependency {} +public protocol InputProjectInfoDependency: Dependency { + var techStackAppendBuildable: any TechStackAppendBuildable { get } +} public final class InputProjectInfoComponent: Component, @@ -19,6 +22,9 @@ public final class InputProjectInfoComponent: model: model as InputProjectInfoStateProtocol, modelChangePublisher: model.objectWillChange ) - return InputProjectInfoView(container: container) + return InputProjectInfoView( + techStackAppendBuildable: dependency.techStackAppendBuildable, + container: container + ) } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index a06d0251..65a85946 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -171,4 +171,13 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { func endAtDatePickerDismissed() { model?.updateIsPresentedEndAtDatePicker(isPresented: false) } + + func techStackAppendButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedTechStackAppend(isPresented: true) + } + + func techStackAppendDismissed() { + model?.updateIsPresentedTechStackAppend(isPresented: false) + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 2b88a5e7..a3131b88 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -32,4 +32,6 @@ protocol InputProjectInfoIntentProtocol { func endAtButtonDidTap(index: Int) func startAtDatePickerDismissed() func endAtDatePickerDismissed() + func techStackAppendButtonDidTap(index: Int) + func techStackAppendDismissed() } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index 09241494..bf715843 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -10,6 +10,7 @@ final class InputProjectInfoModel: ObservableObject, InputProjectInfoStateProtoc @Published var isPresentedPreviewImagePicker: Bool = false @Published var isPresentedStartAtDatePicker: Bool = false @Published var isPresentedEndAtDatePicker: Bool = false + @Published var isPresentedTechStackAppend: Bool = false var focusedProjectIndex: Int = 0 } @@ -147,4 +148,8 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { func updateIsPresentedEndAtDatePicker(isPresented: Bool) { self.isPresentedEndAtDatePicker = isPresented } + + func updateIsPresentedTechStackAppend(isPresented: Bool) { + self.isPresentedTechStackAppend = isPresented + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index da8c7784..d87208f5 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -48,6 +48,7 @@ protocol InputProjectInfoStateProtocol { var isPresentedPreviewImagePicker: Bool { get } var isPresentedStartAtDatePicker: Bool { get } var isPresentedEndAtDatePicker: Bool { get } + var isPresentedTechStackAppend: Bool { get } } protocol InputProjectInfoActionProtocol: AnyObject { @@ -74,4 +75,5 @@ protocol InputProjectInfoActionProtocol: AnyObject { func updateIsPresentedPreviewImagePicker(isPresented: Bool) func updateIsPresentedStartAtDatePicker(isPresented: Bool) func updateIsPresentedEndAtDatePicker(isPresented: Bool) + func updateIsPresentedTechStackAppend(isPresented: Bool) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 539d8044..cdec47e5 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -4,6 +4,7 @@ import FoundationUtil import InputInformationBaseFeature import SwiftUI import TagLayoutView +import TechStackAppendFeatureInterface import ViewUtil struct InputProjectInfoView: View { @@ -11,10 +12,13 @@ struct InputProjectInfoView: View { @StateObject var container: MVIContainer var intent: any InputProjectInfoIntentProtocol { container.intent } var state: any InputProjectInfoStateProtocol { container.model } + private let techStackAppendBuildable: any TechStackAppendBuildable init( + techStackAppendBuildable: any TechStackAppendBuildable, container: MVIContainer ) { + self.techStackAppendBuildable = techStackAppendBuildable self._container = StateObject(wrappedValue: container) } @@ -89,6 +93,21 @@ struct InputProjectInfoView: View { ) { date in intent.projectEndAtDidSelect(index: state.focusedProjectIndex, endAt: date) } + .fullScreenCover( + isPresented: Binding( + get: { state.isPresentedTechStackAppend }, + set: { _ in intent.techStackAppendDismissed() } + ) + ) { + DeferView { + techStackAppendBuildable.makeView( + initial: Array(state.projectList[safe: state.focusedProjectIndex]?.techStacks ?? []) + ) { + intent.techStacksDidSelect(index: state.focusedProjectIndex, techStacks: $0) + } + .eraseToAnyView() + } + } } @ViewBuilder @@ -268,7 +287,9 @@ private extension InputProjectInfoView { Color.sms(.neutral(.n10)) } .clipShape(RoundedRectangle(cornerRadius: 8)) - .buttonWrapper {} + .buttonWrapper { + intent.techStackAppendButtonDidTap(index: index) + } TagLayoutView( Array(state.projectList[safe: index]?.techStacks ?? []), From 1b29dde545c55f4353d412521c2e91d397d490ba Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 14:44:15 +0900 Subject: [PATCH 27/28] =?UTF-8?q?:bug:=20::=20Demo=EC=95=B1=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Demo/Sources/AppDelegate.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift index 8b0f97e7..3383be9b 100644 --- a/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/InputProjectInfoFeature/Demo/Sources/AppDelegate.swift @@ -1,6 +1,7 @@ import BaseFeature import SwiftUI import InputProjectInfoFeatureInterface +import TechStackAppendFeatureInterface @testable import InputProjectInfoFeature final class DummyInputProjectInfoDelegate: InputProjectInfoDelegate { @@ -8,6 +9,12 @@ final class DummyInputProjectInfoDelegate: InputProjectInfoDelegate { func completeToInputProjectInfo(input: [InputProjectInfoObject]) {} } +struct DummyTechStackAppendBuildable: TechStackAppendBuildable { + func makeView(initial techStacks: [String], completion: @escaping ([String]) -> Void) -> some View { + EmptyView() + } +} + @main struct InputProjectInfoApp: App { var body: some Scene { @@ -22,7 +29,10 @@ struct InputProjectInfoApp: App { model: model as InputProjectInfoStateProtocol, modelChangePublisher: model.objectWillChange ) - InputProjectInfoView(container: container) + InputProjectInfoView( + techStackAppendBuildable: DummyTechStackAppendBuildable(), + container: container + ) } } } From c125c0ef9d1664a358cac14976ff74dc291f64fd Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 12 Jul 2023 22:06:11 +0900 Subject: [PATCH 28/28] :sparkles: :: Needle Generate DI --- .../Sources/Application/NeedleGenerated.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 34cbaea3..61d7929b 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -91,15 +91,17 @@ private func factoryace9f05f51d68f4c0677f47b58f8f304c97af4d5(_ component: Needle return SplashDependencye0cb7136f2ec3edfd60aProvider(appComponent: parent1(component) as! AppComponent) } private class InputProjectInfoDependencye065c7f60c5c520999a0Provider: InputProjectInfoDependency { - - - init() { - + var techStackAppendBuildable: any TechStackAppendBuildable { + return appComponent.techStackAppendBuildable + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent } } /// ^->AppComponent->InputProjectInfoComponent -private func factory2378736e5949c5e8e9f4e3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { - return InputProjectInfoDependencye065c7f60c5c520999a0Provider() +private func factory2378736e5949c5e8e9f4f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return InputProjectInfoDependencye065c7f60c5c520999a0Provider(appComponent: parent1(component) as! AppComponent) } private class InputWorkInfoDependency74441f61366e4e5af9a2Provider: InputWorkInfoDependency { @@ -423,7 +425,7 @@ extension SplashComponent: Registration { } extension InputProjectInfoComponent: Registration { public func registerItems() { - + keyPathToName[\InputProjectInfoDependency.techStackAppendBuildable] = "techStackAppendBuildable-any TechStackAppendBuildable" } } extension InputWorkInfoComponent: Registration { @@ -561,7 +563,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->SplashComponent", factoryace9f05f51d68f4c0677f47b58f8f304c97af4d5) - registerProviderFactory("^->AppComponent->InputProjectInfoComponent", factory2378736e5949c5e8e9f4e3b0c44298fc1c149afb) + registerProviderFactory("^->AppComponent->InputProjectInfoComponent", factory2378736e5949c5e8e9f4f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->InputWorkInfoComponent", factoryfff86bd7854b30412216e3b0c44298fc1c149afb) registerProviderFactory("^->AppComponent->MainComponent", factoryc9274e46e78e70f29c54f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->InputSchoolLifeInfoComponent", factorydc1feebed8f042db375fe3b0c44298fc1c149afb)