From 2ad989c90c3269601a4e79fb49f711bab31ff60e Mon Sep 17 00:00:00 2001 From: baegteun Date: Sat, 15 Jul 2023 21:30:51 +0900 Subject: [PATCH 01/62] :sparkles: :: MyPageFeature Module --- .../Dependency+Target.swift | 8 ++++++ .../ModulePaths.swift | 1 + .../Demo/Resources/LaunchScreen.storyboard | 25 +++++++++++++++++++ .../Demo/Sources/AppDelegate.swift | 19 ++++++++++++++ .../MyPageFeature/Interface/Interface.swift | 1 + Projects/Feature/MyPageFeature/Project.swift | 10 ++++++++ .../MyPageFeature/Sources/Source.swift | 1 + .../Tests/MyPageFeatureTest.swift | 11 ++++++++ 8 files changed, 76 insertions(+) create mode 100644 Projects/Feature/MyPageFeature/Demo/Resources/LaunchScreen.storyboard create mode 100644 Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift create mode 100644 Projects/Feature/MyPageFeature/Interface/Interface.swift create mode 100644 Projects/Feature/MyPageFeature/Project.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Source.swift create mode 100644 Projects/Feature/MyPageFeature/Tests/MyPageFeatureTest.swift diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift index 681970e8..6a3dc9f5 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 MyPageFeatureInterface = TargetDependency.project( + target: ModulePaths.Feature.MyPageFeature.targetName(type: .interface), + path: .relativeToFeature(ModulePaths.Feature.MyPageFeature.rawValue) + ) + static let MyPageFeature = TargetDependency.project( + target: ModulePaths.Feature.MyPageFeature.targetName(type: .sources), + path: .relativeToFeature(ModulePaths.Feature.MyPageFeature.rawValue) + ) static let InputProjectInfoFeatureInterface = TargetDependency.project( target: ModulePaths.Feature.InputProjectInfoFeature.targetName(type: .interface), path: .relativeToFeature(ModulePaths.Feature.InputProjectInfoFeature.rawValue) diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift index c05fc80d..63fc3166 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 MyPageFeature case InputProjectInfoFeature case SplashFeature case MainFeature diff --git a/Projects/Feature/MyPageFeature/Demo/Resources/LaunchScreen.storyboard b/Projects/Feature/MyPageFeature/Demo/Resources/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Demo/Resources/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift new file mode 100644 index 00000000..ef2bae04 --- /dev/null +++ b/Projects/Feature/MyPageFeature/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/MyPageFeature/Interface/Interface.swift b/Projects/Feature/MyPageFeature/Interface/Interface.swift new file mode 100644 index 00000000..b1853ce6 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Interface/Interface.swift @@ -0,0 +1 @@ +// This is for Tuist diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift new file mode 100644 index 00000000..5d487339 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -0,0 +1,10 @@ +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: ModulePaths.Feature.MyPageFeature.rawValue, + product: .staticLibrary, + targets: [.interface, .unitTest, .demo], + internalDependencies: [] +) diff --git a/Projects/Feature/MyPageFeature/Sources/Source.swift b/Projects/Feature/MyPageFeature/Sources/Source.swift new file mode 100644 index 00000000..b1853ce6 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Source.swift @@ -0,0 +1 @@ +// This is for Tuist diff --git a/Projects/Feature/MyPageFeature/Tests/MyPageFeatureTest.swift b/Projects/Feature/MyPageFeature/Tests/MyPageFeatureTest.swift new file mode 100644 index 00000000..13663bea --- /dev/null +++ b/Projects/Feature/MyPageFeature/Tests/MyPageFeatureTest.swift @@ -0,0 +1,11 @@ +import XCTest + +final class MyPageFeatureTests: XCTestCase { + override func setUpWithError() throws {} + + override func tearDownWithError() throws {} + + func testExample() { + XCTAssertEqual(1, 1) + } +} From 2678cb0b829b3a3259822b2bee58c08876d042ff Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 17 Jul 2023 21:21:47 +0900 Subject: [PATCH 02/62] =?UTF-8?q?:sparkles:=20::=20MyPage=20MVI=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/App/Project.swift | 1 + Projects/Feature/MainFeature/Project.swift | 1 + .../MyPageFeature/Interface/Interface.swift | 1 - .../Interface/MyPageBuildable.swift | 6 ++++++ .../Sources/DI/MyPageComponent.swift | 19 +++++++++++++++++++ .../Sources/Intent/MyPageIntent.swift | 9 +++++++++ .../Sources/Intent/MyPageIntentProtocol.swift | 3 +++ .../Sources/Model/MyPageModel.swift | 5 +++++ .../Sources/Model/MyPageModelProtocol.swift | 4 ++++ .../Sources/Scene/MyPageView.swift | 12 ++++++++++++ .../MyPageFeature/Sources/Source.swift | 1 - 11 files changed, 60 insertions(+), 2 deletions(-) delete mode 100644 Projects/Feature/MyPageFeature/Interface/Interface.swift create mode 100644 Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift delete mode 100644 Projects/Feature/MyPageFeature/Sources/Source.swift diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index 8c7c3850..822247a9 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -52,6 +52,7 @@ let targets: [Target] = [ .Feature.TechStackAppendFeature, .Feature.StudentDetailFeature, .Feature.FilterFeature, + .Feature.MyPageFeature, .Domain.AuthDomain, .Domain.StudentDomain, .Domain.FileDomain, diff --git a/Projects/Feature/MainFeature/Project.swift b/Projects/Feature/MainFeature/Project.swift index c6585593..12ae87df 100644 --- a/Projects/Feature/MainFeature/Project.swift +++ b/Projects/Feature/MainFeature/Project.swift @@ -10,6 +10,7 @@ let project = Project.makeModule( .Feature.BaseFeature, .Feature.StudentDetailFeatureInterface, .Feature.FilterFeatureInterface, + .Feature.MyPageFeatureInterface, .Domain.StudentDomainInterface, .Domain.AuthDomainInterface, .Domain.UserDomainInterface diff --git a/Projects/Feature/MyPageFeature/Interface/Interface.swift b/Projects/Feature/MyPageFeature/Interface/Interface.swift deleted file mode 100644 index b1853ce6..00000000 --- a/Projects/Feature/MyPageFeature/Interface/Interface.swift +++ /dev/null @@ -1 +0,0 @@ -// This is for Tuist diff --git a/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift b/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift new file mode 100644 index 00000000..affa584a --- /dev/null +++ b/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol MyPageBuildable { + associatedtype ViewType: View + func makeView() -> ViewType +} diff --git a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift new file mode 100644 index 00000000..f75170d6 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift @@ -0,0 +1,19 @@ +import BaseFeature +import MyPageFeatureInterface +import NeedleFoundation +import SwiftUI + +public protocol MyPageDependency: Dependency {} + +public final class MyPageComponent: Component, MyPageBuildable { + public func makeView() -> some View { + let model = MyPageModel() + let intent = MyPageIntent(model: model) + let container = MVIContainer( + intent: intent as MyPageIntentProtocol, + model: model as MyPageStateProtocol, + modelChangePublisher: model.objectWillChange + ) + return MyPageView(container: container) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift new file mode 100644 index 00000000..876e26a1 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -0,0 +1,9 @@ +import Foundation + +final class MyPageIntent:MyPageIntentProtocol { + private weak var model: (any MyPageActionProtocol)? + + init(model: any MyPageActionProtocol) { + self.model = model + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift new file mode 100644 index 00000000..853af598 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -0,0 +1,3 @@ +import Foundation + +protocol MyPageIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift new file mode 100644 index 00000000..ec6ac31b --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -0,0 +1,5 @@ +import Foundation + +final class MyPageModel: ObservableObject, MyPageStateProtocol {} + +extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift new file mode 100644 index 00000000..119cfee5 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -0,0 +1,4 @@ + +protocol MyPageStateProtocol {} + +protocol MyPageActionProtocol: AnyObject {} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift new file mode 100644 index 00000000..162f9e69 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -0,0 +1,12 @@ +import BaseFeature +import SwiftUI + +struct MyPageView: View { + @StateObject var container: MVIContainer + var intent: any MyPageIntentProtocol { container.intent } + var state: any MyPageStateProtocol { container.model } + + var body: some View { + EmptyView() + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Source.swift b/Projects/Feature/MyPageFeature/Sources/Source.swift deleted file mode 100644 index b1853ce6..00000000 --- a/Projects/Feature/MyPageFeature/Sources/Source.swift +++ /dev/null @@ -1 +0,0 @@ -// This is for Tuist From c1041922c0a001c0bb7091cea2c6afccf8fc2b7d Mon Sep 17 00:00:00 2001 From: baegteun Date: Thu, 20 Jul 2023 21:57:58 +0900 Subject: [PATCH 03/62] :heavy_plus_sign: :: [#220] UserDomainInterface -> MyPageFeature --- Projects/Feature/MyPageFeature/Project.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index 5d487339..bd10af29 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -6,5 +6,8 @@ let project = Project.makeModule( name: ModulePaths.Feature.MyPageFeature.rawValue, product: .staticLibrary, targets: [.interface, .unitTest, .demo], - internalDependencies: [] + internalDependencies: [ + .Feature.BaseFeature, + .Domain.UserDomainInterface + ] ) From 1caee72666a5b7bfda35cfb77e7815351c546184 Mon Sep 17 00:00:00 2001 From: baegteun Date: Thu, 20 Jul 2023 21:59:31 +0900 Subject: [PATCH 04/62] :sparkles: :: [#220] FetchMyProfileUseCase DI -> MyPageComponent --- .../MyPageFeature/Sources/DI/MyPageComponent.swift | 10 ++++++++-- .../MyPageFeature/Sources/Intent/MyPageIntent.swift | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift index f75170d6..a69d6df3 100644 --- a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift +++ b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift @@ -2,13 +2,19 @@ import BaseFeature import MyPageFeatureInterface import NeedleFoundation import SwiftUI +import UserDomainInterface -public protocol MyPageDependency: Dependency {} +public protocol MyPageDependency: Dependency { + var fetchMyProfileUseCase: any FetchMyProfileUseCase { get } +} public final class MyPageComponent: Component, MyPageBuildable { public func makeView() -> some View { let model = MyPageModel() - let intent = MyPageIntent(model: model) + let intent = MyPageIntent( + model: model, + fetchMyProfileUseCase: dependency.fetchMyProfileUseCase + ) let container = MVIContainer( intent: intent as MyPageIntentProtocol, model: model as MyPageStateProtocol, diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 876e26a1..91f21ab0 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -1,9 +1,15 @@ import Foundation +import UserDomainInterface final class MyPageIntent:MyPageIntentProtocol { private weak var model: (any MyPageActionProtocol)? + private let fetchMyProfileUseCase: any FetchMyProfileUseCase - init(model: any MyPageActionProtocol) { + init( + model: any MyPageActionProtocol, + fetchMyProfileUseCase: any FetchMyProfileUseCase + ) { self.model = model + self.fetchMyProfileUseCase = fetchMyProfileUseCase } } From abf55f2665aad3ea3356fcdbf526976858de278b Mon Sep 17 00:00:00 2001 From: baegteun Date: Thu, 20 Jul 2023 22:01:07 +0900 Subject: [PATCH 05/62] =?UTF-8?q?:rotating=5Flight:=20::=20[#220]=20MyPage?= =?UTF-8?q?IntentProtocol=20=EC=B1=84=ED=83=9D=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 91f21ab0..b63152bf 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -1,7 +1,7 @@ import Foundation import UserDomainInterface -final class MyPageIntent:MyPageIntentProtocol { +final class MyPageIntent: MyPageIntentProtocol { private weak var model: (any MyPageActionProtocol)? private let fetchMyProfileUseCase: any FetchMyProfileUseCase From db6109c3e51cd71a0051f7e8dcd6de05262d68a3 Mon Sep 17 00:00:00 2001 From: baegteun Date: Tue, 1 Aug 2023 18:03:50 +0900 Subject: [PATCH 06/62] =?UTF-8?q?:sparkles:=20::=20[#220]=20MyPageFeature?= =?UTF-8?q?=20/=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=84=B9=EC=85=98=20MV=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/MyPageIntent.swift | 2 +- .../Sources/Intent/MyPageIntentProtocol.swift | 2 +- .../Intent/MyPageProfileIntentProtocol.swift | 102 ++++++++++++++++++ .../Sources/Model/MyPageModel.swift | 17 ++- .../Sources/Model/MyPageModelProtocol.swift | 5 +- .../Model/MyPageProfileModelProtocol.swift | 87 +++++++++++++++ .../Sources/Scene/MyPageView.swift | 4 +- 7 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntentProtocol.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModelProtocol.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index b63152bf..2bac7ebe 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -2,7 +2,7 @@ import Foundation import UserDomainInterface final class MyPageIntent: MyPageIntentProtocol { - private weak var model: (any MyPageActionProtocol)? + weak var model: (any MyPageActionProtocol)? private let fetchMyProfileUseCase: any FetchMyProfileUseCase init( diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 853af598..aa38190a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -1,3 +1,3 @@ import Foundation -protocol MyPageIntentProtocol {} +protocol MyPageIntentProtocol: MyPageProfileIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntentProtocol.swift new file mode 100644 index 00000000..77ff8238 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntentProtocol.swift @@ -0,0 +1,102 @@ +import DesignSystem + +protocol MyPageProfileIntentProtocol { + func updateIntroduce(introduce: String) + func updateEmail(email: String) + func updateMajor(major: String) + func updatePortfolioURL(portfolioURL: String) + func techStackAppendIsRequired() + func techStackAppendDismissed() + func techStackAppendDidComplete(techStacks: [String]) + func removeTechStack(techStack: String) + func majorSheetIsRequired() + func majorSheetDismissed() + func imagePickerIsRequired() + func imagePickerDismissed() + func imageMethodPickerIsRequired() + func imageMethodPickerDismissed() + func imageDidSelected(imageResult: PickedImageResult?) + func cameraIsRequired() + func cameraDismissed() + func activeSelfEntering() + func deActiveSelfEntering() +} + +extension MyPageIntent: MyPageProfileIntentProtocol { + func updateIntroduce(introduce: String) { + model?.updateIntroduce(introduce: introduce) + } + + func updateEmail(email: String) { + model?.updateEmail(email: email) + } + + func updateMajor(major: String) { + model?.updateMajor(major: major) + } + + func updatePortfolioURL(portfolioURL: String) { + model?.updatePortfolioURL(portfolioURL: portfolioURL) + } + + func techStackAppendIsRequired() { + model?.updateIsPresentedTechStackAppend(isPresented: true) + } + + func techStackAppendDismissed() { + model?.updateIsPresentedTechStackAppend(isPresented: false) + } + + func techStackAppendDidComplete(techStacks: [String]) { + model?.updateTechStacks(techStacks: techStacks) + } + + func removeTechStack(techStack: String) { + model?.removeTechStack(techStack: techStack) + } + + func majorSheetIsRequired() { + model?.updateIsPresentedMajorSheet(isPresented: true) + } + + func majorSheetDismissed() { + model?.updateIsPresentedMajorSheet(isPresented: false) + } + + func imagePickerIsRequired() { + model?.updateIsPresentedProfileImage(isPresented: true) + } + + func imagePickerDismissed() { + model?.updateIsPresentedProfileImage(isPresented: false) + } + + func imageMethodPickerIsRequired() { + model?.updateIsPresentedImageMethodPicker(isPresented: true) + } + + func imageMethodPickerDismissed() { + model?.updateIsPresentedImageMethodPicker(isPresented: false) + } + + func imageDidSelected(imageResult: PickedImageResult?) { + guard let imageResult else { return } + #warning("이미지 업로드 후 model에서 profile URL 변경") + } + + func cameraIsRequired() { + model?.updateIsPresentedProfileCamera(isPresented: true) + } + + func cameraDismissed() { + model?.updateIsPresentedProfileCamera(isPresented: false) + } + + func activeSelfEntering() { + model?.updateIsSelfEntering(isSelfEntering: true) + } + + func deActiveSelfEntering() { + model?.updateIsSelfEntering(isSelfEntering: false) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index ec6ac31b..c2b155ae 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -1,5 +1,20 @@ import Foundation -final class MyPageModel: ObservableObject, MyPageStateProtocol {} +final class MyPageModel: ObservableObject, MyPageStateProtocol { + // MARK: Profile + @Published var profileURL: String = "" + @Published var introduce: String = "" + @Published var email: String = "" + @Published var major: String = "" + @Published var majorList: [String] = [] + @Published var portfolioURL: String = "" + @Published var techStacks: [String] = [] + @Published var isSelfEntering: Bool = false + @Published var isPresentedMajorSheet: Bool = false + @Published var isPresentedImageMethodPicker: Bool = false + @Published var isPresentedProfileCamera: Bool = false + @Published var isPresentedProfileImage: Bool = false + @Published var isPresentedTechStackAppend: Bool = false +} extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index 119cfee5..0a5512fb 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -1,4 +1,3 @@ +protocol MyPageStateProtocol: MyPageProfileStateProtocol {} -protocol MyPageStateProtocol {} - -protocol MyPageActionProtocol: AnyObject {} +protocol MyPageActionProtocol: AnyObject, MyPageProfileActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModelProtocol.swift new file mode 100644 index 00000000..a7f71c5b --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModelProtocol.swift @@ -0,0 +1,87 @@ +import Foundation + +protocol MyPageProfileStateProtocol { + var profileURL: String { get } + var introduce: String { get } + var email: String { get } + var major: String { get } + var majorList: [String] { get } + var portfolioURL: String { get } + var techStacks: [String] { get } + var isSelfEntering: Bool { get } + var isPresentedMajorSheet: Bool { get } + var isPresentedImageMethodPicker: Bool { get } + var isPresentedProfileCamera: Bool { get } + var isPresentedProfileImage: Bool { get } + var isPresentedTechStackAppend: Bool { get } +} + +protocol MyPageProfileActionProtocol: AnyObject { + func updateProfileURL(url: String) + func updateIntroduce(introduce: String) + func updateEmail(email: String) + func updateMajor(major: String) + func updatePortfolioURL(portfolioURL: String) + func updateTechStacks(techStacks: [String]) + func removeTechStack(techStack: String) + func updateIsSelfEntering(isSelfEntering: Bool) + func updateIsPresentedMajorSheet(isPresented: Bool) + func updateIsPresentedImageMethodPicker(isPresented: Bool) + func updateIsPresentedProfileCamera(isPresented: Bool) + func updateIsPresentedProfileImage(isPresented: Bool) + func updateIsPresentedTechStackAppend(isPresented: Bool) +} + +extension MyPageModel: MyPageProfileActionProtocol { + func updateProfileURL(url: String) { + self.profileURL = url + } + + func updateIntroduce(introduce: String) { + self.introduce = introduce + } + + func updateEmail(email: String) { + self.email = email + } + + func updateMajor(major: String) { + self.major = major + } + + func updatePortfolioURL(portfolioURL: String) { + self.portfolioURL = portfolioURL + } + + func updateTechStacks(techStacks: [String]) { + self.techStacks = techStacks + } + + func removeTechStack(techStack: String) { + self.techStacks.removeAll { $0 == techStack } + } + + func updateIsSelfEntering(isSelfEntering: Bool) { + self.isSelfEntering = isSelfEntering + } + + func updateIsPresentedMajorSheet(isPresented: Bool) { + self.isPresentedMajorSheet = isPresented + } + + func updateIsPresentedImageMethodPicker(isPresented: Bool) { + self.isPresentedImageMethodPicker = isPresented + } + + func updateIsPresentedProfileCamera(isPresented: Bool) { + self.isPresentedProfileCamera = isPresented + } + + func updateIsPresentedProfileImage(isPresented: Bool) { + self.isPresentedProfileImage = isPresented + } + + func updateIsPresentedTechStackAppend(isPresented: Bool) { + self.isPresentedTechStackAppend = isPresented + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 162f9e69..a8a70329 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -7,6 +7,8 @@ struct MyPageView: View { var state: any MyPageStateProtocol { container.model } var body: some View { - EmptyView() + ScrollView { + + } } } From fb6bda1c2cf3b85ef0cb8487cf7e235d89f09844 Mon Sep 17 00:00:00 2001 From: baegteun Date: Tue, 1 Aug 2023 18:12:47 +0900 Subject: [PATCH 07/62] :sparkles: :: [#220] MyPageFeature / MyPageSchoolLifeIntent --- .../Sources/Intent/MyPageIntentProtocol.swift | 2 +- ...tentProtocol.swift => MyPageProfileIntent.swift} | 0 .../Sources/Intent/MyPageSchoolLifeIntent.swift | 9 +++++++++ .../MyPageFeature/Sources/Model/MyPageModel.swift | 3 +++ .../Sources/Model/MyPageModelProtocol.swift | 4 ++-- ...ModelProtocol.swift => MyPageProfileModel.swift} | 0 .../Sources/Model/MyPageSchoolLifeModel.swift | 13 +++++++++++++ 7 files changed, 28 insertions(+), 3 deletions(-) rename Projects/Feature/MyPageFeature/Sources/Intent/{MyPageProfileIntentProtocol.swift => MyPageProfileIntent.swift} (100%) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageSchoolLifeIntent.swift rename Projects/Feature/MyPageFeature/Sources/Model/{MyPageProfileModelProtocol.swift => MyPageProfileModel.swift} (100%) create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageSchoolLifeModel.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index aa38190a..80726e44 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -1,3 +1,3 @@ import Foundation -protocol MyPageIntentProtocol: MyPageProfileIntentProtocol {} +protocol MyPageIntentProtocol: MyPageProfileIntentProtocol, MyPageSchoolLifeIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift similarity index 100% rename from Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntentProtocol.swift rename to Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageSchoolLifeIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageSchoolLifeIntent.swift new file mode 100644 index 00000000..44d97533 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageSchoolLifeIntent.swift @@ -0,0 +1,9 @@ +protocol MyPageSchoolLifeIntentProtocol { + func updateGSMScore(gsmScore: String) +} + +extension MyPageIntent: MyPageSchoolLifeIntentProtocol { + func updateGSMScore(gsmScore: String) { + model?.updateGSMScore(gsmScore: gsmScore) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index c2b155ae..cbf96673 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -15,6 +15,9 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { @Published var isPresentedProfileCamera: Bool = false @Published var isPresentedProfileImage: Bool = false @Published var isPresentedTechStackAppend: Bool = false + + // MARK: SchoolLife + @Published var gsmScore: String = "" } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index 0a5512fb..dff22986 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -1,3 +1,3 @@ -protocol MyPageStateProtocol: MyPageProfileStateProtocol {} +protocol MyPageStateProtocol: MyPageProfileStateProtocol, MyPageSchoolLifeStateProtocol {} -protocol MyPageActionProtocol: AnyObject, MyPageProfileActionProtocol {} +protocol MyPageActionProtocol: AnyObject, MyPageProfileActionProtocol, MyPageSchoolLifeActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModel.swift similarity index 100% rename from Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModelProtocol.swift rename to Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModel.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageSchoolLifeModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageSchoolLifeModel.swift new file mode 100644 index 00000000..48392fc0 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageSchoolLifeModel.swift @@ -0,0 +1,13 @@ +protocol MyPageSchoolLifeStateProtocol { + var gsmScore: String { get } +} + +protocol MyPageSchoolLifeActionProtocol: AnyObject { + func updateGSMScore(gsmScore: String) +} + +extension MyPageModel: MyPageSchoolLifeActionProtocol { + func updateGSMScore(gsmScore: String) { + self.gsmScore = gsmScore + } +} From 14be83c05836457d436c44432e62e43e21a4fce1 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 2 Aug 2023 17:13:46 +0900 Subject: [PATCH 08/62] :sparkles: :: [#220] MyPgaeFeature / MyPageWorkInfo --- Projects/Feature/MyPageFeature/Project.swift | 3 +- .../Sources/Intent/MyPageIntentProtocol.swift | 5 +- .../Sources/Intent/MyPageWorkInfoIntent.swift | 41 ++++++++++++++++ .../Sources/Model/MyPageModel.swift | 8 ++++ .../Sources/Model/MyPageModelProtocol.swift | 11 ++++- .../Sources/Model/MyPageWorkInfoModel.swift | 47 +++++++++++++++++++ 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageWorkInfoIntent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index bd10af29..87668c49 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -8,6 +8,7 @@ let project = Project.makeModule( targets: [.interface, .unitTest, .demo], internalDependencies: [ .Feature.BaseFeature, - .Domain.UserDomainInterface + .Domain.UserDomainInterface, + .Domain.StudentDomainInterface, ] ) diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 80726e44..ab4bc2a1 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -1,3 +1,6 @@ import Foundation -protocol MyPageIntentProtocol: MyPageProfileIntentProtocol, MyPageSchoolLifeIntentProtocol {} +protocol MyPageIntentProtocol: + MyPageProfileIntentProtocol, + MyPageSchoolLifeIntentProtocol, + MyPageWorkInfoIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageWorkInfoIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageWorkInfoIntent.swift new file mode 100644 index 00000000..cd50dc2f --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageWorkInfoIntent.swift @@ -0,0 +1,41 @@ +import StudentDomainInterface + +protocol MyPageWorkInfoIntentProtocol { + func appendWorkRegion() + func updateWorkRegion(region: String, at index: Int) + func deleteWorkRegion(at index: Int) + func updateSalary(salary: String) + func updateFormOfEmployment(form: FormOfEmployment) + func formOfEmployeementSheetIsRequired() + func formOfEmployeementSheetDismissed() +} + +extension MyPageIntent: MyPageWorkInfoIntentProtocol { + func appendWorkRegion() { + model?.appendWorkRegion() + } + + func updateWorkRegion(region: String, at index: Int) { + model?.updateWorkRegion(region: region, at: index) + } + + func deleteWorkRegion(at index: Int) { + model?.deleteWorkRegion(at: index) + } + + func updateSalary(salary: String) { + model?.updateSalary(salary: salary) + } + + func updateFormOfEmployment(form: FormOfEmployment) { + model?.updateFormOfEmployment(form: form) + } + + func formOfEmployeementSheetIsRequired() { + model?.updateIsPresentedFormOfEmployeementSheet(isPresented: true) + } + + func formOfEmployeementSheetDismissed() { + model?.updateIsPresentedFormOfEmployeementSheet(isPresented: false) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index cbf96673..b418dc69 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -1,4 +1,5 @@ import Foundation +import StudentDomainInterface final class MyPageModel: ObservableObject, MyPageStateProtocol { // MARK: Profile @@ -18,6 +19,13 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { // MARK: SchoolLife @Published var gsmScore: String = "" + + // MARK: WorkInfo + var workRegionList: [String] = [] + var salary: String = "" + var salaryDisplay: String = "" + var formOfEmployment: FormOfEmployment = .fullTime + var isPresentedFormOfEmployeementSheet: Bool = false } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index dff22986..20efab33 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -1,3 +1,10 @@ -protocol MyPageStateProtocol: MyPageProfileStateProtocol, MyPageSchoolLifeStateProtocol {} +protocol MyPageStateProtocol: + MyPageProfileStateProtocol, + MyPageSchoolLifeStateProtocol, + MyPageWorkInfoStateProtocol {} -protocol MyPageActionProtocol: AnyObject, MyPageProfileActionProtocol, MyPageSchoolLifeActionProtocol {} +protocol MyPageActionProtocol: + AnyObject, + MyPageProfileActionProtocol, + MyPageSchoolLifeActionProtocol, + MyPageWorkInfoActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift new file mode 100644 index 00000000..0dabf766 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift @@ -0,0 +1,47 @@ +import FoundationUtil +import StudentDomainInterface + +protocol MyPageWorkInfoStateProtocol { + var workRegionList: [String] { get } + var salary: String { get } + var salaryDisplay: String { get } + var formOfEmployment: FormOfEmployment { get } + var isPresentedFormOfEmployeementSheet: Bool { get } +} + +protocol MyPageWorkInfoActionProtocol: AnyObject { + func appendWorkRegion() + func updateWorkRegion(region: String, at index: Int) + func deleteWorkRegion(at index: Int) + func updateSalary(salary: String) + func updateFormOfEmployment(form: FormOfEmployment) + func updateIsPresentedFormOfEmployeementSheet(isPresented: Bool) +} + +extension MyPageModel: MyPageWorkInfoActionProtocol { + func appendWorkRegion() { + self.workRegionList.append("") + } + + func updateWorkRegion(region: String, at index: Int) { + guard workRegionList[safe: index] != nil else { return } + self.workRegionList[index] = region + } + + func deleteWorkRegion(at index: Int) { + self.workRegionList.remove(at: index) + } + + func updateSalary(salary: String) { + guard let salaryInt = Int(salary).map({ String(min($0, 9999)) }) else { return } + self.salary = salaryInt + } + + func updateFormOfEmployment(form: FormOfEmployment) { + self.formOfEmployment = form + } + + func updateIsPresentedFormOfEmployeementSheet(isPresented: Bool) { + self.isPresentedFormOfEmployeementSheet = isPresented + } +} From 1ae74677f827d806a609b1c769e81ce94830558e Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 2 Aug 2023 17:28:18 +0900 Subject: [PATCH 09/62] :sparkles: :: [#220] MyPageFeature / MyPageMilitary --- .../Sources/Intent/MyPageIntentProtocol.swift | 3 ++- .../Sources/Intent/MyPageMilitaryIntent.swift | 21 +++++++++++++++++++ .../Sources/Model/MyPageMilitaryModel.swift | 21 +++++++++++++++++++ .../Sources/Model/MyPageModel.swift | 14 ++++++++----- .../Sources/Model/MyPageModelProtocol.swift | 6 ++++-- 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageMilitaryIntent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageMilitaryModel.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index ab4bc2a1..1b5f535b 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -3,4 +3,5 @@ import Foundation protocol MyPageIntentProtocol: MyPageProfileIntentProtocol, MyPageSchoolLifeIntentProtocol, - MyPageWorkInfoIntentProtocol {} + MyPageWorkInfoIntentProtocol, + MyPageMilitaryIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageMilitaryIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageMilitaryIntent.swift new file mode 100644 index 00000000..6e66691e --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageMilitaryIntent.swift @@ -0,0 +1,21 @@ +import StudentDomainInterface + +protocol MyPageMilitaryIntentProtocol { + func militarySheetIsRequired() + func militarySheetDismissed() + func militaryServiceTypeDidSelected(type: MilitaryServiceType) +} + +extension MyPageIntent: MyPageMilitaryIntentProtocol { + func militarySheetIsRequired() { + model?.updateIsPresentedMilitarySheet(isPresented: true) + } + + func militarySheetDismissed() { + model?.updateIsPresentedMilitarySheet(isPresented: false) + } + + func militaryServiceTypeDidSelected(type: MilitaryServiceType) { + model?.updateMilitaryServiceType(type: type) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageMilitaryModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageMilitaryModel.swift new file mode 100644 index 00000000..24987daa --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageMilitaryModel.swift @@ -0,0 +1,21 @@ +import StudentDomainInterface + +protocol MyPageMilitaryStateProtocol { + var selectedMilitaryServiceType: MilitaryServiceType { get } + var isPresentedMilitarySheet: Bool { get } +} + +protocol MyPageMilitaryActionProtocol: AnyObject { + func updateIsPresentedMilitarySheet(isPresented: Bool) + func updateMilitaryServiceType(type: MilitaryServiceType) +} + +extension MyPageModel: MyPageMilitaryActionProtocol { + func updateIsPresentedMilitarySheet(isPresented: Bool) { + self.isPresentedMilitarySheet = isPresented + } + + func updateMilitaryServiceType(type: MilitaryServiceType) { + self.selectedMilitaryServiceType = type + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index b418dc69..403f16a1 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -21,11 +21,15 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { @Published var gsmScore: String = "" // MARK: WorkInfo - var workRegionList: [String] = [] - var salary: String = "" - var salaryDisplay: String = "" - var formOfEmployment: FormOfEmployment = .fullTime - var isPresentedFormOfEmployeementSheet: Bool = false + @Published var workRegionList: [String] = [] + @Published var salary: String = "" + @Published var salaryDisplay: String = "" + @Published var formOfEmployment: FormOfEmployment = .fullTime + @Published var isPresentedFormOfEmployeementSheet: Bool = false + + // MARK: Military + @Published var selectedMilitaryServiceType: MilitaryServiceType = .hope + @Published var isPresentedMilitarySheet: Bool = false } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index 20efab33..f9c21f0e 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -1,10 +1,12 @@ protocol MyPageStateProtocol: MyPageProfileStateProtocol, MyPageSchoolLifeStateProtocol, - MyPageWorkInfoStateProtocol {} + MyPageWorkInfoStateProtocol, + MyPageMilitaryStateProtocol {} protocol MyPageActionProtocol: AnyObject, MyPageProfileActionProtocol, MyPageSchoolLifeActionProtocol, - MyPageWorkInfoActionProtocol {} + MyPageWorkInfoActionProtocol, + MyPageMilitaryActionProtocol {} From 06073c6393bf4f72b40dbfc243a27d52c1c53247 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 2 Aug 2023 17:35:22 +0900 Subject: [PATCH 10/62] :sparkles: :: [#220] MyPageFeature / MyPageCertificate --- .../Intent/MyPageCertificateIntent.swift | 19 ++++++++++++++ .../Sources/Intent/MyPageIntentProtocol.swift | 3 ++- .../Model/MyPageCertificateModel.swift | 26 +++++++++++++++++++ .../Sources/Model/MyPageModel.swift | 3 +++ .../Sources/Model/MyPageModelProtocol.swift | 6 +++-- 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift new file mode 100644 index 00000000..e05cdd7b --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift @@ -0,0 +1,19 @@ +protocol MyPageCertificateIntentProtocol { + func updateCertificate(certificate: String, at index: Int) + func deleteCertificateColumn(at index: Int) + func certificateAppendButtonDidTap() +} + +extension MyPageIntent: MyPageCertificateIntentProtocol { + func updateCertificate(certificate: String, at index: Int) { + model?.updateCertificate(certificate: certificate, at: index) + } + + func deleteCertificateColumn(at index: Int) { + model?.deleteCertificateColumn(at: index) + } + + func certificateAppendButtonDidTap() { + model?.appendCertificate() + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 1b5f535b..279ed1b4 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -4,4 +4,5 @@ protocol MyPageIntentProtocol: MyPageProfileIntentProtocol, MyPageSchoolLifeIntentProtocol, MyPageWorkInfoIntentProtocol, - MyPageMilitaryIntentProtocol {} + MyPageMilitaryIntentProtocol, + MyPageCertificateIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift new file mode 100644 index 00000000..aa252d17 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift @@ -0,0 +1,26 @@ +import FoundationUtil + +protocol MyPageCertificateStateProtocol { + var certificates: [String] { get } +} + +protocol MyPageCertificateActionProtocol: AnyObject { + func updateCertificate(certificate: String, at index: Int) + func deleteCertificateColumn(at index: Int) + func appendCertificate() +} + +extension MyPageModel: MyPageCertificateActionProtocol { + func updateCertificate(certificate: String, at index: Int) { + guard certificates[safe: index] != nil else { return } + certificates[index] = certificate + } + + func deleteCertificateColumn(at index: Int) { + certificates.remove(at: index) + } + + func appendCertificate() { + certificates.append("") + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index 403f16a1..a090097d 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -30,6 +30,9 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { // MARK: Military @Published var selectedMilitaryServiceType: MilitaryServiceType = .hope @Published var isPresentedMilitarySheet: Bool = false + + // MARK: Certificate + @Published var certificates: [String] = [] } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index f9c21f0e..2158fb7f 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -2,11 +2,13 @@ protocol MyPageStateProtocol: MyPageProfileStateProtocol, MyPageSchoolLifeStateProtocol, MyPageWorkInfoStateProtocol, - MyPageMilitaryStateProtocol {} + MyPageMilitaryStateProtocol, + MyPageCertificateStateProtocol {} protocol MyPageActionProtocol: AnyObject, MyPageProfileActionProtocol, MyPageSchoolLifeActionProtocol, MyPageWorkInfoActionProtocol, - MyPageMilitaryActionProtocol {} + MyPageMilitaryActionProtocol, + MyPageCertificateActionProtocol {} From 37295cd776da27cc0d965894c6dcdbc4c1de27f3 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 2 Aug 2023 17:51:49 +0900 Subject: [PATCH 11/62] :sparkles: :: [#220] MyPageFeature / MyPageLanguage --- .../Sources/Intent/MyPageIntentProtocol.swift | 3 +- .../Sources/Intent/MyPageLanguageIntent.swift | 24 +++++++++++ .../Sources/Model/MyPageLanguageModel.swift | 42 +++++++++++++++++++ .../Sources/Model/MyPageModel.swift | 3 ++ .../Sources/Model/MyPageModelProtocol.swift | 6 ++- 5 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 279ed1b4..f8258d10 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -5,4 +5,5 @@ protocol MyPageIntentProtocol: MyPageSchoolLifeIntentProtocol, MyPageWorkInfoIntentProtocol, MyPageMilitaryIntentProtocol, - MyPageCertificateIntentProtocol {} + MyPageCertificateIntentProtocol, + MyPageLanguageIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift new file mode 100644 index 00000000..c6165134 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift @@ -0,0 +1,24 @@ +protocol MyPageLanguageIntentProtocol { + func updateLanguageName(name: String, at index: Int) + func updateLanguageScore(score: String, at index: Int) + func deleteLanguage(at index: Int) + func languageAppendButtonDidTap() +} + +extension MyPageIntent: MyPageLanguageIntentProtocol { + func updateLanguageName(name: String, at index: Int) { + model?.updateLanguageName(name: name, at: index) + } + + func updateLanguageScore(score: String, at index: Int) { + model?.updateLanguageScore(score: score, at: index) + } + + func deleteLanguage(at index: Int) { + model?.deleteLanguage(at: index) + } + + func languageAppendButtonDidTap() { + model?.appendLanguage() + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift new file mode 100644 index 00000000..44b9b048 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift @@ -0,0 +1,42 @@ +import StudentDomainInterface + +struct LanguageModel: Equatable { + var name: String + var score: String + + init(name: String, score: String) { + self.name = name + self.score = score + } +} + +protocol MyPageLanguageInfoStateProtocol { + var languageList: [LanguageModel] { get } +} + +protocol MyPageLanguageInfoActionProtocol: AnyObject { + func updateLanguageName(name: String, at index: Int) + func updateLanguageScore(score: String, at index: Int) + func deleteLanguage(at index: Int) + func appendLanguage() +} + +extension MyPageModel: MyPageLanguageInfoActionProtocol { + func updateLanguageName(name: String, at index: Int) { + guard languageList[safe: index] != nil else { return } + languageList[index].name = name + } + + func updateLanguageScore(score: String, at index: Int) { + guard languageList[safe: index] != nil else { return } + languageList[index].score = score + } + + func deleteLanguage(at index: Int) { + languageList.remove(at: index) + } + + func appendLanguage() { + languageList.append(LanguageModel(name: "", score: "")) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index a090097d..f1a8047e 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -33,6 +33,9 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { // MARK: Certificate @Published var certificates: [String] = [] + + // MARK: Language + @Published var languageList: [LanguageModel] = [] } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index 2158fb7f..682fc070 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -3,7 +3,8 @@ protocol MyPageStateProtocol: MyPageSchoolLifeStateProtocol, MyPageWorkInfoStateProtocol, MyPageMilitaryStateProtocol, - MyPageCertificateStateProtocol {} + MyPageCertificateStateProtocol, + MyPageLanguageInfoStateProtocol {} protocol MyPageActionProtocol: AnyObject, @@ -11,4 +12,5 @@ protocol MyPageActionProtocol: MyPageSchoolLifeActionProtocol, MyPageWorkInfoActionProtocol, MyPageMilitaryActionProtocol, - MyPageCertificateActionProtocol {} + MyPageCertificateActionProtocol, + MyPageLanguageInfoActionProtocol {} From 8025c25fcdb96a7074e1590389562a4202710a8a Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 2 Aug 2023 18:20:23 +0900 Subject: [PATCH 12/62] :sparkles: :: [#220] MyPageFeature / MYPageProject --- .../Intent/InputProjectInfoIntent.swift | 10 - .../InputProjectInfoIntentProtocol.swift | 2 - .../Sources/Intent/MyPageIntentProtocol.swift | 3 +- .../Sources/Intent/MyPageProjectIntent.swift | 152 +++++++++++++ .../Sources/Model/MyPageModel.swift | 10 + .../Sources/Model/MyPageModelProtocol.swift | 6 +- .../Sources/Model/MyPageProjectModel.swift | 200 ++++++++++++++++++ 7 files changed, 368 insertions(+), 15 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index 65a85946..5fa1bb21 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -95,16 +95,6 @@ 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 projectIsInProgressButtonDidTap(index: Int, isInProgress: Bool) { model?.updateIsInProgress(index: index, isInProgress: isInProgress) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index a3131b88..16e895ff 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -14,8 +14,6 @@ protocol InputProjectInfoIntentProtocol { 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) func projectStartAtDidSelect(index: Int, startAt: Date) func projectEndAtDidSelect(index: Int, endAt: Date) func projectIsInProgressButtonDidTap(index: Int, isInProgress: Bool) diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index f8258d10..014b9b02 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -6,4 +6,5 @@ protocol MyPageIntentProtocol: MyPageWorkInfoIntentProtocol, MyPageMilitaryIntentProtocol, MyPageCertificateIntentProtocol, - MyPageLanguageIntentProtocol {} + MyPageLanguageIntentProtocol, + MyPageProjectIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift new file mode 100644 index 00000000..68def5b8 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift @@ -0,0 +1,152 @@ +import DesignSystem +import Foundation + +protocol MyPageProjectIntentProtocol { + func projectToggleButtonDidTap(index: Int) + func updateProjectName(index: Int, name: String) + 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 removeProjectTechStackButtonDidTap(index: Int, techStack: 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 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) + func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) + func projectAppendButtonDidTap() + func projectRemoveButtonDidTap(index: Int) + func projectIconImageButtonDidTap(index: Int) + func projectIconImagePickerDismissed() + func projectPreviewImagePickerDismissed() + func projectStartAtDatePickerDismissed() + func projectEndAtDatePickerDismissed() + func projectTechStackAppendButtonDidTap(index: Int) + func projectTechStackAppendDismissed() +} + +extension MyPageIntent: MyPageProjectIntentProtocol { + func projectToggleButtonDidTap(index: Int) { + model?.toggleCollapsedProject(index: index) + } + + func updateProjectName(index: Int, name: String) { + model?.updateProjectName(index: index, name: name) + } + + func updateIconImage(index: Int, image: PickedImageResult) { + #warning("이미지 업로드 후 model에서 iconURL 변경") + } + + func appendPreviewImageButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedPreviewImagePicker(isPresented: true) + } + + func appendPreviewImage(index: Int, image: PickedImageResult) { + #warning("이미지 업로드 후 model에서 preview 이미지 추가") + } + + 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 removeProjectTechStackButtonDidTap(index: Int, techStack: String) { + model?.removeProjectTechStack(index: index, techStack: techStack) + } + + func updateProjectMainTask(index: Int, mainTask: String) { + model?.updateProjectMainTask(index: index, mainTask: mainTask) + } + + func projectStartAtButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedProjectStartAtDatePicker(isPresented: true) + } + + func projectEndAtButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedProjectEndAtDatePicker(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) + } + + 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 relatedLinkAppendButtonDidTap(index: Int) { + model?.appendEmptyRelatedLink(index: index) + } + + func removeProjectRelatedLinkDidTap(index: Int, linkIndex: Int) { + model?.removeProjectRelatedLink(index: index, linkIndex: linkIndex) + } + + func projectAppendButtonDidTap() { + model?.appendEmptyProject() + } + + func projectRemoveButtonDidTap(index: Int) { + model?.removeProject(index: index) + } + + func projectIconImageButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedProjectImagePicker(isPresented: true) + } + + func projectIconImagePickerDismissed() { + model?.updateIsPresentedProjectImagePicker(isPresented: false) + } + + func projectPreviewImagePickerDismissed() { + model?.updateIsPresentedPreviewImagePicker(isPresented: false) + } + + func projectStartAtDatePickerDismissed() { + model?.updateIsPresentedProjectStartAtDatePicker(isPresented: false) + } + + func projectEndAtDatePickerDismissed() { + model?.updateIsPresentedProjectEndAtDatePicker(isPresented: false) + } + + func projectTechStackAppendButtonDidTap(index: Int) { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedProjectTechStackAppend(isPresented: true) + } + + func projectTechStackAppendDismissed() { + model?.updateIsPresentedProjectTechStackAppend(isPresented: false) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index f1a8047e..d1f8159b 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -36,6 +36,16 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { // MARK: Language @Published var languageList: [LanguageModel] = [] + + // MARK: Project + @Published var projectList: [ProjectModel] = [] + @Published var collapsedProject: [Bool] = [] + @Published var focusedProjectIndex: Int = 0 + @Published var isPresentedProjectImagePicker: Bool = false + @Published var isPresentedPreviewImagePicker: Bool = false + @Published var isPresentedProjectStartAtDatePicker: Bool = false + @Published var isPresentedProjectEndAtDatePicker: Bool = false + @Published var isPresentedProjectTechStackAppend: Bool = false } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index 682fc070..b949f179 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -4,7 +4,8 @@ protocol MyPageStateProtocol: MyPageWorkInfoStateProtocol, MyPageMilitaryStateProtocol, MyPageCertificateStateProtocol, - MyPageLanguageInfoStateProtocol {} + MyPageLanguageInfoStateProtocol, + MyPageProjectStateProtocol {} protocol MyPageActionProtocol: AnyObject, @@ -13,4 +14,5 @@ protocol MyPageActionProtocol: MyPageWorkInfoActionProtocol, MyPageMilitaryActionProtocol, MyPageCertificateActionProtocol, - MyPageLanguageInfoActionProtocol {} + MyPageLanguageInfoActionProtocol, + MyPageProjectActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift new file mode 100644 index 00000000..23aed24d --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift @@ -0,0 +1,200 @@ +import DateUtil +import Foundation +import FoundationUtil + +struct ProjectModel { + var name: String + var iconImage: String + var previewImages: [String] + var content: String + var techStacks: Set + var mainTask: String + var startAt: Date + var endAt: Date? + var isInProgress: Bool + var relatedLinks: [RelatedLink] + + var startAtString: String { + startAt.toStringCustomFormat(format: "yyyy.MM") + } + var endAtString: String { + endAt?.toStringCustomFormat(format: "yyyy.MM") ?? "" + } +} + +extension ProjectModel { + struct RelatedLink { + var name: String + var url: String + } +} + +protocol MyPageProjectStateProtocol { + var projectList: [ProjectModel] { get } + var collapsedProject: [Bool] { get } + var focusedProjectIndex: Int { get } + var isPresentedProjectImagePicker: Bool { get } + var isPresentedPreviewImagePicker: Bool { get } + var isPresentedProjectStartAtDatePicker: Bool { get } + var isPresentedProjectEndAtDatePicker: Bool { get } + var isPresentedProjectTechStackAppend: Bool { get } +} + +protocol MyPageProjectActionProtocol: AnyObject { + func toggleCollapsedProject(index: Int) + func updateProjectName(index: Int, name: String) + func updateIconImage(index: Int, imageURL: String) + func appendPreviewImage(index: Int, imageURL: String) + 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) + 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) + func removeProjectRelatedLink(index: Int, linkIndex: Int) + func appendEmptyProject() + func removeProject(index: Int) + func updateFocusedProjectIndex(index: Int) + func updateIsPresentedProjectImagePicker(isPresented: Bool) + func updateIsPresentedPreviewImagePicker(isPresented: Bool) + func updateIsPresentedProjectStartAtDatePicker(isPresented: Bool) + func updateIsPresentedProjectEndAtDatePicker(isPresented: Bool) + func updateIsPresentedProjectTechStackAppend(isPresented: Bool) +} + +extension MyPageModel: MyPageProjectActionProtocol { + func toggleCollapsedProject(index: Int) { + guard collapsedProject[safe: index] != nil else { return } + collapsedProject[index].toggle() + } + + func updateProjectName(index: Int, name: String) { + guard projectList[safe: index] != nil else { return } + projectList[index].name = name + } + + func updateIconImage(index: Int, imageURL: String) { + guard projectList[safe: index] != nil else { return } + projectList[index].iconImage = imageURL + } + + func appendPreviewImage(index: Int, imageURL: String) { + guard projectList[safe: index] != nil else { return } + projectList[index].previewImages.append(imageURL) + } + + func removePreviewImage(index: Int, previewIndex: Int) { + guard projectList[safe: index] != nil else { return } + guard projectList[index].previewImages[safe: previewIndex] != nil else { return } + projectList[index].previewImages.remove(at: previewIndex) + } + + func updateProjectContent(index: Int, content: String) { + guard projectList[safe: index] != nil else { return } + projectList[index].content = content + } + + func updateProjectTechStacks(index: Int, techStacks: [String]) { + guard projectList[safe: index] != nil else { return } + projectList[index].techStacks = Set(techStacks) + } + + func removeProjectTechStack(index: Int, techStack: String) { + guard projectList[safe: index] != nil else { return } + projectList[index].techStacks.remove(techStack) + } + + func updateProjectMainTask(index: Int, mainTask: String) { + guard projectList[safe: index] != nil else { return } + projectList[index].mainTask = mainTask + } + + func updateProjectStartAt(index: Int, startAt: Date) { + guard projectList[safe: index] != nil else { return } + projectList[index].startAt = startAt + } + + func updateProjectEndAt(index: Int, endAt: Date) { + guard projectList[safe: index] != nil else { return } + projectList[index].endAt = endAt + } + + func updateIsInProgress(index: Int, isInProgress: Bool) { + guard projectList[safe: index] != nil else { return } + projectList[index].isInProgress = isInProgress + } + + func updateProjectLinkName(index: Int, linkIndex: Int, name: String) { + guard projectList[safe: index] != nil else { return } + guard projectList[index].relatedLinks[safe: linkIndex] != nil else { return } + projectList[index].relatedLinks[linkIndex].name = name + } + + func updateProjectLinkURL(index: Int, linkIndex: Int, url: String) { + guard projectList[safe: index] != nil else { return } + guard projectList[index].relatedLinks[safe: linkIndex] != nil else { return } + projectList[index].relatedLinks[linkIndex].url = url + } + + func appendEmptyRelatedLink(index: Int) { + guard projectList[safe: index] != nil else { return } + projectList[index].relatedLinks.append(.init(name: "", url: "")) + } + + func removeProjectRelatedLink(index: Int, linkIndex: Int) { + guard projectList[safe: index] != nil else { return } + guard projectList[index].relatedLinks[safe: linkIndex] != nil else { return } + projectList[index].relatedLinks.remove(at: linkIndex) + } + + func appendEmptyProject() { + projectList.append( + .init( + name: "", + iconImage: "", + previewImages: [], + content: "", + techStacks: [], + mainTask: "", + startAt: Date(), + endAt: nil, + isInProgress: false, + relatedLinks: [] + ) + ) + } + + func removeProject(index: Int) { + guard projectList[safe: index] != nil else { return } + projectList.remove(at: index) + } + + func updateFocusedProjectIndex(index: Int) { + focusedProjectIndex = index + } + + func updateIsPresentedProjectImagePicker(isPresented: Bool) { + isPresentedProjectImagePicker = isPresented + } + + func updateIsPresentedPreviewImagePicker(isPresented: Bool) { + isPresentedPreviewImagePicker = isPresented + } + + func updateIsPresentedProjectStartAtDatePicker(isPresented: Bool) { + isPresentedProjectStartAtDatePicker = isPresented + } + + func updateIsPresentedProjectEndAtDatePicker(isPresented: Bool) { + isPresentedProjectEndAtDatePicker = isPresented + } + + func updateIsPresentedProjectTechStackAppend(isPresented: Bool) { + isPresentedProjectTechStackAppend = isPresented + } +} From 39476a70ad28e3ecf3258a5534950012e78638cb Mon Sep 17 00:00:00 2001 From: baegteun Date: Thu, 3 Aug 2023 14:46:45 +0900 Subject: [PATCH 13/62] :seedling: :: [#220] MyPageFeature / PrizeModel --- .../MyPageFeature/Sources/Model/MyPagePrizeModel.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift new file mode 100644 index 00000000..954dd727 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift @@ -0,0 +1,9 @@ +// +// MyPagePrizeModel.swift +// MyPageFeature +// +// Created by 최형우 on 2023/08/02. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation From 873019546e795ebc95319850c63b24397c7e8b6e Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 3 Aug 2023 16:59:26 +0900 Subject: [PATCH 14/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPgaeFeature=20/?= =?UTF-8?q?=20MyPagePrizeModel=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 | 1 + .../Sources/Model/InputPrizeInfoModel.swift | 1 - .../Sources/Model/MyPageModel.swift | 6 ++ .../Sources/Model/MyPagePrizeModel.swift | 84 +++++++++++++++++-- 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index ff0e9375..64bbd30e 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -35,6 +35,7 @@ import MainFeature import MainFeatureInterface import MajorDomain import MajorDomainInterface +import MyPageFeatureInterface import NeedleFoundation import RootFeature import SigninFeature diff --git a/Projects/Feature/InputPrizeInfoFeature/Sources/Model/InputPrizeInfoModel.swift b/Projects/Feature/InputPrizeInfoFeature/Sources/Model/InputPrizeInfoModel.swift index 8773e0be..8151ff1a 100644 --- a/Projects/Feature/InputPrizeInfoFeature/Sources/Model/InputPrizeInfoModel.swift +++ b/Projects/Feature/InputPrizeInfoFeature/Sources/Model/InputPrizeInfoModel.swift @@ -52,5 +52,4 @@ extension InputPrizeInfoModel: InputPrizeInfoActionProtocol { func updateIsPresentedPrizeAtDatePicker(isPresented: Bool) { self.isPresentedPrizeAtDatePicker = isPresented } - } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index d1f8159b..35575d30 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -46,6 +46,12 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { @Published var isPresentedProjectStartAtDatePicker: Bool = false @Published var isPresentedProjectEndAtDatePicker: Bool = false @Published var isPresentedProjectTechStackAppend: Bool = false + + // MARK: Prize + @Published var prizeList: [PrizeModel] = [] + @Published var collapsedPrize: [Bool] = [] + @Published var isPresentedPrizeAtDatePicker: Bool = false + @Published var focusedPrizeIndex: Int = 0 } extension MyPageModel: MyPageActionProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift index 954dd727..e126fe20 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift @@ -1,9 +1,77 @@ -// -// MyPagePrizeModel.swift -// MyPageFeature -// -// Created by 최형우 on 2023/08/02. -// Copyright © 2023 com.msg. All rights reserved. -// - import Foundation +import DateUtil + +struct PrizeModel: Equatable { + var name: String + var prize: String + var prizeAt: Date + + var prizeAtString: String { + prizeAt.toStringCustomFormat(format: "yyyy.MM") + } +} + +protocol MyPagePrizeStateProtocol { + var prizeList: [PrizeModel] { get } + var collapsedPrize: [Bool] { get } + var isPresentedPrizeAtDatePicker: Bool { get } + var focusedPrizeIndex: Int { get } +} + +protocol MyPagePrizeActionProtocol: AnyObject { + func toggleCollapsedPrize(index: Int) + func updatePrizeName(index: Int, name: String) + func updatePrizePrize(index: Int, prize: String) + func updatePrizePrizeAt(index: Int, prizeAt: Date) + func appendEmptyPrize() + func removePrize(index: Int) + func updateFocusedPrizeIndex(index: Int) + func updateIsPresentedPrizeAtDatePicker(isPresented: Bool) +} + +extension MyPageModel: MyPagePrizeActionProtocol { + func toggleCollapsedPrize(index: Int) { + guard collapsedPrize[safe: index] != nil else { return } + self.collapsedPrize[index].toggle() + } + + func updatePrizeName(index: Int, name: String) { + guard prizeList[safe: index] != nil else { return } + self.prizeList[index].name = name + } + + func updatePrizePrize(index: Int, prize: String) { + guard prizeList[safe: index] != nil else { return } + self.prizeList[index].prize = prize + } + + func updatePrizePrizeAt(index: Int, prizeAt: Date) { + guard prizeList[safe: index] != nil else { return } + self.prizeList[index].prizeAt = prizeAt + } + + func appendEmptyPrize() { + let newPrize = PrizeModel( + name: "", + prize: "", + prizeAt: Date() + ) + self.prizeList.append(newPrize) + self.collapsedPrize.append(false) + } + + func removePrize(index: Int) { + guard prizeList[safe: index] != nil, collapsedPrize[safe: index] != nil else { return } + self.prizeList.remove(at: index) + self.collapsedPrize.remove(at: index) + } + + func updateFocusedPrizeIndex(index: Int) { + self.focusedPrizeIndex = index + } + + func updateIsPresentedPrizeAtDatePicker(isPresented: Bool) { + self.isPresentedPrizeAtDatePicker = isPresented + } + +} From c5538e9cc894cf9ef4790519ea6e9c0172be50d1 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 3 Aug 2023 17:12:20 +0900 Subject: [PATCH 15/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPgaeFeature=20/?= =?UTF-8?q?=20MyPagePrizeIntent=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/MyPageCertificateIntent.swift | 4 +- .../Sources/Intent/MyPageIntentProtocol.swift | 3 +- .../Intent/MyPagePrizeIntentProtocol.swift | 62 +++++++++++++++++++ .../Model/MyPageCertificateModel.swift | 4 +- .../Sources/Model/MyPageModelProtocol.swift | 6 +- 5 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Intent/MyPagePrizeIntentProtocol.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift index e05cdd7b..0483adc4 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageCertificateIntent.swift @@ -8,11 +8,11 @@ extension MyPageIntent: MyPageCertificateIntentProtocol { func updateCertificate(certificate: String, at index: Int) { model?.updateCertificate(certificate: certificate, at: index) } - + func deleteCertificateColumn(at index: Int) { model?.deleteCertificateColumn(at: index) } - + func certificateAppendButtonDidTap() { model?.appendCertificate() } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 014b9b02..96fabd27 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -7,4 +7,5 @@ protocol MyPageIntentProtocol: MyPageMilitaryIntentProtocol, MyPageCertificateIntentProtocol, MyPageLanguageIntentProtocol, - MyPageProjectIntentProtocol {} + MyPageProjectIntentProtocol, + MyPagePrizeIntentProtocol {} diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPagePrizeIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPagePrizeIntentProtocol.swift new file mode 100644 index 00000000..b7aa4551 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPagePrizeIntentProtocol.swift @@ -0,0 +1,62 @@ +import Foundation + +protocol MyPagePrizeIntentProtocol { + func prizeToggleButtonDidTap(index: Int) + func prizeRemoveButtonDidTap(index: Int) + func updatePrizeName(index: Int, name: String) + func updatePrizePrize(index: Int, prize: String) + func updatePrizePrizeAt(index: Int, prizeAt: Date) + func prizePrizeAtDidSelect(index: Int, prizeAt: Date) + func appendEmptyPrize() + func removePrize(index: Int) + func prizeAtButtonDidTap(index: Int) + func prizeAtDismissed() + func prizeAppendButtonDidTap() +} + +extension MyPageIntent: MyPagePrizeIntentProtocol { + func prizeToggleButtonDidTap(index: Int) { + model?.toggleCollapsedPrize(index: index) + } + + func updatePrizeName(index: Int, name: String) { + model?.updatePrizeName(index: index, name: name) + } + + func updatePrizePrize(index: Int, prize: String) { + model?.updatePrizePrize(index: index, prize: prize) + } + + func updatePrizePrizeAt(index: Int, prizeAt: Date) { + model?.updatePrizePrizeAt(index: index, prizeAt: prizeAt) + } + + func prizePrizeAtDidSelect(index: Int, prizeAt: Date) { + model?.updatePrizePrizeAt(index: index, prizeAt: prizeAt) + } + + func prizeRemoveButtonDidTap(index: Int) { + model?.removePrize(index: index) + } + + func appendEmptyPrize() { + model?.appendEmptyPrize() + } + + func removePrize(index: Int) { + model?.removePrize(index: index) + } + + func prizeAtButtonDidTap(index: Int) { + model?.updateFocusedPrizeIndex(index: index) + model?.updateIsPresentedPrizeAtDatePicker(isPresented: true) + } + + func prizeAtDismissed() { + model?.updateIsPresentedPrizeAtDatePicker(isPresented: false) + } + + func prizeAppendButtonDidTap() { + model?.appendEmptyPrize() + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift index aa252d17..ea77add7 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift @@ -15,11 +15,11 @@ extension MyPageModel: MyPageCertificateActionProtocol { guard certificates[safe: index] != nil else { return } certificates[index] = certificate } - + func deleteCertificateColumn(at index: Int) { certificates.remove(at: index) } - + func appendCertificate() { certificates.append("") } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index b949f179..e792fd3b 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -5,7 +5,8 @@ protocol MyPageStateProtocol: MyPageMilitaryStateProtocol, MyPageCertificateStateProtocol, MyPageLanguageInfoStateProtocol, - MyPageProjectStateProtocol {} + MyPageProjectStateProtocol, + MyPagePrizeStateProtocol {} protocol MyPageActionProtocol: AnyObject, @@ -15,4 +16,5 @@ protocol MyPageActionProtocol: MyPageMilitaryActionProtocol, MyPageCertificateActionProtocol, MyPageLanguageInfoActionProtocol, - MyPageProjectActionProtocol {} + MyPageProjectActionProtocol, + MyPagePrizeActionProtocol {} From 85a3191f08b4cb658ccc1ddbf69812188918c759 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:31:07 +0900 Subject: [PATCH 16/62] =?UTF-8?q?:art:=20LogoutLine=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Icons.xcassets/LogoutLine.imageset/Contents.json | 12 ++++++++++++ .../LogoutLine.imageset/LogoutLine.svg | 4 ++++ .../Core/DesignSystem/Sources/Icon/SMSIcon.swift | 4 ++++ 3 files changed, 20 insertions(+) create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/Contents.json create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/LogoutLine.svg diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/Contents.json b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/Contents.json new file mode 100644 index 00000000..0e798e55 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "LogoutLine.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/LogoutLine.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/LogoutLine.svg new file mode 100644 index 00000000..e5514b25 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/LogoutLine.imageset/LogoutLine.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift b/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift index 8520184a..3b7d1866 100644 --- a/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift +++ b/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift @@ -37,6 +37,7 @@ public struct SMSIcon: View { case smsLogo case trash case leftArrow + case logoutLine case smallPlus case upArrow case magnifyingglass @@ -103,6 +104,9 @@ public struct SMSIcon: View { case .leftArrow: return DesignSystemAsset.Icons.leftArrow.swiftUIImage + case .logoutLine: + return DesignSystemAsset.Icons.logoutLine.swiftUIImage + case .redLogout: return DesignSystemAsset.Icons.redLogout.swiftUIImage From b8aa3b08c0aff2867f587e61cae6ac686de5db95 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:31:56 +0900 Subject: [PATCH 17/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPgaeFeature=20/?= =?UTF-8?q?=20MyPage=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=ED=83=88=ED=87=B4=20=EA=B8=B0=EB=8A=A5=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/DI/AppComponent.swift | 6 ++ .../Sources/Application/NeedleGenerated.swift | 32 ++++++- Projects/Feature/MainFeature/Project.swift | 1 - .../Sources/DI/MainComponent.swift | 9 +- .../Sources/Intent/MainIntent.swift | 59 ++---------- .../Sources/Intent/MainIntentProtocol.swift | 13 +-- .../MainFeature/Sources/Model/MainModel.swift | 26 ++---- .../Sources/Model/MainModelProtocol.swift | 10 +-- .../MainFeature/Sources/Scene/MainView.swift | 86 +++--------------- .../Interface/MyPageBuildable.swift | 2 +- .../Interface/MyPageDelegate.swift | 3 + Projects/Feature/MyPageFeature/Project.swift | 1 + .../Sources/DI/MyPageComponent.swift | 11 ++- .../Sources/Intent/MyPageIntent.swift | 62 ++++++++++++- .../Sources/Intent/MyPageIntentProtocol.swift | 11 ++- .../Sources/Model/MyPageModel.swift | 24 ++++- .../Sources/Model/MyPageModelProtocol.swift | 14 ++- .../Sources/Scene/MyPageView.swift | 89 ++++++++++++++++++- 18 files changed, 278 insertions(+), 181 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Interface/MyPageDelegate.swift diff --git a/Projects/App/Sources/Application/DI/AppComponent.swift b/Projects/App/Sources/Application/DI/AppComponent.swift index 382a3f78..e79fc5bb 100644 --- a/Projects/App/Sources/Application/DI/AppComponent.swift +++ b/Projects/App/Sources/Application/DI/AppComponent.swift @@ -29,6 +29,8 @@ import KeychainModule import KeychainModuleInterface import MainFeature import MainFeatureInterface +import MyPageFeature +import MyPageFeatureInterface import MajorDomain import MajorDomainInterface import NeedleFoundation @@ -102,6 +104,10 @@ final class AppComponent: BootstrapComponent { MainComponent(parent: self) } + var myPageBuildable: any MyPageBuildable { + MyPageComponent(parent: self) + } + var techStackAppendBuildable: any TechStackAppendBuildable { TechStackAppendComponent(parent: self) } diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 64bbd30e..c08dff82 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -35,6 +35,7 @@ import MainFeature import MainFeatureInterface import MajorDomain import MajorDomainInterface +import MyPageFeature import MyPageFeatureInterface import NeedleFoundation import RootFeature @@ -106,6 +107,22 @@ private class InputProjectInfoDependencye065c7f60c5c520999a0Provider: InputProje private func factory2378736e5949c5e8e9f4f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return InputProjectInfoDependencye065c7f60c5c520999a0Provider(appComponent: parent1(component) as! AppComponent) } +private class MyPageDependency48d84b530313b3ee40feProvider: MyPageDependency { + var userDomainBuildable: any UserDomainBuildable { + return appComponent.userDomainBuildable + } + var authDomainBuildable: any AuthDomainBuildable { + return appComponent.authDomainBuildable + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->MyPageComponent +private func factory0f6f456ebf157d02dfb3f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return MyPageDependency48d84b530313b3ee40feProvider(appComponent: parent1(component) as! AppComponent) +} private class InputWorkInfoDependency74441f61366e4e5af9a2Provider: InputWorkInfoDependency { @@ -121,12 +138,12 @@ private class MainDependency7c6a5b4738b211b8e155Provider: MainDependency { var studentDomainBuildable: any StudentDomainBuildable { return appComponent.studentDomainBuildable } - var authDomainBuildable: any AuthDomainBuildable { - return appComponent.authDomainBuildable - } var filterBuildable: any FilterBuildable { return appComponent.filterBuildable } + var myPageBuildable: any MyPageBuildable { + return appComponent.myPageBuildable + } var studentDetailBuildable: any StudentDetailBuildable { return appComponent.studentDetailBuildable } @@ -445,6 +462,12 @@ extension InputProjectInfoComponent: Registration { keyPathToName[\InputProjectInfoDependency.techStackAppendBuildable] = "techStackAppendBuildable-any TechStackAppendBuildable" } } +extension MyPageComponent: Registration { + public func registerItems() { + keyPathToName[\MyPageDependency.userDomainBuildable] = "userDomainBuildable-any UserDomainBuildable" + keyPathToName[\MyPageDependency.authDomainBuildable] = "authDomainBuildable-any AuthDomainBuildable" + } +} extension InputWorkInfoComponent: Registration { public func registerItems() { @@ -453,8 +476,8 @@ extension InputWorkInfoComponent: Registration { extension MainComponent: Registration { public func registerItems() { keyPathToName[\MainDependency.studentDomainBuildable] = "studentDomainBuildable-any StudentDomainBuildable" - keyPathToName[\MainDependency.authDomainBuildable] = "authDomainBuildable-any AuthDomainBuildable" keyPathToName[\MainDependency.filterBuildable] = "filterBuildable-any FilterBuildable" + keyPathToName[\MainDependency.myPageBuildable] = "myPageBuildable-any MyPageBuildable" keyPathToName[\MainDependency.studentDetailBuildable] = "studentDetailBuildable-any StudentDetailBuildable" keyPathToName[\MainDependency.userDomainBuildable] = "userDomainBuildable-any UserDomainBuildable" } @@ -587,6 +610,7 @@ private func register1() { registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->SplashComponent", factoryace9f05f51d68f4c0677f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->InputProjectInfoComponent", factory2378736e5949c5e8e9f4f47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->MyPageComponent", factory0f6f456ebf157d02dfb3f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->InputWorkInfoComponent", factoryfff86bd7854b30412216e3b0c44298fc1c149afb) registerProviderFactory("^->AppComponent->MainComponent", factoryc9274e46e78e70f29c54f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->InputSchoolLifeInfoComponent", factorydc1feebed8f042db375fe3b0c44298fc1c149afb) diff --git a/Projects/Feature/MainFeature/Project.swift b/Projects/Feature/MainFeature/Project.swift index 12ae87df..4eb8bd6f 100644 --- a/Projects/Feature/MainFeature/Project.swift +++ b/Projects/Feature/MainFeature/Project.swift @@ -12,7 +12,6 @@ let project = Project.makeModule( .Feature.FilterFeatureInterface, .Feature.MyPageFeatureInterface, .Domain.StudentDomainInterface, - .Domain.AuthDomainInterface, .Domain.UserDomainInterface ] ) diff --git a/Projects/Feature/MainFeature/Sources/DI/MainComponent.swift b/Projects/Feature/MainFeature/Sources/DI/MainComponent.swift index ad7de5e8..3b867d29 100644 --- a/Projects/Feature/MainFeature/Sources/DI/MainComponent.swift +++ b/Projects/Feature/MainFeature/Sources/DI/MainComponent.swift @@ -1,4 +1,3 @@ -import AuthDomainInterface import SwiftUI import BaseFeature import MainFeatureInterface @@ -7,11 +6,12 @@ import StudentDetailFeatureInterface import StudentDomainInterface import FilterFeatureInterface import UserDomainInterface +import MyPageFeatureInterface public protocol MainDependency: Dependency { var studentDomainBuildable: any StudentDomainBuildable { get } - var authDomainBuildable: any AuthDomainBuildable { get } var filterBuildable: any FilterBuildable { get } + var myPageBuildable: any MyPageBuildable { get } var studentDetailBuildable: any StudentDetailBuildable { get } var userDomainBuildable: any UserDomainBuildable { get } } @@ -23,8 +23,6 @@ public final class MainComponent: Component, MainBuildable { model: model, mainDelegate: delegate, fetchStudentListUseCase: dependency.studentDomainBuildable.fetchStudentListUseCase, - logoutUseCase: dependency.authDomainBuildable.logoutUseCase, - withdrawalUseCase: dependency.authDomainBuildable.withdrawalUseCase, loadUserRoleUseCase: dependency.userDomainBuildable.loadUserRoleUseCase ) let container = MVIContainer( @@ -35,7 +33,8 @@ public final class MainComponent: Component, MainBuildable { return MainView( container: container, studentDetailBuildable: dependency.studentDetailBuildable, - filterBuildable: dependency.filterBuildable + filterBuildable: dependency.filterBuildable, + myPageBuildable: dependency.myPageBuildable ) } } diff --git a/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift b/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift index 4cf8c730..9996a545 100644 --- a/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift +++ b/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift @@ -1,4 +1,3 @@ -import AuthDomainInterface import FilterFeatureInterface import Combine import MainFeatureInterface @@ -9,23 +8,17 @@ final class MainIntent: MainIntentProtocol { private weak var model: (any MainActionProtocol)? private weak var mainDelegate: (any MainDelegate)? private let fetchStudentListUseCase: any FetchStudentListUseCase - private let logoutUseCase: any LogoutUseCase - private let withdrawalUseCase: any WithdrawalUseCase private let loadUserRoleUseCase: any LoadUserRoleUseCase init( model: any MainActionProtocol, mainDelegate: any MainDelegate, fetchStudentListUseCase: any FetchStudentListUseCase, - logoutUseCase: any LogoutUseCase, - withdrawalUseCase: any WithdrawalUseCase, loadUserRoleUseCase: any LoadUserRoleUseCase ) { self.mainDelegate = mainDelegate self.model = model self.fetchStudentListUseCase = fetchStudentListUseCase - self.logoutUseCase = logoutUseCase - self.withdrawalUseCase = withdrawalUseCase self.loadUserRoleUseCase = loadUserRoleUseCase model.updateUserRole(role: loadUserRoleUseCase.execute()) @@ -65,14 +58,6 @@ final class MainIntent: MainIntentProtocol { } } - func existActionSheetIsRequired() { - model?.updateIsPresentedExistActionSheet(isPresented: true) - } - - func existActionSheetDismissed() { - model?.updateIsPresentedExistActionSheet(isPresented: false) - } - func filterIsRequired() { model?.updateIsPresentedFilterPage(isPresented: true) } @@ -81,44 +66,12 @@ final class MainIntent: MainIntentProtocol { model?.updateIsPresentedFilterPage(isPresented: false) } - func logoutDialogIsRequired() { - model?.updateIsPresentedLogoutDialog(isPresented: true) - } - - func logoutDialogDismissed() { - model?.updateIsPresentedLogoutDialog(isPresented: false) - } - - func logoutDialogIsComplete() { - Task { - do { - try await logoutUseCase.execute() - mainDelegate?.logout() - } catch { - model?.updateIsError(isError: true) - } - } - model?.updateIsPresentedLogoutDialog(isPresented: false) - } - - func withdrawalDialogIsRequired() { - model?.updateIsPresentedWithdrawalDialog(isPresented: true) - } - - func withdrawalDialogDismissed() { - model?.updateIsPresentedWithdrawalDialog(isPresented: false) + func myPageIsRequired() { + model?.updateIsPresentedMypagePage(isPresented: true) } - func withdrawalDialogIsComplete() { - Task { - do { - try await withdrawalUseCase.execute() - mainDelegate?.logout() - } catch { - model?.updateIsError(isError: true) - } - } - model?.updateIsPresentedWithdrawalDialog(isPresented: false) + func myPageDismissed() { + model?.updateIsPresentedMypagePage(isPresented: false) } func studentDidSelect(userID: String) { @@ -128,6 +81,10 @@ final class MainIntent: MainIntentProtocol { func studentDetailDismissed() { model?.updateSelectedUserID(userID: nil) } + + func logout() { + mainDelegate?.logout() + } } extension MainIntent: FilterDelegate { diff --git a/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift b/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift index 62de4c81..b97f4e5f 100644 --- a/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift +++ b/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift @@ -1,20 +1,15 @@ import Foundation import FilterFeatureInterface +import MyPageFeatureInterface import StudentDomainInterface -protocol MainIntentProtocol: FilterDelegate { +protocol MainIntentProtocol: FilterDelegate, MyPageDelegate { func reachedBottom(page: Int, isLast: Bool, filterOption: FilterOption?) func refresh(filterOption: FilterOption?) - func existActionSheetIsRequired() - func existActionSheetDismissed() func filterIsRequired() func filterDismissed() - func logoutDialogIsRequired() - func logoutDialogDismissed() - func logoutDialogIsComplete() - func withdrawalDialogIsRequired() - func withdrawalDialogDismissed() - func withdrawalDialogIsComplete() + func myPageIsRequired() + func myPageDismissed() func studentDidSelect(userID: String) func studentDetailDismissed() } diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift index e55d0cd5..9a4cc630 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift @@ -7,11 +7,8 @@ final class MainModel: ObservableObject, MainStateProtocol { @Published var page: Int = 1 @Published var totalSize: Int = 0 @Published var isLast: Bool = false - @Published var isError: Bool = false @Published var isRefresh: Bool = false - @Published var isPresentedExistActionSheet: Bool = false - @Published var isPresentedLogoutDialog: Bool = false - @Published var isPresentedWithdrawalDialog: Bool = false + var content: [SingleStudentEntity] { get { _content } set { @@ -29,6 +26,7 @@ final class MainModel: ObservableObject, MainStateProtocol { } @Published var _content: [SingleStudentEntity] = [] @Published var isPresentedFilterPage: Bool = false + @Published var isPresentedMyPage: Bool = false @Published var selectedUserID: String? @Published var currentUserRole: UserRoleType = .guest @Published var filterOption: FilterOption? @@ -36,10 +34,6 @@ final class MainModel: ObservableObject, MainStateProtocol { // swiftlint: enable identifier_name extension MainModel: MainActionProtocol { - func updateIsError(isError: Bool) { - self.isError = isError - } - func updatePage(page: Int) { self.page = page } @@ -52,22 +46,14 @@ extension MainModel: MainActionProtocol { self.isLast = isLast } - func updateIsPresentedExistActionSheet(isPresented: Bool) { - self.isPresentedExistActionSheet = isPresented - } - - func updateIsPresentedLogoutDialog(isPresented: Bool) { - self.isPresentedLogoutDialog = isPresented - } - - func updateIsPresentedWithdrawalDialog(isPresented: Bool) { - self.isPresentedWithdrawalDialog = isPresented - } - func updateIsPresentedFilterPage(isPresented: Bool) { self.isPresentedFilterPage = isPresented } + func updateIsPresentedMypagePage(isPresented: Bool) { + self.isPresentedMyPage = isPresented + } + func appendContent(content: [SingleStudentEntity]) { self.content.append(contentsOf: content) } diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift b/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift index 9bd007f2..90b52996 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift @@ -5,12 +5,9 @@ protocol MainStateProtocol { var page: Int { get } var totalSize: Int { get } var isLast: Bool { get } - var isError: Bool { get } var isRefresh: Bool { get } - var isPresentedExistActionSheet: Bool { get } - var isPresentedLogoutDialog: Bool { get } - var isPresentedWithdrawalDialog: Bool { get } var isPresentedFilterPage: Bool { get } + var isPresentedMyPage: Bool { get } var content: [SingleStudentEntity] { get } var selectedUserID: String? { get } var currentUserRole: UserRoleType { get } @@ -18,14 +15,11 @@ protocol MainStateProtocol { } protocol MainActionProtocol: AnyObject { - func updateIsError(isError: Bool) func updatePage(page: Int) func updateTotalSize(totalSize: Int) func updateIsLast(isLast: Bool) - func updateIsPresentedExistActionSheet(isPresented: Bool) - func updateIsPresentedLogoutDialog(isPresented: Bool) - func updateIsPresentedWithdrawalDialog(isPresented: Bool) func updateIsPresentedFilterPage(isPresented: Bool) + func updateIsPresentedMypagePage(isPresented: Bool) func appendContent(content: [SingleStudentEntity]) func updateContent(content: [SingleStudentEntity]) func updateIsRefresh(isRefresh: Bool) diff --git a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift index 2151e939..bc46dd99 100644 --- a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift +++ b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift @@ -7,6 +7,7 @@ import UIKit import UserDomainInterface import ViewUtil import FilterFeatureInterface +import MyPageFeatureInterface enum MainStudentIDProperty { static let studentScrollToTopID = "STUDENT_SCROLL_TO_TOP" @@ -19,15 +20,18 @@ struct MainView: View { var state: any MainStateProtocol { container.model } private let studentDetailBuildable: any StudentDetailBuildable private let filterBuildable: any FilterBuildable + private let myPageBuildable: any MyPageBuildable init( container: MVIContainer, studentDetailBuildable: any StudentDetailBuildable, - filterBuildable: any FilterBuildable + filterBuildable: any FilterBuildable, + myPageBuildable: any MyPageBuildable ) { self._container = StateObject(wrappedValue: container) self.studentDetailBuildable = studentDetailBuildable self.filterBuildable = filterBuildable + self.myPageBuildable = myPageBuildable } var body: some View { @@ -111,6 +115,13 @@ struct MainView: View { set: { _ in intent.filterDismissed() } ) ) + .navigate( + to: myPageBuildable.makeView(delegate: intent).eraseToAnyView(), + when: Binding( + get: { state.isPresentedMyPage }, + set: { _ in intent.myPageDismissed() } + ) + ) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { @@ -123,7 +134,7 @@ struct MainView: View { SMSIcon(.profile, width: 32, height: 32) .clipShape(Circle()) .onTapGesture { - intent.existActionSheetIsRequired() + intent.myPageIsRequired() } } } @@ -132,77 +143,6 @@ struct MainView: View { SMSIcon(.smsLogo, width: 80, height: 29) } } - .smsBottomSheet(isShowing: Binding( - get: { state.isPresentedExistActionSheet }, - set: { _ in intent.existActionSheetDismissed() } - )) { - VStack(alignment: .leading, spacing: 32) { - Button { - intent.logoutDialogIsRequired() - intent.existActionSheetDismissed() - } label: { - HStack(spacing: 12) { - SMSIcon(.redLogout) - - SMSText("로그아웃", font: .body1) - .foregroundStyle(Color.sms(.system(.error))) - - Spacer() - } - } - - Button { - intent.withdrawalDialogIsRequired() - intent.existActionSheetDismissed() - } label: { - HStack(spacing: 12) { - SMSIcon(.redPerson) - - SMSText("회원탈퇴", font: .body1) - .foregroundStyle(Color.sms(.system(.error))) - - Spacer() - } - } - .conditional(state.currentUserRole != .guest) - } - .padding(.top, 12) - .padding(.horizontal, 20) - } - .animation(.default, value: state.isPresentedExistActionSheet) - .smsAlert( - title: "로그아웃", - description: "정말로 로그아웃 하시겠습니까?", - isShowing: - Binding( - get: { state.isPresentedLogoutDialog }, - set: { _ in intent.logoutDialogDismissed() } - ), - alertActions: [ - .init(text: "확인", style: .outline) { - intent.logoutDialogIsComplete() - }, - .init(text: "취소") { - intent.logoutDialogDismissed() - } - ] - ) - .smsAlert( - title: "회원탈퇴", - description: "정말로 회원탈퇴 하시겠습니까?", - isShowing: - Binding( - get: { state.isPresentedWithdrawalDialog }, - set: { _ in intent.withdrawalDialogDismissed() } - ), - alertActions: [ - .init(text: "확인", style: .outline) { - intent.withdrawalDialogIsComplete() - }, - .init(text: "취소") { - intent.withdrawalDialogDismissed() - } - ]) } .navigationViewStyle(.stack) } diff --git a/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift b/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift index affa584a..578302d0 100644 --- a/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift +++ b/Projects/Feature/MyPageFeature/Interface/MyPageBuildable.swift @@ -2,5 +2,5 @@ import SwiftUI public protocol MyPageBuildable { associatedtype ViewType: View - func makeView() -> ViewType + func makeView(delegate: any MyPageDelegate) -> ViewType } diff --git a/Projects/Feature/MyPageFeature/Interface/MyPageDelegate.swift b/Projects/Feature/MyPageFeature/Interface/MyPageDelegate.swift new file mode 100644 index 00000000..cc674d30 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Interface/MyPageDelegate.swift @@ -0,0 +1,3 @@ +public protocol MyPageDelegate: AnyObject { + func logout() +} diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index 87668c49..0cb0ae5e 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -10,5 +10,6 @@ let project = Project.makeModule( .Feature.BaseFeature, .Domain.UserDomainInterface, .Domain.StudentDomainInterface, + .Domain.AuthDomainInterface ] ) diff --git a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift index a69d6df3..12a1097f 100644 --- a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift +++ b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift @@ -1,3 +1,4 @@ +import AuthDomainInterface import BaseFeature import MyPageFeatureInterface import NeedleFoundation @@ -5,15 +6,19 @@ import SwiftUI import UserDomainInterface public protocol MyPageDependency: Dependency { - var fetchMyProfileUseCase: any FetchMyProfileUseCase { get } + var userDomainBuildable: any UserDomainBuildable { get } + var authDomainBuildable: any AuthDomainBuildable { get } } public final class MyPageComponent: Component, MyPageBuildable { - public func makeView() -> some View { + public func makeView(delegate: any MyPageDelegate) -> some View { let model = MyPageModel() let intent = MyPageIntent( model: model, - fetchMyProfileUseCase: dependency.fetchMyProfileUseCase + myPageDelegate: delegate, + fetchMyProfileUseCase: dependency.userDomainBuildable.fetchMyProfileUseCase, + logoutUseCase: dependency.authDomainBuildable.logoutUseCase, + withdrawalUseCase: dependency.authDomainBuildable.withdrawalUseCase ) let container = MVIContainer( intent: intent as MyPageIntentProtocol, diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 2bac7ebe..9504fed9 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -1,15 +1,75 @@ import Foundation import UserDomainInterface +import AuthDomainInterface +import MyPageFeatureInterface final class MyPageIntent: MyPageIntentProtocol { weak var model: (any MyPageActionProtocol)? + private weak var myPageDelegate: (any MyPageDelegate)? private let fetchMyProfileUseCase: any FetchMyProfileUseCase + private let logoutUseCase: any LogoutUseCase + private let withdrawalUseCase: any WithdrawalUseCase init( model: any MyPageActionProtocol, - fetchMyProfileUseCase: any FetchMyProfileUseCase + myPageDelegate: any MyPageDelegate, + fetchMyProfileUseCase: any FetchMyProfileUseCase, + logoutUseCase: any LogoutUseCase, + withdrawalUseCase: any WithdrawalUseCase ) { self.model = model + self.myPageDelegate = myPageDelegate self.fetchMyProfileUseCase = fetchMyProfileUseCase + self.logoutUseCase = logoutUseCase + self.withdrawalUseCase = withdrawalUseCase } + + func existActionSheetIsRequired() { + model?.updateIsPresentedExistActionSheet(isPresented: true) + } + + func existActionSheetDismissed() { + model?.updateIsPresentedExistActionSheet(isPresented: false) + } + + func logoutDialogIsRequired() { + model?.updateIsPresentedLogoutDialog(isPresented: true) + } + + func logoutDialogDismissed() { + model?.updateIsPresentedLogoutDialog(isPresented: false) + } + + func logoutDialogIsComplete() { + Task { + do { + try await logoutUseCase.execute() + myPageDelegate?.logout() + } catch { + model?.updateIsError(isError: true) + } + } + model?.updateIsPresentedLogoutDialog(isPresented: false) + } + + func withdrawalDialogIsRequired() { + model?.updateIsPresentedWithdrawalDialog(isPresented: true) + } + + func withdrawalDialogDismissed() { + model?.updateIsPresentedWithdrawalDialog(isPresented: false) + } + + func withdrawalDialogIsComplete() { + Task { + do { + try await withdrawalUseCase.execute() + myPageDelegate?.logout() + } catch { + model?.updateIsError(isError: true) + } + } + model?.updateIsPresentedWithdrawalDialog(isPresented: false) + } + } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 96fabd27..a457ab90 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -8,4 +8,13 @@ protocol MyPageIntentProtocol: MyPageCertificateIntentProtocol, MyPageLanguageIntentProtocol, MyPageProjectIntentProtocol, - MyPagePrizeIntentProtocol {} + MyPagePrizeIntentProtocol { + func existActionSheetIsRequired() + func existActionSheetDismissed() + func logoutDialogIsRequired() + func logoutDialogDismissed() + func logoutDialogIsComplete() + func withdrawalDialogIsRequired() + func withdrawalDialogDismissed() + func withdrawalDialogIsComplete() +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index 35575d30..91bfea4e 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -2,6 +2,12 @@ import Foundation import StudentDomainInterface final class MyPageModel: ObservableObject, MyPageStateProtocol { + // MARK: MyPage + @Published var isError: Bool = false + @Published var isPresentedExistActionSheet: Bool = false + @Published var isPresentedLogoutDialog: Bool = false + @Published var isPresentedWithdrawalDialog: Bool = false + // MARK: Profile @Published var profileURL: String = "" @Published var introduce: String = "" @@ -54,4 +60,20 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { @Published var focusedPrizeIndex: Int = 0 } -extension MyPageModel: MyPageActionProtocol {} +extension MyPageModel: MyPageActionProtocol { + func updateIsError(isError: Bool) { + self.isError = isError + } + + func updateIsPresentedExistActionSheet(isPresented: Bool) { + self.isPresentedExistActionSheet = isPresented + } + + func updateIsPresentedLogoutDialog(isPresented: Bool) { + self.isPresentedLogoutDialog = isPresented + } + + func updateIsPresentedWithdrawalDialog(isPresented: Bool) { + self.isPresentedWithdrawalDialog = isPresented + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index e792fd3b..3dd410be 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -6,7 +6,12 @@ protocol MyPageStateProtocol: MyPageCertificateStateProtocol, MyPageLanguageInfoStateProtocol, MyPageProjectStateProtocol, - MyPagePrizeStateProtocol {} + MyPagePrizeStateProtocol { + var isError: Bool { get } + var isPresentedExistActionSheet: Bool { get } + var isPresentedLogoutDialog: Bool { get } + var isPresentedWithdrawalDialog: Bool { get } +} protocol MyPageActionProtocol: AnyObject, @@ -17,4 +22,9 @@ protocol MyPageActionProtocol: MyPageCertificateActionProtocol, MyPageLanguageInfoActionProtocol, MyPageProjectActionProtocol, - MyPagePrizeActionProtocol {} + MyPagePrizeActionProtocol { + func updateIsError(isError: Bool) + func updateIsPresentedExistActionSheet(isPresented: Bool) + func updateIsPresentedLogoutDialog(isPresented: Bool) + func updateIsPresentedWithdrawalDialog(isPresented: Bool) +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index a8a70329..5922f2cf 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -1,14 +1,101 @@ import BaseFeature import SwiftUI +import DesignSystem struct MyPageView: View { + @Environment(\.dismiss) var dismiss @StateObject var container: MVIContainer var intent: any MyPageIntentProtocol { container.intent } var state: any MyPageStateProtocol { container.model } var body: some View { ScrollView { - } + .smsBottomSheet(isShowing: Binding( + get: { state.isPresentedExistActionSheet }, + set: { _ in intent.existActionSheetDismissed() } + )) { + VStack(alignment: .leading, spacing: 32) { + Button { + intent.logoutDialogIsRequired() + intent.existActionSheetDismissed() + } label: { + HStack(spacing: 12) { + SMSIcon(.redLogout) + + SMSText("로그아웃", font: .body1) + .foregroundStyle(Color.sms(.system(.error))) + + Spacer() + } + } + + Button { + intent.withdrawalDialogIsRequired() + intent.existActionSheetDismissed() + } label: { + HStack(spacing: 12) { + SMSIcon(.redPerson) + + SMSText("회원탈퇴", font: .body1) + .foregroundStyle(Color.sms(.system(.error))) + + Spacer() + } + } +// .conditional(state.currentUserRole != .guest) + } + .padding(.top, 12) + .padding(.horizontal, 20) + } + .animation(.default, value: state.isPresentedExistActionSheet) + .smsAlert( + title: "로그아웃", + description: "정말로 로그아웃 하시겠습니까?", + isShowing: + Binding( + get: { state.isPresentedLogoutDialog }, + set: { _ in intent.logoutDialogDismissed() } + ), + alertActions: [ + .init(text: "확인", style: .outline) { + intent.logoutDialogIsComplete() + }, + .init(text: "취소") { + intent.logoutDialogDismissed() + } + ] + ) + .smsAlert( + title: "회원탈퇴", + description: "정말로 회원탈퇴 하시겠습니까?", + isShowing: + Binding( + get: { state.isPresentedWithdrawalDialog }, + set: { _ in intent.withdrawalDialogDismissed() } + ), + alertActions: [ + .init(text: "확인", style: .outline) { + intent.withdrawalDialogIsComplete() + }, + .init(text: "취소") { + intent.withdrawalDialogDismissed() + } + ] + ) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SMSIcon(.logoutLine) + .onTapGesture { + intent.existActionSheetIsRequired() + } + } + } + .hideKeyboardWhenTap() + .navigationTitle("마이페이지") + .smsBackButton( + dismiss: dismiss + ) + .navigationBarTitleDisplayMode(.inline) } } From 2e82c28535061eaa184f591546b4f22b54f46d6a Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 16:15:58 +0900 Subject: [PATCH 18/62] :sparkles: :: [#220] MyPageFeature / MyPageProfileView --- .../Testing/UseCase/LogoutUseCaseSpy.swift | 11 ++ .../UseCase/WithdrawalUseCaseSpy.swift | 11 ++ .../UseCase/FetchMyProfileUseCaseSpy.swift | 41 +++++ .../Demo/Sources/AppDelegate.swift | 45 +++-- Projects/Feature/MyPageFeature/Project.swift | 5 + .../Sources/Scene/MyPageProfileView.swift | 160 ++++++++++++++++++ .../Sources/Scene/MyPageView.swift | 23 ++- 7 files changed, 273 insertions(+), 23 deletions(-) create mode 100644 Projects/Domain/AuthDomain/Testing/UseCase/LogoutUseCaseSpy.swift create mode 100644 Projects/Domain/AuthDomain/Testing/UseCase/WithdrawalUseCaseSpy.swift create mode 100644 Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift diff --git a/Projects/Domain/AuthDomain/Testing/UseCase/LogoutUseCaseSpy.swift b/Projects/Domain/AuthDomain/Testing/UseCase/LogoutUseCaseSpy.swift new file mode 100644 index 00000000..eb7cef4f --- /dev/null +++ b/Projects/Domain/AuthDomain/Testing/UseCase/LogoutUseCaseSpy.swift @@ -0,0 +1,11 @@ +import AuthDomainInterface + +final class LogoutUseCaseSpy: LogoutUseCase { + var executeCallCount = 0 + var executeHandler: () async throws -> Void = {} + + func execute() async throws { + executeCallCount += 1 + try await executeHandler() + } +} diff --git a/Projects/Domain/AuthDomain/Testing/UseCase/WithdrawalUseCaseSpy.swift b/Projects/Domain/AuthDomain/Testing/UseCase/WithdrawalUseCaseSpy.swift new file mode 100644 index 00000000..b9cff4ac --- /dev/null +++ b/Projects/Domain/AuthDomain/Testing/UseCase/WithdrawalUseCaseSpy.swift @@ -0,0 +1,11 @@ +import AuthDomainInterface + +final class WithdrawalUseCaseSpy: WithdrawalUseCase { + var executeCallCount = 0 + var executeHandler: () async throws -> Void = {} + + func execute() async throws { + executeCallCount += 1 + try await executeHandler() + } +} diff --git a/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift b/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift new file mode 100644 index 00000000..8dd138ad --- /dev/null +++ b/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift @@ -0,0 +1,41 @@ +import UserDomainInterface + +final class FetchMyProfileUseCaseSpy: FetchMyProfileUseCase { + var executeCallCount = 0 + var executeHandler: () async throws -> MyPageEntity = { + .init( + name: "name", + introduce: "intro", + portfolioURL: "https://github.com/baekteun", + grade: 3, + classNum: 2, + number: 18, + department: .software, + major: "iOS", + profileImageURL: "https://avatars.githubusercontent.com/u/74440939?v=4", + contactEmail: "baegteun@gmail.com", + gsmAuthenticationScore: 990, + formOfEmployment: .fullTime, + regions: ["Seoul"], + militaryService: .hope, + salary: 9999, + languageCertificates: [ + .init(name: "한국어", score: "원어민") + ], + certificates: [ + "정보처리산업기사" + ], + techStacks: [ + "Swift", + "Tuist", + "MicroFeatures" + ] + ) + } + + func execute() async throws -> MyPageEntity { + executeCallCount += 1 + return try await executeHandler() + } + +} diff --git a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift index ef2bae04..c55ebe5b 100644 --- a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift @@ -1,19 +1,34 @@ -import UIKit +import MyPageFeatureInterface +import SwiftUI +@testable import AuthDomainTesting +@testable import MyPageFeature +@testable import UserDomainTesting +final class DummyMyPageDelegate: MyPageDelegate { + func logout() {} +} @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 MyPageDemoApp: App { + var body: some Scene { + WindowGroup { + let model = MyPageModel() + let fetchMyProfileUseCase = FetchMyProfileUseCaseSpy() + let logoutUseCase = LogoutUseCaseSpy() + let withdrawalUseCase = WithdrawalUseCaseSpy() + let intent = MyPageIntent( + model: model, + myPageDelegate: DummyMyPageDelegate(), + fetchMyProfileUseCase: fetchMyProfileUseCase, + logoutUseCase: logoutUseCase, + withdrawalUseCase: withdrawalUseCase + ) + MyPageView( + container: .init( + intent: intent as MyPageIntentProtocol, + model: model as MyPageStateProtocol, + modelChangePublisher: model.objectWillChange + ) + ) + } } } diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index 0cb0ae5e..1414cf32 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -11,5 +11,10 @@ let project = Project.makeModule( .Domain.UserDomainInterface, .Domain.StudentDomainInterface, .Domain.AuthDomainInterface + ], + demoDependencies: [ + .Domain.UserDomainTesting, + .Domain.AuthDomainTesting, + .Domain.StudentDomainTesting ] ) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift new file mode 100644 index 00000000..91f2ea17 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift @@ -0,0 +1,160 @@ +import DesignSystem +import NukeUI +import SwiftUI +import TagLayoutView +import ViewUtil + +struct MyPageProfileView: View { + let intent: MyPageProfileIntentProtocol + let state: MyPageProfileStateProtocol + let geometry: GeometryProxy + + init( + intent: MyPageProfileIntentProtocol, + state: MyPageProfileStateProtocol, + geometry: GeometryProxy + ) { + self.intent = intent + self.state = state + self.geometry = geometry + } + + var body: some View { + Section { + VStack(spacing: 32) { + VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 8) { + ZStack(alignment: .bottomTrailing) { + if let profileImageURL = URL(string: state.profileURL) { + LazyImage(url: profileImageURL) { image in + if let image = image.image { + image.resizable() + .frame(width: 100, height: 100) + } else { + Color.sms(.neutral(.n30)) + .frame(width: 100, height: 100) + } + } + } else { + SMSIcon(.profile, width: 100, height: 100) + } + + SMSIcon(.profileSmallPlus) + .overlay { + RoundedRectangle(cornerRadius: 7) + .strokeBorder(Color.sms(.system(.white)), lineWidth: 4) + } + .offset(x: 5, y: 4) + } + .buttonWrapper { + withAnimation { + intent.imageMethodPickerIsRequired() + } + } + .titleWrapper("사진") + } + + SMSTextField( + "1줄 자기소개 입력", + text: Binding(get: { state.introduce }, set: intent.updateIntroduce(introduce:)), + errorText: "1글자에서 50글자 사이로 입력해주세요" + ) + .titleWrapper("자기소개") + + SMSTextField( + "공개용 이메일 입력", + text: Binding(get: { state.email }, set: intent.updateEmail(email:)), + errorText: "이메일 형식에 맞게 입력해주세요" + ) + .keyboardType(.emailAddress) + .titleWrapper("이메일") + + SMSTextField( + state.isSelfEntering ? "전공 분야 입력" : "전공 분야 선택", + text: Binding(get: { state.major }, set: intent.updateMajor(major:)), + errorText: "전공 분야를 선택해주세요", + isOnClear: false + ) + .disabled(!state.isSelfEntering) + .overlay(alignment: .topTrailing) { + SMSIcon(.downChevron) + .padding([.top, .trailing], 12) + } + .simultaneousGesture( + TapGesture() + .onEnded { + intent.majorSheetIsRequired() + intent.deActiveSelfEntering() + } + ) + .titleWrapper("분야") + + SMSTextField( + "E.g. https://github.com", + text: Binding( + get: { state.portfolioURL }, + set: intent.updatePortfolioURL(portfolioURL:) + ), + errorText: "URL 형식에 맞게 입력해주세요" + ) { + intent.techStackAppendIsRequired() + } + .keyboardType(.URL) + .titleWrapper("포트폴리오 URL") + + 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 { + intent.techStackAppendIsRequired() + } + + TagLayoutView( + state.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.removeTechStack(techStack: techStack) + } + } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background(Color.sms(.neutral(.n10))) + .fixedSize() + .clipShape(RoundedRectangle(cornerRadius: 4)) + } + } + .titleWrapper("세부스택 (최대 5개)") + } + } + } header: { + SMSText("프로필", font: .title1) + .aligned(.leading) + .padding(.vertical) + .background { + Color.sms(.system(.white)) + } + } + .padding([.horizontal], 20) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 5922f2cf..9cc68694 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -9,7 +9,17 @@ struct MyPageView: View { var state: any MyPageStateProtocol { container.model } var body: some View { - ScrollView { + GeometryReader { geometry in + VStack { + Spacer() + .frame(height: 1) + + ScrollView { + LazyVStack(pinnedViews: [.sectionHeaders]) { + MyPageProfileView(intent: intent, state: state, geometry: geometry) + } + } + } } .smsBottomSheet(isShowing: Binding( get: { state.isPresentedExistActionSheet }, @@ -36,14 +46,13 @@ struct MyPageView: View { } label: { HStack(spacing: 12) { SMSIcon(.redPerson) - + SMSText("회원탈퇴", font: .body1) .foregroundStyle(Color.sms(.system(.error))) - + Spacer() } } -// .conditional(state.currentUserRole != .guest) } .padding(.top, 12) .padding(.horizontal, 20) @@ -52,8 +61,7 @@ struct MyPageView: View { .smsAlert( title: "로그아웃", description: "정말로 로그아웃 하시겠습니까?", - isShowing: - Binding( + isShowing: Binding( get: { state.isPresentedLogoutDialog }, set: { _ in intent.logoutDialogDismissed() } ), @@ -69,8 +77,7 @@ struct MyPageView: View { .smsAlert( title: "회원탈퇴", description: "정말로 회원탈퇴 하시겠습니까?", - isShowing: - Binding( + isShowing: Binding( get: { state.isPresentedWithdrawalDialog }, set: { _ in intent.withdrawalDialogDismissed() } ), From c389698f5136bc26996f4c8050338b58b3481616 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 16:22:54 +0900 Subject: [PATCH 19/62] :sparkles: :: [#220] MyPageFeature / MyPageSchoolLifeView --- .../Sources/Scene/MyPageSchoolLifeView.swift | 34 +++++++++++++++++++ .../Sources/Scene/MyPageView.swift | 12 ++++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift new file mode 100644 index 00000000..fba73943 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift @@ -0,0 +1,34 @@ +import DesignSystem +import SwiftUI + +struct MyPageSchoolLifeView: View { + let intent: MyPageSchoolLifeIntentProtocol + let state: MyPageSchoolLifeStateProtocol + + var body: some View { + Section { + VStack(spacing: 32) { + VStack(spacing: 24) { + SMSTextField( + "인증제 점수 입력", + text: Binding( + get: { state.gsmScore }, + set: intent.updateGSMScore(gsmScore:) + ), + errorText: "인증제 점수를 입력해주세요" + ) + .keyboardType(.numberPad) + .titleWrapper("인증제 점수") + } + } + } header: { + SMSText("학교 생활", font: .title1) + .aligned(.leading) + .padding(.vertical) + .background { + Color.sms(.system(.white)) + } + } + .padding([.horizontal], 20) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 9cc68694..a302e700 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -16,7 +16,17 @@ struct MyPageView: View { ScrollView { LazyVStack(pinnedViews: [.sectionHeaders]) { - MyPageProfileView(intent: intent, state: state, geometry: geometry) + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageProfileView(intent: intent, state: state, geometry: geometry) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageSchoolLifeView(intent: intent, state: state) + } } } } From 358991fbe67debfcefd2578eda755c6bd8e30050 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 16:44:33 +0900 Subject: [PATCH 20/62] :sparkles: :: [#220] MyPageFeature / MyPageWorkInfoView --- .../Sources/Scene/MyPageProfileView.swift | 2 +- .../Sources/Scene/MyPageSchoolLifeView.swift | 2 +- .../Sources/Scene/MyPageView.swift | 11 +++ .../Sources/Scene/MyPageWorkInfoView.swift | 96 +++++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift index 91f2ea17..88112b0a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift @@ -155,6 +155,6 @@ struct MyPageProfileView: View { Color.sms(.system(.white)) } } - .padding([.horizontal], 20) + .padding(.horizontal, 20) } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift index fba73943..6bae8a1a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift @@ -29,6 +29,6 @@ struct MyPageSchoolLifeView: View { Color.sms(.system(.white)) } } - .padding([.horizontal], 20) + .padding(.horizontal, 20) } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index a302e700..32e22924 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -26,6 +26,17 @@ struct MyPageView: View { .padding(.vertical, 16) MyPageSchoolLifeView(intent: intent, state: state) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageWorkInfoView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift new file mode 100644 index 00000000..7d3b7b48 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift @@ -0,0 +1,96 @@ +import BaseFeature +import DesignSystem +import SwiftUI +import ViewUtil + +struct MyPageWorkInfoView: View { + @StateObject var container: MVIContainer + var intent: MyPageWorkInfoIntentProtocol { container.intent } + var state: MyPageWorkInfoStateProtocol { container.model } + + init(container: MVIContainer) { + self._container = StateObject(wrappedValue: container) + } + + var body: some View { + Section { + VStack(spacing: 32) { + VStack(spacing: 24) { + SMSTextField( + "정규직", + text: Binding( + get: { state.formOfEmployment.display() }, + set: { _ in } + ), + isOnClear: false + ) + .disabled(true) + .overlay(alignment: .trailing) { + SMSIcon(.downChevron) + .padding(.trailing, 12) + } + .titleWrapper("희망 고용 형태") + .onTapGesture { + intent.formOfEmployeementSheetIsRequired() + } + + VStack(alignment: .leading, spacing: 4) { + SMSTextField( + "희망 연봉 (10,000원 단위)", + text: Binding( + get: { state.salary }, + set: intent.updateSalary(salary:) + ) + ) + .keyboardType(.numberPad) + + Text(state.salaryDisplay) + .smsFont(.caption1, color: .neutral(.n30)) + } + .titleWrapper("희망 연봉") + + workRegionList() + } + } + } header: { + SMSText("근무 조건", font: .title1) + .aligned(.leading) + .padding(.vertical) + .background { + Color.sms(.system(.white)) + } + } + .padding(.horizontal, 20) + } + + @ViewBuilder + func workRegionList() -> some View { + VStack(spacing: 8) { + ForEach(state.workRegionList.indices, id: \.self) { index in + HStack(spacing: 16) { + SMSTextField( + "근무 희망 지역 입력", + text: Binding( + get: { state.workRegionList[safe: index] ?? "" }, + set: { intent.updateWorkRegion(region: $0, at: index) } + ) + ) + + Button { + intent.deleteWorkRegion(at: index) + } label: { + SMSIcon(.trash) + } + } + } + + SMSChip("추가") { + intent.appendWorkRegion() + } + .aligned(.leading) + } + .titleWrapper("근무 지역") + .aligned(.leading) + .animation(.default, value: state.workRegionList.count) + } +} From 1519ba324fd7b00632ab42ee78ed32b3c0344f95 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 17:25:29 +0900 Subject: [PATCH 21/62] :sparkles: :: MyPageMilitaryView --- .../Sources/Scene/MyPageMilitaryView.swift | 40 ++++ .../Sources/Scene/MyPageProfileView.swift | 212 +++++++++--------- .../Sources/Scene/MyPageSchoolLifeView.swift | 24 +- .../Sources/Scene/MyPageView.swift | 5 + .../Sources/Scene/MyPageWorkInfoView.swift | 62 +++-- 5 files changed, 191 insertions(+), 152 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift new file mode 100644 index 00000000..d4514b6c --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift @@ -0,0 +1,40 @@ +import DesignSystem +import SwiftUI +import ViewUtil + +struct MyPageMilitaryView: View { + let intent: MyPageMilitaryIntentProtocol + let state: MyPageMilitaryStateProtocol + + var body: some View { + Section { + VStack(spacing: 24) { + SMSTextField( + "병특 희망", + text: Binding( + get: { state.selectedMilitaryServiceType.display() }, + set: { _ in } + ), + isOnClear: false + ) + .disabled(true) + .overlay(alignment: .trailing) { + SMSIcon(.downChevron) + .padding(.trailing, 12) + } + .titleWrapper("병특 희망 여부") + .onTapGesture { + intent.militarySheetIsRequired() + } + } + } header: { + SMSText("학교 생활", font: .title1) + .aligned(.leading) + .padding(.vertical) + .background { + Color.sms(.system(.white)) + } + } + .padding(.horizontal, 20) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift index 88112b0a..5cc1922b 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift @@ -21,131 +21,129 @@ struct MyPageProfileView: View { var body: some View { Section { - VStack(spacing: 32) { - VStack(alignment: .leading, spacing: 24) { - VStack(alignment: .leading, spacing: 8) { - ZStack(alignment: .bottomTrailing) { - if let profileImageURL = URL(string: state.profileURL) { - LazyImage(url: profileImageURL) { image in - if let image = image.image { - image.resizable() - .frame(width: 100, height: 100) - } else { - Color.sms(.neutral(.n30)) - .frame(width: 100, height: 100) - } + VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 8) { + ZStack(alignment: .bottomTrailing) { + if let profileImageURL = URL(string: state.profileURL) { + LazyImage(url: profileImageURL) { image in + if let image = image.image { + image.resizable() + .frame(width: 100, height: 100) + } else { + Color.sms(.neutral(.n30)) + .frame(width: 100, height: 100) } - } else { - SMSIcon(.profile, width: 100, height: 100) } - - SMSIcon(.profileSmallPlus) - .overlay { - RoundedRectangle(cornerRadius: 7) - .strokeBorder(Color.sms(.system(.white)), lineWidth: 4) - } - .offset(x: 5, y: 4) + } else { + SMSIcon(.profile, width: 100, height: 100) } - .buttonWrapper { - withAnimation { - intent.imageMethodPickerIsRequired() + + SMSIcon(.profileSmallPlus) + .overlay { + RoundedRectangle(cornerRadius: 7) + .strokeBorder(Color.sms(.system(.white)), lineWidth: 4) } + .offset(x: 5, y: 4) + } + .buttonWrapper { + withAnimation { + intent.imageMethodPickerIsRequired() } - .titleWrapper("사진") } + .titleWrapper("사진") + } - SMSTextField( - "1줄 자기소개 입력", - text: Binding(get: { state.introduce }, set: intent.updateIntroduce(introduce:)), - errorText: "1글자에서 50글자 사이로 입력해주세요" - ) - .titleWrapper("자기소개") + SMSTextField( + "1줄 자기소개 입력", + text: Binding(get: { state.introduce }, set: intent.updateIntroduce(introduce:)), + errorText: "1글자에서 50글자 사이로 입력해주세요" + ) + .titleWrapper("자기소개") - SMSTextField( - "공개용 이메일 입력", - text: Binding(get: { state.email }, set: intent.updateEmail(email:)), - errorText: "이메일 형식에 맞게 입력해주세요" - ) - .keyboardType(.emailAddress) - .titleWrapper("이메일") + SMSTextField( + "공개용 이메일 입력", + text: Binding(get: { state.email }, set: intent.updateEmail(email:)), + errorText: "이메일 형식에 맞게 입력해주세요" + ) + .keyboardType(.emailAddress) + .titleWrapper("이메일") - SMSTextField( - state.isSelfEntering ? "전공 분야 입력" : "전공 분야 선택", - text: Binding(get: { state.major }, set: intent.updateMajor(major:)), - errorText: "전공 분야를 선택해주세요", - isOnClear: false - ) - .disabled(!state.isSelfEntering) - .overlay(alignment: .topTrailing) { - SMSIcon(.downChevron) - .padding([.top, .trailing], 12) - } - .simultaneousGesture( - TapGesture() - .onEnded { - intent.majorSheetIsRequired() - intent.deActiveSelfEntering() - } - ) - .titleWrapper("분야") + SMSTextField( + state.isSelfEntering ? "전공 분야 입력" : "전공 분야 선택", + text: Binding(get: { state.major }, set: intent.updateMajor(major:)), + errorText: "전공 분야를 선택해주세요", + isOnClear: false + ) + .disabled(!state.isSelfEntering) + .overlay(alignment: .topTrailing) { + SMSIcon(.downChevron) + .padding([.top, .trailing], 12) + } + .simultaneousGesture( + TapGesture() + .onEnded { + intent.majorSheetIsRequired() + intent.deActiveSelfEntering() + } + ) + .titleWrapper("분야") - SMSTextField( - "E.g. https://github.com", - text: Binding( - get: { state.portfolioURL }, - set: intent.updatePortfolioURL(portfolioURL:) - ), - errorText: "URL 형식에 맞게 입력해주세요" - ) { - intent.techStackAppendIsRequired() - } - .keyboardType(.URL) - .titleWrapper("포트폴리오 URL") + SMSTextField( + "E.g. https://github.com", + text: Binding( + get: { state.portfolioURL }, + set: intent.updatePortfolioURL(portfolioURL:) + ), + errorText: "URL 형식에 맞게 입력해주세요" + ) { + intent.techStackAppendIsRequired() + } + .keyboardType(.URL) + .titleWrapper("포트폴리오 URL") - VStack(spacing: 8) { - HStack(spacing: 8) { - SMSIcon(.magnifyingglass) + VStack(spacing: 8) { + HStack(spacing: 8) { + SMSIcon(.magnifyingglass) - SMSText("찾고 싶은 세부 스택 입력", font: .body1) - .foregroundColor(.sms(.neutral(.n30))) + SMSText("찾고 싶은 세부 스택 입력", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) - Spacer() - } - .padding(12) - .background { - Color.sms(.neutral(.n10)) - } - .clipShape(RoundedRectangle(cornerRadius: 8)) - .buttonWrapper { - intent.techStackAppendIsRequired() - } + Spacer() + } + .padding(12) + .background { + Color.sms(.neutral(.n10)) + } + .clipShape(RoundedRectangle(cornerRadius: 8)) + .buttonWrapper { + intent.techStackAppendIsRequired() + } - TagLayoutView( - state.techStacks, - tagFont: UIFont( - font: DesignSystemFontFamily.Pretendard.regular, - size: 24 - ) ?? .init(), - padding: 20, - parentWidth: geometry.size.width - ) { techStack in - HStack { - SMSText(techStack, font: .body2) + TagLayoutView( + state.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.removeTechStack(techStack: techStack) - } - } - .padding(.horizontal, 12) - .padding(.vertical, 10) - .background(Color.sms(.neutral(.n10))) - .fixedSize() - .clipShape(RoundedRectangle(cornerRadius: 4)) + SMSIcon(.xmarkOutline, width: 20, height: 20) + .buttonWrapper { + intent.removeTechStack(techStack: techStack) + } } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background(Color.sms(.neutral(.n10))) + .fixedSize() + .clipShape(RoundedRectangle(cornerRadius: 4)) } - .titleWrapper("세부스택 (최대 5개)") } + .titleWrapper("세부스택 (최대 5개)") } } header: { SMSText("프로필", font: .title1) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift index 6bae8a1a..12f12981 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift @@ -7,19 +7,17 @@ struct MyPageSchoolLifeView: View { var body: some View { Section { - VStack(spacing: 32) { - VStack(spacing: 24) { - SMSTextField( - "인증제 점수 입력", - text: Binding( - get: { state.gsmScore }, - set: intent.updateGSMScore(gsmScore:) - ), - errorText: "인증제 점수를 입력해주세요" - ) - .keyboardType(.numberPad) - .titleWrapper("인증제 점수") - } + VStack(spacing: 24) { + SMSTextField( + "인증제 점수 입력", + text: Binding( + get: { state.gsmScore }, + set: intent.updateGSMScore(gsmScore:) + ), + errorText: "인증제 점수를 입력해주세요" + ) + .keyboardType(.numberPad) + .titleWrapper("인증제 점수") } } header: { SMSText("학교 생활", font: .title1) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 32e22924..f038a39b 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -37,6 +37,11 @@ struct MyPageView: View { modelChangePublisher: container.objectWillChange ) ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageMilitaryView(intent: intent, state: state) } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift index 7d3b7b48..8175e8bb 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift @@ -14,43 +14,41 @@ struct MyPageWorkInfoView: View { var body: some View { Section { - VStack(spacing: 32) { - VStack(spacing: 24) { + VStack(spacing: 24) { + SMSTextField( + "정규직", + text: Binding( + get: { state.formOfEmployment.display() }, + set: { _ in } + ), + isOnClear: false + ) + .disabled(true) + .overlay(alignment: .trailing) { + SMSIcon(.downChevron) + .padding(.trailing, 12) + } + .titleWrapper("희망 고용 형태") + .onTapGesture { + intent.formOfEmployeementSheetIsRequired() + } + + VStack(alignment: .leading, spacing: 4) { SMSTextField( - "정규직", + "희망 연봉 (10,000원 단위)", text: Binding( - get: { state.formOfEmployment.display() }, - set: { _ in } - ), - isOnClear: false - ) - .disabled(true) - .overlay(alignment: .trailing) { - SMSIcon(.downChevron) - .padding(.trailing, 12) - } - .titleWrapper("희망 고용 형태") - .onTapGesture { - intent.formOfEmployeementSheetIsRequired() - } - - VStack(alignment: .leading, spacing: 4) { - SMSTextField( - "희망 연봉 (10,000원 단위)", - text: Binding( - get: { state.salary }, - set: intent.updateSalary(salary:) - ) + get: { state.salary }, + set: intent.updateSalary(salary:) ) - .keyboardType(.numberPad) - - Text(state.salaryDisplay) - .smsFont(.caption1, color: .neutral(.n30)) - } - .titleWrapper("희망 연봉") + ) + .keyboardType(.numberPad) - workRegionList() + Text(state.salaryDisplay) + .smsFont(.caption1, color: .neutral(.n30)) } + .titleWrapper("희망 연봉") + + workRegionList() } } header: { SMSText("근무 조건", font: .title1) From a4e07e274a6ca4280c2cd2edb36e51debcbd533d Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 17:35:13 +0900 Subject: [PATCH 22/62] :sparkles: :: [#220] MyPageFeature / MyPageCertificateView --- .../Sources/Scene/MyPageCertificateView.swift | 56 +++++++++++++++++++ .../Sources/Scene/MyPageMilitaryView.swift | 7 +-- .../Sources/Scene/MyPageProfileView.swift | 7 +-- .../Sources/Scene/MyPageSchoolLifeView.swift | 7 +-- .../Sources/Scene/MyPageView.swift | 13 +++++ .../Sources/Scene/MyPageWorkInfoView.swift | 7 +-- .../Scene/View/SectionHeaderView.swift | 15 +++++ 7 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageCertificateView.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/View/SectionHeaderView.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageCertificateView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageCertificateView.swift new file mode 100644 index 00000000..a65e44c2 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageCertificateView.swift @@ -0,0 +1,56 @@ +import BaseFeature +import DesignSystem +import SwiftUI +import ViewUtil + +struct MyPageCertificateView: View { + @StateObject var container: MVIContainer + var intent: MyPageCertificateIntentProtocol { container.intent } + var state: MyPageCertificateStateProtocol { container.model } + + init(container: MVIContainer) { + self._container = StateObject(wrappedValue: container) + } + + var body: some View { + Section { + VStack(spacing: 8) { + certificateListView() + .titleWrapper("자격증") + .aligned(.leading) + + SMSChip("추가") { + intent.certificateAppendButtonDidTap() + } + .aligned(.leading) + } + .animation(.default, value: state.certificates.count) + } header: { + SectionHeaderView(title: "자격증") + } + .padding(.horizontal, 20) + } + + @ViewBuilder + func certificateListView() -> some View { + VStack(spacing: 12) { + ForEach(state.certificates.indices, id: \.self) { index in + HStack(spacing: 16) { + SMSTextField( + "정보처리산업기사", + text: Binding( + get: { state.certificates[safe: index] ?? "" }, + set: { intent.updateCertificate(certificate: $0, at: index) } + ) + ) + + Button { + intent.deleteCertificateColumn(at: index) + } label: { + SMSIcon(.trash) + } + } + } + } + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift index d4514b6c..94b2a441 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift @@ -28,12 +28,7 @@ struct MyPageMilitaryView: View { } } } header: { - SMSText("학교 생활", font: .title1) - .aligned(.leading) - .padding(.vertical) - .background { - Color.sms(.system(.white)) - } + SectionHeaderView(title: "학교 생활") } .padding(.horizontal, 20) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift index 5cc1922b..8f6b398f 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift @@ -146,12 +146,7 @@ struct MyPageProfileView: View { .titleWrapper("세부스택 (최대 5개)") } } header: { - SMSText("프로필", font: .title1) - .aligned(.leading) - .padding(.vertical) - .background { - Color.sms(.system(.white)) - } + SectionHeaderView(title: "프로필") } .padding(.horizontal, 20) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift index 12f12981..05b2eae9 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageSchoolLifeView.swift @@ -20,12 +20,7 @@ struct MyPageSchoolLifeView: View { .titleWrapper("인증제 점수") } } header: { - SMSText("학교 생활", font: .title1) - .aligned(.leading) - .padding(.vertical) - .background { - Color.sms(.system(.white)) - } + SectionHeaderView(title: "학교 생활") } .padding(.horizontal, 20) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index f038a39b..c2078922 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -43,6 +43,19 @@ struct MyPageView: View { MyPageMilitaryView(intent: intent, state: state) } + + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageCertificateView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift index 8175e8bb..e14cdd37 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift @@ -51,12 +51,7 @@ struct MyPageWorkInfoView: View { workRegionList() } } header: { - SMSText("근무 조건", font: .title1) - .aligned(.leading) - .padding(.vertical) - .background { - Color.sms(.system(.white)) - } + SectionHeaderView(title: "근무 조건") } .padding(.horizontal, 20) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/SectionHeaderView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/SectionHeaderView.swift new file mode 100644 index 00000000..fc8a84c4 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/SectionHeaderView.swift @@ -0,0 +1,15 @@ +import DesignSystem +import SwiftUI + +struct SectionHeaderView: View { + let title: String + + var body: some View { + SMSText(title, font: .title1) + .aligned(.leading) + .padding(.vertical) + .background { + Color.sms(.system(.white)) + } + } +} From c71be70fcd8f552a11720462050bd8827de5e9b8 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 17:41:11 +0900 Subject: [PATCH 23/62] :sparkles: :: [#220] MyPageFeature / MyPageLanguageView --- .../Sources/Intent/MyPageIntentProtocol.swift | 2 +- .../Sources/Intent/MyPageLanguageIntent.swift | 4 +- .../Sources/Scene/MyPageLanguageView.swift | 69 +++++++++++++++++++ .../Sources/Scene/MyPageView.swift | 12 ++++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageLanguageView.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index a457ab90..7f494e37 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -6,7 +6,7 @@ protocol MyPageIntentProtocol: MyPageWorkInfoIntentProtocol, MyPageMilitaryIntentProtocol, MyPageCertificateIntentProtocol, - MyPageLanguageIntentProtocol, + MyPageLanguageInfoIntentProtocol, MyPageProjectIntentProtocol, MyPagePrizeIntentProtocol { func existActionSheetIsRequired() diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift index c6165134..57c44e6a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageLanguageIntent.swift @@ -1,11 +1,11 @@ -protocol MyPageLanguageIntentProtocol { +protocol MyPageLanguageInfoIntentProtocol { func updateLanguageName(name: String, at index: Int) func updateLanguageScore(score: String, at index: Int) func deleteLanguage(at index: Int) func languageAppendButtonDidTap() } -extension MyPageIntent: MyPageLanguageIntentProtocol { +extension MyPageIntent: MyPageLanguageInfoIntentProtocol { func updateLanguageName(name: String, at index: Int) { model?.updateLanguageName(name: name, at: index) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageLanguageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageLanguageView.swift new file mode 100644 index 00000000..3b9dab48 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageLanguageView.swift @@ -0,0 +1,69 @@ +import BaseFeature +import DesignSystem +import SwiftUI +import ViewUtil + +struct MyPageLanguageView: View { + @StateObject var container: MVIContainer + var intent: MyPageLanguageInfoIntentProtocol { container.intent } + var state: MyPageLanguageInfoStateProtocol { container.model } + let geometry: GeometryProxy + + init( + container: MVIContainer, + geometry: GeometryProxy + ) { + self._container = StateObject(wrappedValue: container) + self.geometry = geometry + } + + var body: some View { + Section { + languageListView(proxy: geometry) + } header: { + SectionHeaderView(title: "외국어") + } + .padding(.horizontal, 20) + } + + @ViewBuilder + func languageListView(proxy: GeometryProxy) -> some View { + VStack(spacing: 8) { + ForEach(state.languageList.indices, id: \.self) { index in + HStack(spacing: 16) { + SMSTextField( + "예) 영어, 토익", + text: Binding( + get: { state.languageList[safe: index]?.name ?? "" }, + set: { intent.updateLanguageName(name: $0, at: index) } + ) + ) + .frame(maxWidth: .infinity) + + SMSTextField( + "원어민수준", + text: Binding( + get: { state.languageList[safe: index]?.score ?? "" }, + set: { intent.updateLanguageScore(score: $0, at: index) } + ) + ) + .frame(maxWidth: proxy.size.width / 4) + + Button { + intent.deleteLanguage(at: index) + } label: { + SMSIcon(.trash) + } + } + } + .titleWrapper("외국어") + .aligned(.leading) + + SMSChip("추가") { + intent.languageAppendButtonDidTap() + } + .aligned(.leading) + } + .animation(.default, value: state.languageList.count) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index c2078922..9db97db4 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -55,6 +55,18 @@ struct MyPageView: View { modelChangePublisher: container.objectWillChange ) ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageLanguageView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) } } } From ffe36f35c4d30410b6108db4d7a92832450f8ae7 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 18:06:10 +0900 Subject: [PATCH 24/62] :sparkles: :: [#220] MyPageFeature / MyPageProjectView --- .../Sources/DatePicker}/DatePickerField.swift | 7 +- .../Sources/Scene/View/DatePickerField.swift | 37 -- .../Sources/Model/MyPageProjectModel.swift | 4 +- .../Sources/Scene/MyPageProjectView.swift | 361 ++++++++++++++++++ .../Sources/Scene/MyPageView.swift | 12 + 5 files changed, 379 insertions(+), 42 deletions(-) rename Projects/{Feature/InputPrizeInfoFeature/Sources/Scene/View => Core/DesignSystem/Sources/DatePicker}/DatePickerField.swift (88%) delete mode 100644 Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift diff --git a/Projects/Feature/InputPrizeInfoFeature/Sources/Scene/View/DatePickerField.swift b/Projects/Core/DesignSystem/Sources/DatePicker/DatePickerField.swift similarity index 88% rename from Projects/Feature/InputPrizeInfoFeature/Sources/Scene/View/DatePickerField.swift rename to Projects/Core/DesignSystem/Sources/DatePicker/DatePickerField.swift index bf44884c..a0cd0332 100644 --- a/Projects/Feature/InputPrizeInfoFeature/Sources/Scene/View/DatePickerField.swift +++ b/Projects/Core/DesignSystem/Sources/DatePicker/DatePickerField.swift @@ -1,11 +1,10 @@ -import DesignSystem import SwiftUI -struct DatePickerField: View { +public struct DatePickerField: View { let dateText: String let action: () -> Void - init( + public init( dateText: String, action: @escaping () -> Void ) { @@ -13,7 +12,7 @@ struct DatePickerField: View { self.action = action } - var body: some View { + public var body: some View { SMSTextField( "yyyy.mm", text: Binding( diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift deleted file mode 100644 index bf44884c..00000000 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/View/DatePickerField.swift +++ /dev/null @@ -1,37 +0,0 @@ -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() - } - ) - } -} diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift index 23aed24d..976ef893 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift @@ -167,11 +167,13 @@ extension MyPageModel: MyPageProjectActionProtocol { relatedLinks: [] ) ) + collapsedProject.append(false) } func removeProject(index: Int) { - guard projectList[safe: index] != nil else { return } + guard projectList[safe: index] != nil, collapsedProject[safe: index] != nil else { return } projectList.remove(at: index) + collapsedProject.remove(at: index) } func updateFocusedProjectIndex(index: Int) { diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift new file mode 100644 index 00000000..87d106ca --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift @@ -0,0 +1,361 @@ +import BaseFeature +import DesignSystem +import NukeUI +import SwiftUI +import TagLayoutView +import ViewUtil + +struct MyPageProjectView: View { + @FocusState var projectContentIsFocused: Bool + @StateObject var container: MVIContainer + var intent: MyPageProjectIntentProtocol { container.intent } + var state: MyPageProjectStateProtocol { container.model } + let geometry: GeometryProxy + + init( + container: MVIContainer, + geometry: GeometryProxy + ) { + self._container = StateObject(wrappedValue: container) + self.geometry = geometry + } + + var body: some View { + Section { + VStack(spacing: 24) { + 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) + } header: { + SectionHeaderView(title: "프로젝트") + } + .padding(.horizontal, 20) + } + + @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) + .foregroundColor(.sms(.system(.black))) + + Spacer() + + SMSIcon(.downChevron) + .rotationEffect(collapsed ? .degrees(90) : .degrees(0)) + .buttonWrapper { + intent.projectToggleButtonDidTap(index: index) + } + + SMSIcon(.xmarkOutline) + .buttonWrapper { + intent.projectRemoveButtonDidTap(index: index) + } + } + .padding(.bottom, 8) + + ConditionView(!collapsed) { + projectName(index: index) + + projectIcon(index: index) + + projectPreviewImageList(index: index) + + projectContentTextEditor(index: index) + + projectTechStack(geometry: geometry, index: index) + + projectDuration(index: index) + + projectRelatedLink(index: index, geometry: geometry) + } + } + } +} + +// MARK: - View Section +private extension MyPageProjectView { + @ViewBuilder + 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(index: Int) -> some View { + Group { + if let iconURLString = state.projectList[safe: index]?.iconImage, + let iconURL = URL(string: iconURLString) { + LazyImage(url: iconURL) { image in + if let image = image.image { + image + .resizable() + .frame(width: 108, height: 108) + .cornerRadius(8) + } else { + imagePlaceholder(size: 108) + .overlay { + SMSIcon(.photo) + } + } + } + } else { + imagePlaceholder(size: 108) + .overlay { + SMSIcon(.photo) + } + } + } + .buttonWrapper { + intent.projectIconImageButtonDidTap(index: index) + } + .titleWrapper("아이콘") + .imagePicker( + isShow: Binding( + get: { state.isPresentedProjectImagePicker && state.focusedProjectIndex == index }, + set: { _ in intent.projectIconImagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { nil }, + 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 ?? [] + ConditionView(projectPreviewImages.count < 4) { + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) + } + } + .buttonWrapper { + intent.appendPreviewImageButtonDidTap(index: index) + } + } + + ForEach(projectPreviewImages.indices, id: \.self) { previewIndex in + let url = URL(string: projectPreviewImages[previewIndex]) + LazyImage(url: url) { image in + if let image = image.image { + image + .resizable() + .frame(width: 132, height: 132) + .cornerRadius(8) + .overlay(alignment: .topTrailing) { + SMSIcon(.xmark) + .padding(4) + .buttonWrapper { + intent.removePreviewImageDidTap(index: index, previewIndex: previewIndex) + } + } + } else { + imagePlaceholder(size: 132) + } + } + } + } + .titleWrapper("미리보기 사진") + } + + @ViewBuilder + 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) } + ) + ) + .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("내용") + } + + @ViewBuilder + 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 { + intent.projectTechStackAppendButtonDidTap(index: index) + } + + 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)) + } + } + .titleWrapper("사용 기술 (최대 20개)") + } + + @ViewBuilder + func projectDuration(index: Int) -> some View { + VStack(spacing: 8) { + HStack(spacing: 8) { + let project = state.projectList[safe: index] + DatePickerField(dateText: project?.startAtString ?? "") { + intent.projectStartAtButtonDidTap(index: index) + } + .frame(maxWidth: .infinity) + + if !(project?.isInProgress ?? false) { + SMSIcon(.waterWave) + + DatePickerField(dateText: project?.endAtString ?? "") { + intent.projectEndAtButtonDidTap(index: index) + } + .frame(maxWidth: .infinity) + } + } + .animation(.spring(blendDuration: 0.3), value: state.projectList.map(\.isInProgress)) + + HStack(spacing: 8) { + SMSCheckbox( + isSelected: Binding( + get: { state.projectList[safe: index]?.isInProgress ?? false }, + set: { isInProgress in + withAnimation { + intent.projectIsInProgressButtonDidTap(index: index, isInProgress: isInProgress) + } + } + ) + ) + + SMSText("진행중", font: .body1) + .foregroundColor(.sms(.neutral(.n30))) + .aligned(.leading) + } + } + .titleWrapper("진행 기간") + } + + @ViewBuilder + func projectRelatedLink(index: Int, geometry: GeometryProxy) -> some View { + VStack(spacing: 8) { + let relatedLinks = state.projectList[safe: index]?.relatedLinks ?? [] + ForEach(relatedLinks.indices, id: \.self) { relatedIndex in + HStack(spacing: 16) { + SMSTextField( + "이름", + text: Binding( + get: { relatedLinks[relatedIndex].name }, + set: { intent.updateProjectLinkName(index: index, linkIndex: relatedIndex, name: $0) } + ) + ) + .frame(maxWidth: geometry.size.width / 4) + + SMSTextField( + "URL", + text: Binding( + get: { relatedLinks[relatedIndex].url }, + set: { intent.updateProjectLinkURL(index: index, linkIndex: relatedIndex, url: $0) } + ) + ) + .frame(maxWidth: .infinity) + + Button { + intent.removeProjectRelatedLinkDidTap(index: index, linkIndex: relatedIndex) + } label: { + SMSIcon(.trash) + } + } + } + + SMSChip("추가") { + intent.relatedLinkAppendButtonDidTap(index: index) + } + .aligned(.leading) + } + .titleWrapper("관련 링크") + } +} + +// MARK: - Reusable +private extension MyPageProjectView { + @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/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 9db97db4..d0b16883 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -67,6 +67,18 @@ struct MyPageView: View { ), geometry: geometry ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageProjectView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) } } } From 2a243f012b3096b5970b86d7c63274796b6e0da0 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 18:14:17 +0900 Subject: [PATCH 25/62] :sparkles: :: [#220] MyPageFeature / MyPagePrizeVie --- .../Sources/Scene/MyPagePrizeView.swift | 103 ++++++++++++++++++ .../Sources/Scene/MyPageView.swift | 11 ++ 2 files changed, 114 insertions(+) create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift new file mode 100644 index 00000000..dacab54f --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift @@ -0,0 +1,103 @@ +import BaseFeature +import DesignSystem +import SwiftUI +import ViewUtil + +struct MyPagePrizeView: View { + @StateObject var container: MVIContainer + var intent: MyPagePrizeIntentProtocol { container.intent } + var state: MyPagePrizeStateProtocol { container.model } + + init(container: MVIContainer) { + self._container = StateObject(wrappedValue: container) + } + + var body: some View { + Section { + VStack(spacing: 24) { + ForEach(state.prizeList.indices, id: \.self) { index in + prizeListRowView(index: index) + + SMSSeparator(height: 1) + } + } + + SMSChip("추가") { + withAnimation { + intent.prizeAppendButtonDidTap() + } + } + .foregroundColor(.sms(.system(.black))) + .aligned(.trailing) + } header: { + SectionHeaderView(title: "수상") + } + .padding(.horizontal, 20) + } + + @ViewBuilder + func prizeListRowView(index: Int) -> some View { + let collapsed = state.collapsedPrize[safe: index] ?? false + VStack(alignment: .leading, spacing: 24) { + HStack(spacing: 16) { + SMSText("수상", font: .title1) + .foregroundColor(.sms(.system(.black))) + + Spacer() + + SMSIcon(.downChevron) + .rotationEffect(collapsed ? .degrees(90) : .degrees(0)) + .buttonWrapper { + intent.prizeToggleButtonDidTap(index: index) + } + + SMSIcon(.xmarkOutline) + .buttonWrapper { + intent.prizeRemoveButtonDidTap(index: index) + } + } + .padding(.bottom, 8) + + ConditionView(!collapsed) { + prizeName(index: index) + + prizePrize(index: index) + + prizePrizeAt(index: index) + } + } + } + + @ViewBuilder + func prizeName(index: Int) -> some View { + SMSTextField( + "수상 내역 이름입력", + text: Binding( + get: { state.prizeList[safe: index]?.name ?? "" }, + set: { intent.updatePrizeName(index: index, name: $0) } + ) + ) + .titleWrapper("이름") + } + + @ViewBuilder + func prizePrize(index: Int) -> some View { + SMSTextField( + "수상 종류입력", + text: Binding( + get: { state.prizeList[safe: index]?.prize ?? "" }, + set: { intent.updatePrizePrize(index: index, prize: $0)} + ) + ) + .titleWrapper("종류") + } + + @ViewBuilder + func prizePrizeAt(index: Int) -> some View { + let prize = state.prizeList[safe: index] + DatePickerField(dateText: prize?.prizeAtString ?? "") { + intent.prizeAtButtonDidTap(index: index) + } + .titleWrapper("수상 일자") + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index d0b16883..dbc80892 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -79,6 +79,17 @@ struct MyPageView: View { ), geometry: geometry ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPagePrizeView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) } } } From 19578e4495ff06f9865f55f64342bf498e03e171 Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 4 Aug 2023 18:21:21 +0900 Subject: [PATCH 26/62] =?UTF-8?q?:sparkles:=20::=20[#220]=20MyPageFeature?= =?UTF-8?q?=20/=20=EC=A0=80=EC=9E=A5=20=EB=B2=84=ED=8A=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/NeedleGenerated.swift | 12 +--------- .../PressedSelectionControlStyle.swift | 4 ---- .../RefreshEndpoint/RefreshEndpoint.swift | 2 +- .../Interface/Enums/SortType.swift | 1 - .../Interface/Error/UserDomainError.swift | 2 +- .../Sources/Model/LanguageInputModel.swift | 5 ----- .../Sources/Model/MyPageLanguageModel.swift | 5 ----- .../Sources/Scene/MyPageView.swift | 22 +++++++++++++++---- .../View/StudentDetailTitleWrapper.swift | 4 ---- Scripts/CodeSigning.swift | 4 +--- Scripts/GenerateFeature/Project.swift | 8 +++---- Scripts/GenerateModule.swift | 21 ++++++++---------- Scripts/InitEnvironment.swift | 2 +- Scripts/NewDependency.swift | 5 ++--- Tuist/Dependencies.swift | 4 ++-- .../Project+Template.swift | 2 +- Tuist/Templates/Tests/Tests.swift | 2 +- Tuist/Templates/UITests/UITests.swift | 2 +- 18 files changed, 43 insertions(+), 64 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index c08dff82..d8aef160 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -1,5 +1,3 @@ - - import AuthDomain import AuthDomainInterface import BaseDomain @@ -56,7 +54,7 @@ import UserDomain import UserDomainInterface // swiftlint:disable unused_declaration -private let needleDependenciesHash : String? = nil +private let needleDependenciesHash: String? = nil // MARK: - Traversal Helpers @@ -125,7 +123,6 @@ private func factory0f6f456ebf157d02dfb3f47b58f8f304c97af4d5(_ component: Needle } private class InputWorkInfoDependency74441f61366e4e5af9a2Provider: InputWorkInfoDependency { - init() { } @@ -161,7 +158,6 @@ private func factoryc9274e46e78e70f29c54f47b58f8f304c97af4d5(_ component: Needle } private class InputSchoolLifeInfoDependency30edf0903f9bdb7a60fbProvider: InputSchoolLifeInfoDependency { - init() { } @@ -229,7 +225,6 @@ private func factory2882a056d84a613debccf47b58f8f304c97af4d5(_ component: Needle } private class InputMilitaryInfoDependency0cd58f3f7088aec361b6Provider: InputMilitaryInfoDependency { - init() { } @@ -240,7 +235,6 @@ private func factory6e35522c47cca1190471e3b0c44298fc1c149afb(_ component: Needle } private class InputLanguageInfoDependencye83ef16d0fe38d31cb64Provider: InputLanguageInfoDependency { - init() { } @@ -304,7 +298,6 @@ private func factory0b9613d8c923fa9ae897f47b58f8f304c97af4d5(_ component: Needle } private class InputCertificateInfoDependencyd369771b4dc3e8540791Provider: InputCertificateInfoDependency { - init() { } @@ -347,7 +340,6 @@ private func factoryb3d74d9bff60efbc0282f47b58f8f304c97af4d5(_ component: Needle } private class InputPrizeInfoDependencyff32e2191f3500ff4774Provider: InputPrizeInfoDependency { - init() { } @@ -444,7 +436,6 @@ extension JwtStoreComponent: Registration { extension AppComponent: Registration { public func registerItems() { - } } extension KeychainComponent: Registration { @@ -590,7 +581,6 @@ extension UserDomainComponent: Registration { } } - #endif private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { diff --git a/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift b/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift index 3099b41d..4dcb6135 100644 --- a/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift +++ b/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift @@ -3,10 +3,6 @@ import SwiftUI internal struct PressedSelectionButtonStyle: ButtonStyle { var isSelected: Bool - init(isSelected: Bool) { - self.isSelected = isSelected - } - @ViewBuilder func makeBody(configuration: Configuration) -> some View { Circle() diff --git a/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift b/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift index e301d88d..2c32d978 100644 --- a/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift +++ b/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift @@ -22,7 +22,7 @@ extension RefreshEndpoint { return .none } - var headers: [String : String]? { + var headers: [String: String]? { switch self { case let .refresh(refreshToken): return [ diff --git a/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift b/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift index 8b04064d..da552cf2 100644 --- a/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift +++ b/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift @@ -16,4 +16,3 @@ public extension SortType { } } } - diff --git a/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift b/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift index 0c6d6735..7df04c85 100644 --- a/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift +++ b/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift @@ -1,5 +1,5 @@ import Foundation public enum UserDomainError: Error { - + } diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift index c6eefa74..66176d52 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift @@ -3,9 +3,4 @@ import Foundation struct LanguageInputModel: Equatable { let languageName: String let languageScore: String - - init(languageName: String, languageScore: String) { - self.languageName = languageName - self.languageScore = languageScore - } } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift index 44b9b048..d88c4124 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift @@ -3,11 +3,6 @@ import StudentDomainInterface struct LanguageModel: Equatable { var name: String var score: String - - init(name: String, score: String) { - self.name = name - self.score = score - } } protocol MyPageLanguageInfoStateProtocol { diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index dbc80892..0224f7c1 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -4,6 +4,7 @@ import DesignSystem struct MyPageView: View { @Environment(\.dismiss) var dismiss + @Environment(\.safeAreaInsets) var safeAreaInsets @StateObject var container: MVIContainer var intent: any MyPageIntentProtocol { container.intent } var state: any MyPageStateProtocol { container.model } @@ -93,12 +94,25 @@ struct MyPageView: View { } } } + + CTAButton(text: "저장") { + #warning("저장 로직 추가") + } + .padding(.horizontal, 20) + .padding(.bottom, safeAreaInsets.bottom + 16) + .background { + Color.sms(.system(.white)) + } + .ignoresSafeArea() } } - .smsBottomSheet(isShowing: Binding( - get: { state.isPresentedExistActionSheet }, - set: { _ in intent.existActionSheetDismissed() } - )) { + .edgesIgnoringSafeArea([.bottom]) + .smsBottomSheet( + isShowing: Binding( + get: { state.isPresentedExistActionSheet }, + set: { _ in intent.existActionSheetDismissed() } + ) + ) { VStack(alignment: .leading, spacing: 32) { Button { intent.logoutDialogIsRequired() diff --git a/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift b/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift index fa5ea63c..a312ed05 100644 --- a/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift +++ b/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift @@ -4,10 +4,6 @@ import SwiftUI struct StudentDetailTitleWrapper: ViewModifier { let title: String - init(title: String) { - self.title = title - } - func body(content: Content) -> some View { VStack(alignment: .leading, spacing: 8) { Text(title) diff --git a/Scripts/CodeSigning.swift b/Scripts/CodeSigning.swift index 39bc29b8..3cf6c01f 100644 --- a/Scripts/CodeSigning.swift +++ b/Scripts/CodeSigning.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } @@ -32,5 +32,3 @@ public extension SettingsDictionary { writeContentInFile(path: "Tuist/ProjectDescriptionHelpers/CodeSign.swift", content: codeSignContent) print("✅ Code Sign extension generated successfully!") - - diff --git a/Scripts/GenerateFeature/Project.swift b/Scripts/GenerateFeature/Project.swift index 3cabc43e..46e7287a 100644 --- a/Scripts/GenerateFeature/Project.swift +++ b/Scripts/GenerateFeature/Project.swift @@ -1,9 +1,9 @@ -//import ProjectDescription -//import ProjectDescriptionHelpers +// import ProjectDescription +// import ProjectDescriptionHelpers // -//let project = Project.staticFramework( +// let project = Project.staticFramework( // name: "Feature", // dependencies: [ // .Project.Features.Feature // ] -//) +// ) diff --git a/Scripts/GenerateModule.swift b/Scripts/GenerateModule.swift index 21df0b24..f9272010 100644 --- a/Scripts/GenerateModule.swift +++ b/Scripts/GenerateModule.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } @@ -124,7 +124,7 @@ let project = Project.makeModule( """ writeContentInFile( - path: currentPath + "Projects/\(layer.rawValue)/\(moduleName)/Project.swift", + path: currentPath + "Projects/\(layer.rawValue)/\(moduleName)/Project.swift", content: projectSwift ) } @@ -135,14 +135,14 @@ func makeProjectDirectory() { func makeProjectScaffold(targetString: String) { _ = try? bash.run( - commandName: "tuist", + commandName: "tuist", arguments: ["scaffold", "Module", "--name", "\(moduleName)", "--layer", "\(layer.rawValue)", "--target", "\(targetString)"] ) } func makeScaffold(target: MicroTargetType) { _ = try? bash.run( - commandName: "tuist", + commandName: "tuist", arguments: ["scaffold", "\(target.rawValue)", "--name", "\(moduleName)", "--layer", "\(layer.rawValue)"] ) } @@ -162,7 +162,7 @@ func updateFileContent( guard let readHandle = try? FileHandle(forReadingFrom: fileURL) else { fatalError("❌ Failed to find \(filePath)") } - guard let readData = try? readHandle.readToEnd() else { + guard let readData = try? readHandle.readToEnd() else { fatalError("❌ Failed to find \(filePath)") } try? readHandle.close() @@ -178,14 +178,13 @@ func updateFileContent( try? writeHandle.close() } - // MARK: - Starting point print("Enter layer name\n(Feature | Domain | Core | Shared)", terminator: " : ") let layerInput = readLine() -guard - let layerInput, - !layerInput.isEmpty , +guard + let layerInput, + !layerInput.isEmpty, let layerUnwrapping = LayerType(rawValue: layerInput) else { print("Layer is empty or invalid") @@ -230,7 +229,6 @@ print("interface: \(hasInterface), testing: \(hasTesting), unitTests: \(hasUnitT print("------------------------------------------------------------------------------------------------------------------------") print("✅ Module is created successfully!") - // MARK: - Bash protocol CommandExecuting { func run(commandName: String, arguments: [String]) throws -> String @@ -246,7 +244,7 @@ struct Bash: CommandExecuting { } private func resolve(_ command: String) throws -> String { - guard var bashCommand = try? run("/bin/bash" , with: ["-l", "-c", "which \(command)"]) else { + guard var bashCommand = try? run("/bin/bash", with: ["-l", "-c", "which \(command)"]) else { throw BashError.commandNotFound(name: command) } bashCommand = bashCommand.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) @@ -265,4 +263,3 @@ struct Bash: CommandExecuting { return output } } - diff --git a/Scripts/InitEnvironment.swift b/Scripts/InitEnvironment.swift index 132cf78e..6e2f3a26 100644 --- a/Scripts/InitEnvironment.swift +++ b/Scripts/InitEnvironment.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } diff --git a/Scripts/NewDependency.swift b/Scripts/NewDependency.swift index dcdf6ea6..561eccbb 100644 --- a/Scripts/NewDependency.swift +++ b/Scripts/NewDependency.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } @@ -18,7 +18,7 @@ signal(SIGINT, handleSIGINT) guard let readHandle = try? FileHandle(forReadingFrom: fileURL) else { fatalError("❌ Failed to find \(filePath)") } - guard let readData = try? readHandle.readToEnd() else { + guard let readData = try? readHandle.readToEnd() else { fatalError("❌ Failed to find \(filePath)") } try? readHandle.close() @@ -74,4 +74,3 @@ signal(SIGINT, handleSIGINT) } registerDependency(name: dependencyName, package: packageName, url: dependencyURL, version: dependencyVersion) - diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 34877fbe..90e41989 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -12,8 +12,8 @@ let dependencies = Dependencies( .remote(url: "https://github.com/GSM-MSG/GAuthSignin-Swift", requirement: .exact("0.0.3")), .remote(url: "https://github.com/Quick/Nimble.git", requirement: .exact("11.2.2")), .remote(url: "https://github.com/Quick/Quick.git", requirement: .exact("6.1.0")), - .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.2.11")), - + .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.2.11")) + ], baseSettings: .settings( configurations: [ diff --git a/Tuist/ProjectDescriptionHelpers/Project+Template.swift b/Tuist/ProjectDescriptionHelpers/Project+Template.swift index a8befb61..bf9c99f6 100644 --- a/Tuist/ProjectDescriptionHelpers/Project+Template.swift +++ b/Tuist/ProjectDescriptionHelpers/Project+Template.swift @@ -178,7 +178,7 @@ public extension Project { infoPlist: .extendingDefault(with: [ "UIMainStoryboardFile": "", "UILaunchStoryboardName": "LaunchScreen", - "ENABLE_TESTS": .boolean(true), + "ENABLE_TESTS": .boolean(true) ]), sources: .demoSources, resources: ["Demo/Resources/**"], diff --git a/Tuist/Templates/Tests/Tests.swift b/Tuist/Templates/Tests/Tests.swift index 09d79c9e..b5d64a60 100644 --- a/Tuist/Templates/Tests/Tests.swift +++ b/Tuist/Templates/Tests/Tests.swift @@ -13,6 +13,6 @@ private let template = Template( .file( path: "Projects/\(layerAttribute)/\(nameAttribute)/Tests/\(nameAttribute)Test.swift", templatePath: "Tests.stencil" - ), + ) ] ) diff --git a/Tuist/Templates/UITests/UITests.swift b/Tuist/Templates/UITests/UITests.swift index 716ef246..503d283e 100644 --- a/Tuist/Templates/UITests/UITests.swift +++ b/Tuist/Templates/UITests/UITests.swift @@ -13,6 +13,6 @@ private let template = Template( .file( path: "Projects/\(layerAttribute)/\(nameAttribute)/UITests/\(nameAttribute)UITests.swift", templatePath: "UITests.stencil" - ), + ) ] ) From 0851b4f5b98801de4a4f289812d6bf51606eea45 Mon Sep 17 00:00:00 2001 From: baegteun Date: Mon, 7 Aug 2023 11:20:46 +0900 Subject: [PATCH 27/62] =?UTF-8?q?:sparkles:=20::=20[#220]=20MyPageFeature?= =?UTF-8?q?=20/=20=EB=B3=B8=EC=9D=B8=20=EC=A0=95=EB=B3=B4=20fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit g --- .../Sources/Application/NeedleGenerated.swift | 14 ++++++++-- .../Sources/Intent/MyPageIntent.swift | 26 +++++++++++++++++++ .../Sources/Intent/MyPageIntentProtocol.swift | 1 + .../Model/MyPageCertificateModel.swift | 5 ++++ .../Sources/Model/MyPageLanguageModel.swift | 5 ++++ .../Sources/Model/MyPageWorkInfoModel.swift | 5 ++++ .../Sources/Scene/MyPageView.swift | 3 +++ 7 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index d8aef160..a36737be 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -1,3 +1,5 @@ + + import AuthDomain import AuthDomainInterface import BaseDomain @@ -54,7 +56,7 @@ import UserDomain import UserDomainInterface // swiftlint:disable unused_declaration -private let needleDependenciesHash: String? = nil +private let needleDependenciesHash : String? = nil // MARK: - Traversal Helpers @@ -123,6 +125,7 @@ private func factory0f6f456ebf157d02dfb3f47b58f8f304c97af4d5(_ component: Needle } private class InputWorkInfoDependency74441f61366e4e5af9a2Provider: InputWorkInfoDependency { + init() { } @@ -158,6 +161,7 @@ private func factoryc9274e46e78e70f29c54f47b58f8f304c97af4d5(_ component: Needle } private class InputSchoolLifeInfoDependency30edf0903f9bdb7a60fbProvider: InputSchoolLifeInfoDependency { + init() { } @@ -225,6 +229,7 @@ private func factory2882a056d84a613debccf47b58f8f304c97af4d5(_ component: Needle } private class InputMilitaryInfoDependency0cd58f3f7088aec361b6Provider: InputMilitaryInfoDependency { + init() { } @@ -235,6 +240,7 @@ private func factory6e35522c47cca1190471e3b0c44298fc1c149afb(_ component: Needle } private class InputLanguageInfoDependencye83ef16d0fe38d31cb64Provider: InputLanguageInfoDependency { + init() { } @@ -298,6 +304,7 @@ private func factory0b9613d8c923fa9ae897f47b58f8f304c97af4d5(_ component: Needle } private class InputCertificateInfoDependencyd369771b4dc3e8540791Provider: InputCertificateInfoDependency { + init() { } @@ -340,6 +347,7 @@ private func factoryb3d74d9bff60efbc0282f47b58f8f304c97af4d5(_ component: Needle } private class InputPrizeInfoDependencyff32e2191f3500ff4774Provider: InputPrizeInfoDependency { + init() { } @@ -436,6 +444,7 @@ extension JwtStoreComponent: Registration { extension AppComponent: Registration { public func registerItems() { + } } extension KeychainComponent: Registration { @@ -581,6 +590,7 @@ extension UserDomainComponent: Registration { } } + #endif private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { @@ -594,7 +604,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi #if !NEEDLE_DYNAMIC -private func register1() { +@inline(never) private func register1() { registerProviderFactory("^->AppComponent->JwtStoreComponent", factoryb27d5aae1eb7e73575a6f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 9504fed9..fded23f7 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -24,6 +24,32 @@ final class MyPageIntent: MyPageIntentProtocol { self.withdrawalUseCase = withdrawalUseCase } + func onAppear() { + Task { + do { + let profile = try await fetchMyProfileUseCase.execute() + model?.updateProfileURL(url: profile.profileImageURL) + model?.updateIntroduce(introduce: profile.introduce) + model?.updateMajor(major: profile.major) + model?.updateEmail(email: profile.contactEmail) + model?.updateGSMScore(gsmScore: "\(profile.gsmAuthenticationScore)") + model?.updateFormOfEmployment(form: profile.formOfEmployment) + model?.updateWorkRegions(regions: profile.regions) + model?.updateMilitaryServiceType(type: profile.militaryService) + model?.updateSalary(salary: "\(profile.salary)") + model?.updateLanguageScores( + languages: profile.languageCertificates.map { + LanguageModel(name: $0.name, score: $0.score) + } + ) + model?.updateCertificates(certificates: profile.certificates) + model?.updateTechStacks(techStacks: profile.techStacks) + } catch { + model?.updateIsError(isError: true) + } + } + } + func existActionSheetIsRequired() { model?.updateIsPresentedExistActionSheet(isPresented: true) } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 7f494e37..34155210 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -9,6 +9,7 @@ protocol MyPageIntentProtocol: MyPageLanguageInfoIntentProtocol, MyPageProjectIntentProtocol, MyPagePrizeIntentProtocol { + func onAppear() func existActionSheetIsRequired() func existActionSheetDismissed() func logoutDialogIsRequired() diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift index ea77add7..e2538383 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageCertificateModel.swift @@ -6,6 +6,7 @@ protocol MyPageCertificateStateProtocol { protocol MyPageCertificateActionProtocol: AnyObject { func updateCertificate(certificate: String, at index: Int) + func updateCertificates(certificates: [String]) func deleteCertificateColumn(at index: Int) func appendCertificate() } @@ -16,6 +17,10 @@ extension MyPageModel: MyPageCertificateActionProtocol { certificates[index] = certificate } + func updateCertificates(certificates: [String]) { + self.certificates = certificates + } + func deleteCertificateColumn(at index: Int) { certificates.remove(at: index) } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift index d88c4124..ceab066d 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift @@ -12,6 +12,7 @@ protocol MyPageLanguageInfoStateProtocol { protocol MyPageLanguageInfoActionProtocol: AnyObject { func updateLanguageName(name: String, at index: Int) func updateLanguageScore(score: String, at index: Int) + func updateLanguageScores(languages: [LanguageModel]) func deleteLanguage(at index: Int) func appendLanguage() } @@ -27,6 +28,10 @@ extension MyPageModel: MyPageLanguageInfoActionProtocol { languageList[index].score = score } + func updateLanguageScores(languages: [LanguageModel]) { + self.languageList = languages + } + func deleteLanguage(at index: Int) { languageList.remove(at: index) } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift index 0dabf766..e882568e 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift @@ -12,6 +12,7 @@ protocol MyPageWorkInfoStateProtocol { protocol MyPageWorkInfoActionProtocol: AnyObject { func appendWorkRegion() func updateWorkRegion(region: String, at index: Int) + func updateWorkRegions(regions: [String]) func deleteWorkRegion(at index: Int) func updateSalary(salary: String) func updateFormOfEmployment(form: FormOfEmployment) @@ -28,6 +29,10 @@ extension MyPageModel: MyPageWorkInfoActionProtocol { self.workRegionList[index] = region } + func updateWorkRegions(regions: [String]) { + self.workRegionList = regions + } + func deleteWorkRegion(at index: Int) { self.workRegionList.remove(at: index) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 0224f7c1..7b219da4 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -106,6 +106,9 @@ struct MyPageView: View { .ignoresSafeArea() } } + .onAppear { + intent.onAppear() + } .edgesIgnoringSafeArea([.bottom]) .smsBottomSheet( isShowing: Binding( From 910ebda19107059de1b98c751fb66cb05ba87981 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:59:33 +0900 Subject: [PATCH 28/62] =?UTF-8?q?:lipstick:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20MyPageVIew=20UI=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MyPageView.swift | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index dbc80892..800fcf73 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -1,12 +1,24 @@ import BaseFeature import SwiftUI import DesignSystem +import ViewUtil +import TechStackAppendFeatureInterface struct MyPageView: View { @Environment(\.dismiss) var dismiss + @Environment(\.safeAreaInsets) var safeAreaInsets @StateObject var container: MVIContainer var intent: any MyPageIntentProtocol { container.intent } var state: any MyPageStateProtocol { container.model } + private let techStackAppendBuildable: any TechStackAppendBuildable + + init( + container: MVIContainer, + techStackAppendBuildable: any TechStackAppendBuildable + ) { + self.techStackAppendBuildable = techStackAppendBuildable + self._container = StateObject(wrappedValue: container) + } var body: some View { GeometryReader { geometry in @@ -93,12 +105,25 @@ struct MyPageView: View { } } } + + CTAButton(text: "저장") { + #warning("저장 로직 추가") + } + .padding(.horizontal, 20) + .padding(.bottom, safeAreaInsets.bottom + 16) + .background { + Color.sms(.system(.white)) + } + .ignoresSafeArea() } } - .smsBottomSheet(isShowing: Binding( - get: { state.isPresentedExistActionSheet }, - set: { _ in intent.existActionSheetDismissed() } - )) { + .edgesIgnoringSafeArea([.bottom]) + .smsBottomSheet( + isShowing: Binding( + get: { state.isPresentedExistActionSheet }, + set: { _ in intent.existActionSheetDismissed() } + ) + ) { VStack(alignment: .leading, spacing: 32) { Button { intent.logoutDialogIsRequired() @@ -172,6 +197,58 @@ struct MyPageView: View { } } } + .datePicker( + isShowing: Binding( + get: { state.isPresentedPrizeAtDatePicker }, + set: { _ in intent.prizeAtDismissed() } + ) + ) { date in + intent.prizePrizeAtDidSelect(index: state.focusedPrizeIndex, prizeAt: date) + } + .imagePicker( + isShow: Binding( + get: { state.isPresentedPreviewImagePicker }, + set: { _ in intent.projectPreviewImagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { + guard let image = $0 else { return } + intent.appendPreviewImage(index: state.focusedProjectIndex, image: image) + } + ) + ) + .datePicker( + isShowing: Binding( + get: { state.isPresentedProjectStartAtDatePicker }, + set: { _ in intent.projectStartAtDatePickerDismissed() } + ) + ) { date in + intent.projectStartAtDidSelect(index: state.focusedProjectIndex, startAt: date) + } + .datePicker( + isShowing: Binding( + get: { state.isPresentedProjectEndAtDatePicker }, + set: { _ in intent.projectEndAtDatePickerDismissed() } + ) + ) { date in + intent.projectEndAtDidSelect(index: state.focusedProjectIndex, endAt: date) + } + .fullScreenCover( + isPresented: Binding( + get: { state.isPresentedProjectTechStackAppend }, + set: { _ in intent.projectTechStackAppendDismissed() } + ) + ) { + DeferView { + techStackAppendBuildable.makeView( + initial: Array(state.projectList[safe: state.focusedProjectIndex]?.techStacks ?? []) + ) { + intent.techStacksDidSelect(index: state.focusedProjectIndex, techStacks: $0) + } + .eraseToAnyView() + } + } .hideKeyboardWhenTap() .navigationTitle("마이페이지") .smsBackButton( From 3d73b5b49616cac1f878d9116f405bc002def929 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 10 Aug 2023 02:00:33 +0900 Subject: [PATCH 29/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20subView,=20=EC=88=98?= =?UTF-8?q?=EC=83=81=20subView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/NeedleGenerated.swift | 4 ++++ .../PressedSelectionControlStyle.swift | 4 ---- .../RefreshEndpoint/RefreshEndpoint.swift | 2 +- .../Interface/Enums/SortType.swift | 1 - .../Interface/Error/UserDomainError.swift | 2 +- .../Sources/Model/LanguageInputModel.swift | 5 ----- .../Demo/Sources/AppDelegate.swift | 10 ++++++++- .../Sources/DI/MyPageComponent.swift | 7 ++++++- .../Sources/Model/MyPageLanguageModel.swift | 5 ----- .../View/StudentDetailTitleWrapper.swift | 4 ---- Scripts/CodeSigning.swift | 4 +--- Scripts/GenerateFeature/Project.swift | 8 +++---- Scripts/GenerateModule.swift | 21 ++++++++----------- Scripts/InitEnvironment.swift | 2 +- Scripts/NewDependency.swift | 5 ++--- Tuist/Dependencies.swift | 4 ++-- .../Project+Template.swift | 2 +- Tuist/Templates/Tests/Tests.swift | 2 +- Tuist/Templates/UITests/UITests.swift | 2 +- 19 files changed, 43 insertions(+), 51 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index c08dff82..4bbe0e5d 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -114,6 +114,9 @@ private class MyPageDependency48d84b530313b3ee40feProvider: MyPageDependency { var authDomainBuildable: any AuthDomainBuildable { return appComponent.authDomainBuildable } + var techStackAppendBuildable: any TechStackAppendBuildable { + return appComponent.techStackAppendBuildable + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -466,6 +469,7 @@ extension MyPageComponent: Registration { public func registerItems() { keyPathToName[\MyPageDependency.userDomainBuildable] = "userDomainBuildable-any UserDomainBuildable" keyPathToName[\MyPageDependency.authDomainBuildable] = "authDomainBuildable-any AuthDomainBuildable" + keyPathToName[\MyPageDependency.techStackAppendBuildable] = "techStackAppendBuildable-any TechStackAppendBuildable" } } extension InputWorkInfoComponent: Registration { diff --git a/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift b/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift index 3099b41d..4dcb6135 100644 --- a/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift +++ b/Projects/Core/DesignSystem/Sources/SelectionControls/PressedSelectionControlStyle.swift @@ -3,10 +3,6 @@ import SwiftUI internal struct PressedSelectionButtonStyle: ButtonStyle { var isSelected: Bool - init(isSelected: Bool) { - self.isSelected = isSelected - } - @ViewBuilder func makeBody(configuration: Configuration) -> some View { Circle() diff --git a/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift b/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift index e301d88d..2c32d978 100644 --- a/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift +++ b/Projects/Domain/BaseDomain/Sources/RefreshEndpoint/RefreshEndpoint.swift @@ -22,7 +22,7 @@ extension RefreshEndpoint { return .none } - var headers: [String : String]? { + var headers: [String: String]? { switch self { case let .refresh(refreshToken): return [ diff --git a/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift b/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift index 8b04064d..da552cf2 100644 --- a/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift +++ b/Projects/Domain/StudentDomain/Interface/Enums/SortType.swift @@ -16,4 +16,3 @@ public extension SortType { } } } - diff --git a/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift b/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift index 0c6d6735..7df04c85 100644 --- a/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift +++ b/Projects/Domain/UserDomain/Interface/Error/UserDomainError.swift @@ -1,5 +1,5 @@ import Foundation public enum UserDomainError: Error { - + } diff --git a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift index c6eefa74..66176d52 100644 --- a/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift +++ b/Projects/Feature/InputLanguageInfoFeature/Sources/Model/LanguageInputModel.swift @@ -3,9 +3,4 @@ import Foundation struct LanguageInputModel: Equatable { let languageName: String let languageScore: String - - init(languageName: String, languageScore: String) { - self.languageName = languageName - self.languageScore = languageScore - } } diff --git a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift index c55ebe5b..33819db1 100644 --- a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift @@ -1,5 +1,6 @@ import MyPageFeatureInterface import SwiftUI +import TechStackAppendFeatureInterface @testable import AuthDomainTesting @testable import MyPageFeature @testable import UserDomainTesting @@ -7,6 +8,13 @@ import SwiftUI final class DummyMyPageDelegate: MyPageDelegate { func logout() {} } + +struct DummyTechStackAppendBuildable: TechStackAppendBuildable { + func makeView(initial techStacks: [String], completion: @escaping ([String]) -> Void) -> some View { + EmptyView() + } +} + @main struct MyPageDemoApp: App { var body: some Scene { @@ -27,7 +35,7 @@ struct MyPageDemoApp: App { intent: intent as MyPageIntentProtocol, model: model as MyPageStateProtocol, modelChangePublisher: model.objectWillChange - ) + ), techStackAppendBuildable: DummyTechStackAppendBuildable() ) } } diff --git a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift index 12a1097f..b440b03f 100644 --- a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift +++ b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift @@ -4,10 +4,12 @@ import MyPageFeatureInterface import NeedleFoundation import SwiftUI import UserDomainInterface +import TechStackAppendFeatureInterface public protocol MyPageDependency: Dependency { var userDomainBuildable: any UserDomainBuildable { get } var authDomainBuildable: any AuthDomainBuildable { get } + var techStackAppendBuildable: any TechStackAppendBuildable { get } } public final class MyPageComponent: Component, MyPageBuildable { @@ -25,6 +27,9 @@ public final class MyPageComponent: Component, MyPageBuildable model: model as MyPageStateProtocol, modelChangePublisher: model.objectWillChange ) - return MyPageView(container: container) + return MyPageView( + container: container, + techStackAppendBuildable: dependency.techStackAppendBuildable + ) } } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift index 44b9b048..d88c4124 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageLanguageModel.swift @@ -3,11 +3,6 @@ import StudentDomainInterface struct LanguageModel: Equatable { var name: String var score: String - - init(name: String, score: String) { - self.name = name - self.score = score - } } protocol MyPageLanguageInfoStateProtocol { diff --git a/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift b/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift index fa5ea63c..a312ed05 100644 --- a/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift +++ b/Projects/Feature/StudentDetailFeature/Sources/Scene/View/StudentDetailTitleWrapper.swift @@ -4,10 +4,6 @@ import SwiftUI struct StudentDetailTitleWrapper: ViewModifier { let title: String - init(title: String) { - self.title = title - } - func body(content: Content) -> some View { VStack(alignment: .leading, spacing: 8) { Text(title) diff --git a/Scripts/CodeSigning.swift b/Scripts/CodeSigning.swift index 39bc29b8..3cf6c01f 100644 --- a/Scripts/CodeSigning.swift +++ b/Scripts/CodeSigning.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } @@ -32,5 +32,3 @@ public extension SettingsDictionary { writeContentInFile(path: "Tuist/ProjectDescriptionHelpers/CodeSign.swift", content: codeSignContent) print("✅ Code Sign extension generated successfully!") - - diff --git a/Scripts/GenerateFeature/Project.swift b/Scripts/GenerateFeature/Project.swift index 3cabc43e..46e7287a 100644 --- a/Scripts/GenerateFeature/Project.swift +++ b/Scripts/GenerateFeature/Project.swift @@ -1,9 +1,9 @@ -//import ProjectDescription -//import ProjectDescriptionHelpers +// import ProjectDescription +// import ProjectDescriptionHelpers // -//let project = Project.staticFramework( +// let project = Project.staticFramework( // name: "Feature", // dependencies: [ // .Project.Features.Feature // ] -//) +// ) diff --git a/Scripts/GenerateModule.swift b/Scripts/GenerateModule.swift index 21df0b24..f9272010 100644 --- a/Scripts/GenerateModule.swift +++ b/Scripts/GenerateModule.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } @@ -124,7 +124,7 @@ let project = Project.makeModule( """ writeContentInFile( - path: currentPath + "Projects/\(layer.rawValue)/\(moduleName)/Project.swift", + path: currentPath + "Projects/\(layer.rawValue)/\(moduleName)/Project.swift", content: projectSwift ) } @@ -135,14 +135,14 @@ func makeProjectDirectory() { func makeProjectScaffold(targetString: String) { _ = try? bash.run( - commandName: "tuist", + commandName: "tuist", arguments: ["scaffold", "Module", "--name", "\(moduleName)", "--layer", "\(layer.rawValue)", "--target", "\(targetString)"] ) } func makeScaffold(target: MicroTargetType) { _ = try? bash.run( - commandName: "tuist", + commandName: "tuist", arguments: ["scaffold", "\(target.rawValue)", "--name", "\(moduleName)", "--layer", "\(layer.rawValue)"] ) } @@ -162,7 +162,7 @@ func updateFileContent( guard let readHandle = try? FileHandle(forReadingFrom: fileURL) else { fatalError("❌ Failed to find \(filePath)") } - guard let readData = try? readHandle.readToEnd() else { + guard let readData = try? readHandle.readToEnd() else { fatalError("❌ Failed to find \(filePath)") } try? readHandle.close() @@ -178,14 +178,13 @@ func updateFileContent( try? writeHandle.close() } - // MARK: - Starting point print("Enter layer name\n(Feature | Domain | Core | Shared)", terminator: " : ") let layerInput = readLine() -guard - let layerInput, - !layerInput.isEmpty , +guard + let layerInput, + !layerInput.isEmpty, let layerUnwrapping = LayerType(rawValue: layerInput) else { print("Layer is empty or invalid") @@ -230,7 +229,6 @@ print("interface: \(hasInterface), testing: \(hasTesting), unitTests: \(hasUnitT print("------------------------------------------------------------------------------------------------------------------------") print("✅ Module is created successfully!") - // MARK: - Bash protocol CommandExecuting { func run(commandName: String, arguments: [String]) throws -> String @@ -246,7 +244,7 @@ struct Bash: CommandExecuting { } private func resolve(_ command: String) throws -> String { - guard var bashCommand = try? run("/bin/bash" , with: ["-l", "-c", "which \(command)"]) else { + guard var bashCommand = try? run("/bin/bash", with: ["-l", "-c", "which \(command)"]) else { throw BashError.commandNotFound(name: command) } bashCommand = bashCommand.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) @@ -265,4 +263,3 @@ struct Bash: CommandExecuting { return output } } - diff --git a/Scripts/InitEnvironment.swift b/Scripts/InitEnvironment.swift index 132cf78e..6e2f3a26 100644 --- a/Scripts/InitEnvironment.swift +++ b/Scripts/InitEnvironment.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } diff --git a/Scripts/NewDependency.swift b/Scripts/NewDependency.swift index dcdf6ea6..561eccbb 100644 --- a/Scripts/NewDependency.swift +++ b/Scripts/NewDependency.swift @@ -1,7 +1,7 @@ #!/usr/bin/swift import Foundation -func handleSIGINT(_ signal: Int32) -> Void { +func handleSIGINT(_ signal: Int32) { exit(0) } @@ -18,7 +18,7 @@ signal(SIGINT, handleSIGINT) guard let readHandle = try? FileHandle(forReadingFrom: fileURL) else { fatalError("❌ Failed to find \(filePath)") } - guard let readData = try? readHandle.readToEnd() else { + guard let readData = try? readHandle.readToEnd() else { fatalError("❌ Failed to find \(filePath)") } try? readHandle.close() @@ -74,4 +74,3 @@ signal(SIGINT, handleSIGINT) } registerDependency(name: dependencyName, package: packageName, url: dependencyURL, version: dependencyVersion) - diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 34877fbe..90e41989 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -12,8 +12,8 @@ let dependencies = Dependencies( .remote(url: "https://github.com/GSM-MSG/GAuthSignin-Swift", requirement: .exact("0.0.3")), .remote(url: "https://github.com/Quick/Nimble.git", requirement: .exact("11.2.2")), .remote(url: "https://github.com/Quick/Quick.git", requirement: .exact("6.1.0")), - .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.2.11")), - + .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.2.11")) + ], baseSettings: .settings( configurations: [ diff --git a/Tuist/ProjectDescriptionHelpers/Project+Template.swift b/Tuist/ProjectDescriptionHelpers/Project+Template.swift index a8befb61..bf9c99f6 100644 --- a/Tuist/ProjectDescriptionHelpers/Project+Template.swift +++ b/Tuist/ProjectDescriptionHelpers/Project+Template.swift @@ -178,7 +178,7 @@ public extension Project { infoPlist: .extendingDefault(with: [ "UIMainStoryboardFile": "", "UILaunchStoryboardName": "LaunchScreen", - "ENABLE_TESTS": .boolean(true), + "ENABLE_TESTS": .boolean(true) ]), sources: .demoSources, resources: ["Demo/Resources/**"], diff --git a/Tuist/Templates/Tests/Tests.swift b/Tuist/Templates/Tests/Tests.swift index 09d79c9e..b5d64a60 100644 --- a/Tuist/Templates/Tests/Tests.swift +++ b/Tuist/Templates/Tests/Tests.swift @@ -13,6 +13,6 @@ private let template = Template( .file( path: "Projects/\(layerAttribute)/\(nameAttribute)/Tests/\(nameAttribute)Test.swift", templatePath: "Tests.stencil" - ), + ) ] ) diff --git a/Tuist/Templates/UITests/UITests.swift b/Tuist/Templates/UITests/UITests.swift index 716ef246..503d283e 100644 --- a/Tuist/Templates/UITests/UITests.swift +++ b/Tuist/Templates/UITests/UITests.swift @@ -13,6 +13,6 @@ private let template = Template( .file( path: "Projects/\(layerAttribute)/\(nameAttribute)/UITests/\(nameAttribute)UITests.swift", templatePath: "UITests.stencil" - ), + ) ] ) From 5a90eb166654f210a1ad149bc82a3bc8c762903c Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 10 Aug 2023 02:26:04 +0900 Subject: [PATCH 30/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20ImageUpload=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Demo/Sources/AppDelegate.swift | 5 ++++- .../Sources/DI/MyPageComponent.swift | 5 ++++- .../Sources/Intent/MyPageIntent.swift | 6 +++++- .../Sources/Intent/MyPageProfileIntent.swift | 10 +++++++++- .../Sources/Intent/MyPageProjectIntent.swift | 20 +++++++++++++++++-- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift index 33819db1..059e2bf6 100644 --- a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift @@ -4,6 +4,7 @@ import TechStackAppendFeatureInterface @testable import AuthDomainTesting @testable import MyPageFeature @testable import UserDomainTesting +@testable import FileDomainTesting final class DummyMyPageDelegate: MyPageDelegate { func logout() {} @@ -23,12 +24,14 @@ struct MyPageDemoApp: App { let fetchMyProfileUseCase = FetchMyProfileUseCaseSpy() let logoutUseCase = LogoutUseCaseSpy() let withdrawalUseCase = WithdrawalUseCaseSpy() + let imageUploadUseCase = ImageUploadUseCaseSpy() let intent = MyPageIntent( model: model, myPageDelegate: DummyMyPageDelegate(), fetchMyProfileUseCase: fetchMyProfileUseCase, logoutUseCase: logoutUseCase, - withdrawalUseCase: withdrawalUseCase + withdrawalUseCase: withdrawalUseCase, + imageUploadUseCase: imageUploadUseCase ) MyPageView( container: .init( diff --git a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift index b440b03f..6192e7a8 100644 --- a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift +++ b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift @@ -1,6 +1,7 @@ import AuthDomainInterface import BaseFeature import MyPageFeatureInterface +import FileDomainInterface import NeedleFoundation import SwiftUI import UserDomainInterface @@ -10,6 +11,7 @@ public protocol MyPageDependency: Dependency { var userDomainBuildable: any UserDomainBuildable { get } var authDomainBuildable: any AuthDomainBuildable { get } var techStackAppendBuildable: any TechStackAppendBuildable { get } + var fileDomainBuildable: any FileDomainBuildable { get } } public final class MyPageComponent: Component, MyPageBuildable { @@ -20,7 +22,8 @@ public final class MyPageComponent: Component, MyPageBuildable myPageDelegate: delegate, fetchMyProfileUseCase: dependency.userDomainBuildable.fetchMyProfileUseCase, logoutUseCase: dependency.authDomainBuildable.logoutUseCase, - withdrawalUseCase: dependency.authDomainBuildable.withdrawalUseCase + withdrawalUseCase: dependency.authDomainBuildable.withdrawalUseCase, + imageUploadUseCase: dependency.fileDomainBuildable.imageUploadUseCase ) let container = MVIContainer( intent: intent as MyPageIntentProtocol, diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index fded23f7..ed776b7f 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -1,6 +1,7 @@ import Foundation import UserDomainInterface import AuthDomainInterface +import FileDomainInterface import MyPageFeatureInterface final class MyPageIntent: MyPageIntentProtocol { @@ -9,19 +10,22 @@ final class MyPageIntent: MyPageIntentProtocol { private let fetchMyProfileUseCase: any FetchMyProfileUseCase private let logoutUseCase: any LogoutUseCase private let withdrawalUseCase: any WithdrawalUseCase + let imageUploadUseCase: any ImageUploadUseCase init( model: any MyPageActionProtocol, myPageDelegate: any MyPageDelegate, fetchMyProfileUseCase: any FetchMyProfileUseCase, logoutUseCase: any LogoutUseCase, - withdrawalUseCase: any WithdrawalUseCase + withdrawalUseCase: any WithdrawalUseCase, + imageUploadUseCase: any ImageUploadUseCase ) { self.model = model self.myPageDelegate = myPageDelegate self.fetchMyProfileUseCase = fetchMyProfileUseCase self.logoutUseCase = logoutUseCase self.withdrawalUseCase = withdrawalUseCase + self.imageUploadUseCase = imageUploadUseCase } func onAppear() { diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift index 77ff8238..34328876 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift @@ -81,7 +81,15 @@ extension MyPageIntent: MyPageProfileIntentProtocol { func imageDidSelected(imageResult: PickedImageResult?) { guard let imageResult else { return } - #warning("이미지 업로드 후 model에서 profile URL 변경") + Task { + do { + async let profileImageURL = imageUploadUseCase.execute( + image: imageResult.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), + fileName: imageResult.fileName + ) + try await model?.updateProfileURL(url: profileImageURL) + } + } } func cameraIsRequired() { diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift index 68def5b8..f4984227 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift @@ -42,7 +42,15 @@ extension MyPageIntent: MyPageProjectIntentProtocol { } func updateIconImage(index: Int, image: PickedImageResult) { - #warning("이미지 업로드 후 model에서 iconURL 변경") + Task { + do { + async let iconImageURL = imageUploadUseCase.execute( + image: image.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), + fileName: image.fileName + ) + try await model?.updateIconImage(index: index, imageURL: iconImageURL) + } + } } func appendPreviewImageButtonDidTap(index: Int) { @@ -51,7 +59,15 @@ extension MyPageIntent: MyPageProjectIntentProtocol { } func appendPreviewImage(index: Int, image: PickedImageResult) { - #warning("이미지 업로드 후 model에서 preview 이미지 추가") + Task { + do { + async let previewImageURL = imageUploadUseCase.execute( + image: image.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), + fileName: image.fileName + ) + try await model?.appendPreviewImage(index: index, imageURL: previewImageURL) + } + } } func removePreviewImageDidTap(index: Int, previewIndex: Int) { From e01e00308b94354b84000bd1eea4dcd18bd947ab Mon Sep 17 00:00:00 2001 From: baegteun Date: Fri, 18 Aug 2023 09:04:54 +0900 Subject: [PATCH 31/62] =?UTF-8?q?:lipstick:=20::=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8,=20=EC=88=98=EC=83=81=20Section=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/MyPageFeature/Project.swift | 1 + .../Sources/Scene/MyPagePrizeView.swift | 20 +++++------ .../Sources/Scene/MyPageProjectView.swift | 36 +++++++++---------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index 1414cf32..e4676331 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -15,6 +15,7 @@ let project = Project.makeModule( demoDependencies: [ .Domain.UserDomainTesting, .Domain.AuthDomainTesting, + .Domain.FileDomainTesting, .Domain.StudentDomainTesting ] ) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift index dacab54f..31cb72b7 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift @@ -38,9 +38,17 @@ struct MyPagePrizeView: View { @ViewBuilder func prizeListRowView(index: Int) -> some View { let collapsed = state.collapsedPrize[safe: index] ?? false - VStack(alignment: .leading, spacing: 24) { + Section { + ConditionView(!collapsed) { + prizeName(index: index) + + prizePrize(index: index) + + prizePrizeAt(index: index) + } + } header: { HStack(spacing: 16) { - SMSText("수상", font: .title1) + SMSText(state.prizeList[safe: index]?.name ?? "", font: .title1) .foregroundColor(.sms(.system(.black))) Spacer() @@ -57,14 +65,6 @@ struct MyPagePrizeView: View { } } .padding(.bottom, 8) - - ConditionView(!collapsed) { - prizeName(index: index) - - prizePrize(index: index) - - prizePrizeAt(index: index) - } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift index 87d106ca..12a84dea 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift @@ -46,9 +46,25 @@ struct MyPageProjectView: View { @ViewBuilder func projectListRowView(index: Int, geometry: GeometryProxy) -> some View { let collapsed = state.collapsedProject[safe: index] ?? false - VStack(alignment: .leading, spacing: 24) { + Section { + ConditionView(!collapsed) { + projectName(index: index) + + projectIcon(index: index) + + projectPreviewImageList(index: index) + + projectContentTextEditor(index: index) + + projectTechStack(geometry: geometry, index: index) + + projectDuration(index: index) + + projectRelatedLink(index: index, geometry: geometry) + } + } header: { HStack(spacing: 16) { - SMSText("프로젝트", font: .title1) + SMSText(state.projectList[safe: index]?.name ?? "", font: .title1) .foregroundColor(.sms(.system(.black))) Spacer() @@ -65,22 +81,6 @@ struct MyPageProjectView: View { } } .padding(.bottom, 8) - - ConditionView(!collapsed) { - projectName(index: index) - - projectIcon(index: index) - - projectPreviewImageList(index: index) - - projectContentTextEditor(index: index) - - projectTechStack(geometry: geometry, index: index) - - projectDuration(index: index) - - projectRelatedLink(index: index, geometry: geometry) - } } } } From e1bd2a9751ceba27242db4527daa20137d5b4813 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:20:03 +0900 Subject: [PATCH 32/62] :sparkles: Techstack -> Techstacks --- ...DTO.swift => InputStudentInformationRequestDTO.swift} | 0 .../DTO/Request/ModifyStudentInformationRequestDTO.swift | 9 +++++++++ .../Interface/UseCase/ModifyInformationUseCase.swift | 5 +++++ .../Sources/UseCase/ModifyInformationUseCaseImpl.swift | 9 +++++++++ .../Testing/UseCase/InputInformationUseCaseSpy.swift | 9 +++++++++ .../Sources/Scene/View/ImageMethodPickerView.swift | 9 +++++++++ .../Sources/Scene/View/ImageMethodRowView.swift | 9 +++++++++ .../MyPageFeature/Sources/Scene/View/MajorRowView.swift | 9 +++++++++ 8 files changed, 59 insertions(+) rename Projects/Domain/StudentDomain/Interface/DTO/Request/{StudentRequestDTO.swift => InputStudentInformationRequestDTO.swift} (100%) create mode 100644 Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift create mode 100644 Projects/Domain/StudentDomain/Interface/UseCase/ModifyInformationUseCase.swift create mode 100644 Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift create mode 100644 Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift create mode 100644 Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift diff --git a/Projects/Domain/StudentDomain/Interface/DTO/Request/StudentRequestDTO.swift b/Projects/Domain/StudentDomain/Interface/DTO/Request/InputStudentInformationRequestDTO.swift similarity index 100% rename from Projects/Domain/StudentDomain/Interface/DTO/Request/StudentRequestDTO.swift rename to Projects/Domain/StudentDomain/Interface/DTO/Request/InputStudentInformationRequestDTO.swift diff --git a/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift b/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift new file mode 100644 index 00000000..21ebb79b --- /dev/null +++ b/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift @@ -0,0 +1,9 @@ +// +// ModifyStudentInformationRequestDTO.swift +// StudentDomain +// +// Created by sunghun on 8/17/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation diff --git a/Projects/Domain/StudentDomain/Interface/UseCase/ModifyInformationUseCase.swift b/Projects/Domain/StudentDomain/Interface/UseCase/ModifyInformationUseCase.swift new file mode 100644 index 00000000..6f3ddec6 --- /dev/null +++ b/Projects/Domain/StudentDomain/Interface/UseCase/ModifyInformationUseCase.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol ModifyInformationUseCase { + func execute(req: ModifyStudentInformationRequestDTO) async throws +} diff --git a/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift b/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift new file mode 100644 index 00000000..adc710d5 --- /dev/null +++ b/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift @@ -0,0 +1,9 @@ +// +// ModifyInformationUseCase.swift +// StudentDomain +// +// Created by sunghun on 8/17/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation diff --git a/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift b/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift new file mode 100644 index 00000000..3c7be162 --- /dev/null +++ b/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift @@ -0,0 +1,9 @@ +// +// InputInformationUseCaseSpy.swift +// StudentDomainTesting +// +// Created by sunghun on 8/17/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift new file mode 100644 index 00000000..7a2e0685 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift @@ -0,0 +1,9 @@ +// +// ImageMethodPickerView.swift +// MyPageFeature +// +// Created by sunghun on 8/16/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift new file mode 100644 index 00000000..f65d3cd0 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift @@ -0,0 +1,9 @@ +// +// ImageMethodRowView.swift +// MyPageFeature +// +// Created by sunghun on 8/16/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift new file mode 100644 index 00000000..992bf175 --- /dev/null +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift @@ -0,0 +1,9 @@ +// +// MajorRowView.swift +// MyPageFeature +// +// Created by sunghun on 8/16/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation From 2da4b76a56600590e038a689e4a2e3469967d5d5 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:47:37 +0900 Subject: [PATCH 33/62] =?UTF-8?q?:art:=20[#220]=20MyPageFeature=20/=20Icon?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json | 2 +- .../RedLogout.svg => Logout.imageset/Logout.svg} | 2 +- .../Icon/Icons.xcassets/Photo.imageset/Contents.json | 2 +- .../Icon/Icons.xcassets/Photo.imageset/Image.svg | 6 ------ .../Icon/Icons.xcassets/Photo.imageset/icon.svg | 5 +++++ Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift | 8 ++++---- .../Testing/UseCase/ModifyInformationUseCaseSpy.swift | 9 +++++++++ 7 files changed, 21 insertions(+), 13 deletions(-) rename Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/{RedLogout.imageset => Logout.imageset}/Contents.json (77%) rename Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/{RedLogout.imageset/RedLogout.svg => Logout.imageset/Logout.svg} (83%) delete mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Image.svg create mode 100644 Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/icon.svg create mode 100644 Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/RedLogout.imageset/Contents.json b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Logout.imageset/Contents.json similarity index 77% rename from Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/RedLogout.imageset/Contents.json rename to Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Logout.imageset/Contents.json index 2376ab4c..94ba0f9a 100644 --- a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/RedLogout.imageset/Contents.json +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Logout.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "RedLogout.svg", + "filename" : "Logout.svg", "idiom" : "universal" } ], diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/RedLogout.imageset/RedLogout.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Logout.imageset/Logout.svg similarity index 83% rename from Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/RedLogout.imageset/RedLogout.svg rename to Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Logout.imageset/Logout.svg index 026de5e6..62a0eabd 100644 --- a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/RedLogout.imageset/RedLogout.svg +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Logout.imageset/Logout.svg @@ -1,4 +1,4 @@ - + diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Contents.json b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Contents.json index d2b87ab2..52ca28db 100644 --- a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Contents.json +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Image.svg", + "filename" : "icon.svg", "idiom" : "universal" } ], diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Image.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Image.svg deleted file mode 100644 index 88ab692b..00000000 --- a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/Image.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/icon.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/icon.svg new file mode 100644 index 00000000..7817b176 --- /dev/null +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Photo.imageset/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift b/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift index 3b7d1866..579da745 100644 --- a/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift +++ b/Projects/Core/DesignSystem/Sources/Icon/SMSIcon.swift @@ -31,12 +31,12 @@ public struct SMSIcon: View { case plus case profile case profileSmallPlus - case redLogout case redPerson case search case smsLogo case trash case leftArrow + case logout case logoutLine case smallPlus case upArrow @@ -104,12 +104,12 @@ public struct SMSIcon: View { case .leftArrow: return DesignSystemAsset.Icons.leftArrow.swiftUIImage + case .logout: + return DesignSystemAsset.Icons.logout.swiftUIImage + case .logoutLine: return DesignSystemAsset.Icons.logoutLine.swiftUIImage - case .redLogout: - return DesignSystemAsset.Icons.redLogout.swiftUIImage - case .redPerson: return DesignSystemAsset.Icons.redPerson.swiftUIImage diff --git a/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift b/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift new file mode 100644 index 00000000..44e0c753 --- /dev/null +++ b/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift @@ -0,0 +1,9 @@ +// +// ModifyInformationUseCaseSPY.swift +// StudentDomainTesting +// +// Created by sunghun on 8/21/23. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation From 4c157b8acdf71ecb7411a817e0edddbdd4140416 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:48:46 +0900 Subject: [PATCH 34/62] =?UTF-8?q?:goal=5Fnet:=20[#220]=20FileDomain=20/=20?= =?UTF-8?q?hwp=20=EC=97=90=EB=9F=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/FileDomain/Interface/Error/FileDomainError.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Projects/Domain/FileDomain/Interface/Error/FileDomainError.swift b/Projects/Domain/FileDomain/Interface/Error/FileDomainError.swift index 98a5c7f0..6108e650 100644 --- a/Projects/Domain/FileDomain/Interface/Error/FileDomainError.swift +++ b/Projects/Domain/FileDomain/Interface/Error/FileDomainError.swift @@ -1,7 +1,6 @@ import Foundation public enum FileDomainError: Error { - case notHwpFile case notImageType case internalServerError } @@ -9,9 +8,6 @@ public enum FileDomainError: Error { extension FileDomainError: LocalizedError { public var errorDescription: String? { switch self { - case .notHwpFile: - return "파일 형식이 hwp 혹은 hwpx인 파일이 아닙니다." - case .notImageType: return "이미지 형식이 jpg, jpeg, png, heic인 이미지가 아닙니다." From 1bf5c37c2827d1b9627c4211b2a09235bc6897b9 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:53:04 +0900 Subject: [PATCH 35/62] :sparkles: [#220] StudentDomain / ModifyInformationUseCase --- .../Interface/DI/StudentDomainBuildable.swift | 1 + .../ModifyStudentInformationRequestDTO.swift | 170 +++++++++++++++++- .../DataSource/RemoteStudentDataSource.swift | 1 + .../Repository/StudentRepository.swift | 1 + .../Sources/DI/StudentDomainComponent.swift | 3 + .../RemoteStudentDataSourceImpl.swift | 4 + .../Sources/Endpoint/StudentEndpoint.swift | 9 + .../Repository/StudentRepositoryImpl.swift | 4 + .../ModifyInformationUseCaseImpl.swift | 20 ++- .../UseCase/ModifyInformationUseCaseSpy.swift | 15 +- 10 files changed, 204 insertions(+), 24 deletions(-) diff --git a/Projects/Domain/StudentDomain/Interface/DI/StudentDomainBuildable.swift b/Projects/Domain/StudentDomain/Interface/DI/StudentDomainBuildable.swift index 9e1f63c8..a157a1b9 100644 --- a/Projects/Domain/StudentDomain/Interface/DI/StudentDomainBuildable.swift +++ b/Projects/Domain/StudentDomain/Interface/DI/StudentDomainBuildable.swift @@ -3,4 +3,5 @@ public protocol StudentDomainBuildable { var studentRepository: any StudentRepository { get } var fetchStudentListUseCase: any FetchStudentListUseCase { get } var fetchStudentDetailUSeCase: any FetchStudentDetailUseCase { get } + var modifyInformationUseCase: any ModifyInformationUseCase { get } } diff --git a/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift b/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift index 21ebb79b..2f592e93 100644 --- a/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift +++ b/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift @@ -1,9 +1,163 @@ -// -// ModifyStudentInformationRequestDTO.swift -// StudentDomain -// -// Created by sunghun on 8/17/23. -// Copyright © 2023 com.msg. All rights reserved. -// - import Foundation + +public struct ModifyStudentInformationRequestDTO: Encodable { + public let certificate: [String] + public let contactEmail: String + public let formOfEmployment: FormOfEmployment + public let gsmAuthenticationScore: Int + public let introduce: String + public let languageCertificate: [LanguageCertificate] + public let major: String + public let militaryService: MilitaryServiceType + public let portfolioURL: String + public let profileImgURL: String + public let region: [String] + public let salary: Int + public let techStacks: [String] + public let projects: [Project] + public let prizes: [Prize] + + public init( + certificate: [String], + contactEmail: String, + formOfEmployment: FormOfEmployment, + gsmAuthenticationScore: Int, + introduce: String, + languageCertificate: [LanguageCertificate], + major: String, + militaryService: MilitaryServiceType, + portfolioURL: String, + profileImgURL: String, + region: [String], + salary: Int, + techStacks: [String], + projects: [Project] = [], + prizes: [Prize] = [] + ) { + self.certificate = certificate + self.contactEmail = contactEmail + self.formOfEmployment = formOfEmployment + self.gsmAuthenticationScore = gsmAuthenticationScore + self.introduce = introduce + self.languageCertificate = languageCertificate + self.major = major + self.militaryService = militaryService + self.portfolioURL = portfolioURL + self.profileImgURL = profileImgURL + self.region = region + self.salary = salary + self.techStacks = techStacks + self.projects = projects + self.prizes = prizes + } + + enum CodingKeys: String, CodingKey { + case certificate + case contactEmail + case formOfEmployment + case gsmAuthenticationScore + case introduce + case languageCertificate + case major + case militaryService + case portfolioURL = "portfolioUrl" + case profileImgURL = "profileImgUrl" + case region + case salary + case techStacks + case projects + case prizes + } +} + +public extension ModifyStudentInformationRequestDTO { + struct LanguageCertificate: Encodable { + public let languageCertificateName: String + public let score: String + + public init(languageCertificateName: String, score: String) { + self.languageCertificateName = languageCertificateName + self.score = score + } + } + + struct Project: Encodable { + public let name: String + public let iconImageURL: String + public let previewImageURLs: [String] + public let description: String + public let links: [Link] + public let techStacks: [String] + public let myActivity: String + public let inProgress: InProgress + + public init( + name: String, + iconImageURL: String, + previewImageURLs: [String], + description: String, + links: [Link], + techStacks: [String], + myActivity: String, + inProgress: InProgress + ) { + self.name = name + self.iconImageURL = iconImageURL + self.previewImageURLs = previewImageURLs + self.description = description + self.links = links + self.techStacks = techStacks + self.myActivity = myActivity + self.inProgress = inProgress + } + + enum CodingKeys: String, CodingKey { + case name + case iconImageURL = "icon" + case previewImageURLs = "previewImages" + case description + case links + case techStacks + case myActivity + case inProgress + } + } + + struct Prize: Encodable { + public let name: String + public let type: String + public let date: String + + public init( + name: String, + type: String, + date: String + ) { + self.name = name + self.type = type + self.date = date + } + } +} + +public extension ModifyStudentInformationRequestDTO.Project { + struct Link: Encodable { + public let name: String + public let url: String + + public init(name: String, url: String) { + self.name = name + self.url = url + } + } + + struct InProgress: Encodable { + public let start: String + public let end: String? + + public init(start: String, end: String?) { + self.start = start + self.end = end + } + } +} diff --git a/Projects/Domain/StudentDomain/Interface/DataSource/RemoteStudentDataSource.swift b/Projects/Domain/StudentDomain/Interface/DataSource/RemoteStudentDataSource.swift index 7318889d..2df165b3 100644 --- a/Projects/Domain/StudentDomain/Interface/DataSource/RemoteStudentDataSource.swift +++ b/Projects/Domain/StudentDomain/Interface/DataSource/RemoteStudentDataSource.swift @@ -6,4 +6,5 @@ public protocol RemoteStudentDataSource { func fetchStudentDetailByStudent(userID: String) async throws -> StudentDetailEntity func fetchStudentDetailByGuest(userID: String) async throws -> StudentDetailEntity func fetchStudentDetailByTeacher(userID: String) async throws -> StudentDetailEntity + func modifyInformation(req: ModifyStudentInformationRequestDTO) async throws } diff --git a/Projects/Domain/StudentDomain/Interface/Repository/StudentRepository.swift b/Projects/Domain/StudentDomain/Interface/Repository/StudentRepository.swift index d39cfd2c..f00dfcbc 100644 --- a/Projects/Domain/StudentDomain/Interface/Repository/StudentRepository.swift +++ b/Projects/Domain/StudentDomain/Interface/Repository/StudentRepository.swift @@ -6,4 +6,5 @@ public protocol StudentRepository { func fetchStudentDetailByStudent(userID: String) async throws -> StudentDetailEntity func fetchStudentDetailByGuest(userID: String) async throws -> StudentDetailEntity func fetchStudentDetailByTeacher(userID: String) async throws -> StudentDetailEntity + func modifyInformation(req: ModifyStudentInformationRequestDTO) async throws } diff --git a/Projects/Domain/StudentDomain/Sources/DI/StudentDomainComponent.swift b/Projects/Domain/StudentDomain/Sources/DI/StudentDomainComponent.swift index 082d62f4..dfac8f40 100644 --- a/Projects/Domain/StudentDomain/Sources/DI/StudentDomainComponent.swift +++ b/Projects/Domain/StudentDomain/Sources/DI/StudentDomainComponent.swift @@ -16,6 +16,9 @@ public final class StudentDomainComponent: Component, S public var fetchStudentDetailUSeCase: any FetchStudentDetailUseCase { FetchStudentDetailUseCaseImpl(studentRepository: studentRepository) } + public var modifyInformationUseCase: any ModifyInformationUseCase { + ModifyInformationUseCaseImpl(studentRepository: studentRepository) + } public var studentRepository: any StudentRepository { StudentRepositoryImpl(remoteStudentDataSource: remoteStudentDataSource) } diff --git a/Projects/Domain/StudentDomain/Sources/DataSource/RemoteStudentDataSourceImpl.swift b/Projects/Domain/StudentDomain/Sources/DataSource/RemoteStudentDataSourceImpl.swift index 82633c85..032aea2c 100644 --- a/Projects/Domain/StudentDomain/Sources/DataSource/RemoteStudentDataSourceImpl.swift +++ b/Projects/Domain/StudentDomain/Sources/DataSource/RemoteStudentDataSourceImpl.swift @@ -34,4 +34,8 @@ final class RemoteStudentDataSourceImpl: BaseRemoteDataSource, ) .toDomain() } + + func modifyInformation(req: ModifyStudentInformationRequestDTO) async throws { + try await request(.modifyInformation(req)) + } } diff --git a/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift b/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift index 44c3f169..23bde0a7 100644 --- a/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift +++ b/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift @@ -8,6 +8,7 @@ enum StudentEndpoint { case fetchStudentDetailByStudent(userID: String) case fetchStudentDetailByGuest(userID: String) case fetchStudentDetailByTeacher(userID: String) + case modifyInformation(ModifyStudentInformationRequestDTO) } extension StudentEndpoint: SMSEndpoint { @@ -33,6 +34,9 @@ extension StudentEndpoint: SMSEndpoint { case let .fetchStudentDetailByTeacher(userID): return .get("/teacher/\(userID)") + + case .modifyInformation: + return .put("") } } @@ -105,6 +109,11 @@ extension StudentEndpoint: SMSEndpoint { case .fetchStudentDetailByTeacher: return [:] + + case .modifyInformation: + return [ + 400: .invalidRequest + ] } } } diff --git a/Projects/Domain/StudentDomain/Sources/Repository/StudentRepositoryImpl.swift b/Projects/Domain/StudentDomain/Sources/Repository/StudentRepositoryImpl.swift index d675f06c..60548584 100644 --- a/Projects/Domain/StudentDomain/Sources/Repository/StudentRepositoryImpl.swift +++ b/Projects/Domain/StudentDomain/Sources/Repository/StudentRepositoryImpl.swift @@ -27,4 +27,8 @@ struct StudentRepositoryImpl: StudentRepository { func fetchStudentDetailByTeacher(userID: String) async throws -> StudentDetailEntity { try await remoteStudentDataSource.fetchStudentDetailByTeacher(userID: userID) } + + func modifyInformation(req: ModifyStudentInformationRequestDTO) async throws { + try await remoteStudentDataSource.modifyInformation(req: req) + } } diff --git a/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift b/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift index adc710d5..c87f61e5 100644 --- a/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift +++ b/Projects/Domain/StudentDomain/Sources/UseCase/ModifyInformationUseCaseImpl.swift @@ -1,9 +1,13 @@ -// -// ModifyInformationUseCase.swift -// StudentDomain -// -// Created by sunghun on 8/17/23. -// Copyright © 2023 com.msg. All rights reserved. -// +import StudentDomainInterface -import Foundation +struct ModifyInformationUseCaseImpl: ModifyInformationUseCase { + private let studentRepository: any StudentRepository + + init(studentRepository: any StudentRepository) { + self.studentRepository = studentRepository + } + + func execute(req: ModifyStudentInformationRequestDTO) async throws { + try await studentRepository.modifyInformation(req: req) + } +} diff --git a/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift b/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift index 44e0c753..2ec18e31 100644 --- a/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift +++ b/Projects/Domain/StudentDomain/Testing/UseCase/ModifyInformationUseCaseSpy.swift @@ -1,9 +1,8 @@ -// -// ModifyInformationUseCaseSPY.swift -// StudentDomainTesting -// -// Created by sunghun on 8/21/23. -// Copyright © 2023 com.msg. All rights reserved. -// +import StudentDomainInterface -import Foundation +final class ModifyInformationUseCaseSpy: ModifyInformationUseCase { + var executeCallCount = 0 + func execute(req: ModifyStudentInformationRequestDTO) async throws { + executeCallCount += 1 + } +} From 05fc3d7d4e2bf1e00edb22e174a0b8d4f02b956f Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:54:01 +0900 Subject: [PATCH 36/62] =?UTF-8?q?:recycle:=20[#220]=20Domain=20/=20S=20?= =?UTF-8?q?=EB=B6=99=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/AuthRepositoryImpl.swift | 2 +- .../InputStudentInformationRequestDTO.swift | 32 +++++++++---------- .../Entity/SingleStudentEntity.swift | 6 ++-- ...FetchStudentDetailByGuestResponseDTO.swift | 8 ++--- ...tchStudentDetailByStudentResponseDTO.swift | 4 +-- .../FetchStudentListResponseDTO.swift | 4 +-- .../UseCase/InputInformationUseCaseSpy.swift | 15 ++++----- .../FetchTechStackListResponseDTO.swift | 6 ++-- .../RemoteTechStackDataSourceImpl.swift | 2 +- .../Response/FetchMyProfileResponseDTO.swift | 21 ++++++++++++ 10 files changed, 60 insertions(+), 40 deletions(-) diff --git a/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift b/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift index 12dae8e0..1c41f5b1 100644 --- a/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift +++ b/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift @@ -19,7 +19,7 @@ struct AuthRepositoryImpl: AuthRepository { func logout() async throws { #warning("통신 오류 남") -// try await remoteAuthDataSource.logout() + try await remoteAuthDataSource.logout() try await localAuthDataSource.logout() } diff --git a/Projects/Domain/StudentDomain/Interface/DTO/Request/InputStudentInformationRequestDTO.swift b/Projects/Domain/StudentDomain/Interface/DTO/Request/InputStudentInformationRequestDTO.swift index ecde9a72..92fd7a06 100644 --- a/Projects/Domain/StudentDomain/Interface/DTO/Request/InputStudentInformationRequestDTO.swift +++ b/Projects/Domain/StudentDomain/Interface/DTO/Request/InputStudentInformationRequestDTO.swift @@ -1,70 +1,70 @@ import Foundation public struct InputStudentInformationRequestDTO: Encodable { - public let certificate: [String] + public let certificates: [String] public let contactEmail: String public let formOfEmployment: FormOfEmployment public let gsmAuthenticationScore: Int public let introduce: String - public let languageCertificate: [LanguageCertificate] + public let languageCertificates: [LanguageCertificate] public let major: String public let militaryService: MilitaryServiceType public let portfolioURL: String public let profileImgURL: String - public let region: [String] + public let regions: [String] public let salary: Int - public let techStack: [String] + public let techStacks: [String] public let projects: [Project] public let prizes: [Prize] public init( - certificate: [String], + certificates: [String], contactEmail: String, formOfEmployment: FormOfEmployment, gsmAuthenticationScore: Int, introduce: String, - languageCertificate: [LanguageCertificate], + languageCertificates: [LanguageCertificate], major: String, militaryService: MilitaryServiceType, portfolioURL: String, profileImgURL: String, - region: [String], + regions: [String], salary: Int, - techStack: [String], + techStacks: [String], projects: [Project] = [], prizes: [Prize] = [] ) { - self.certificate = certificate + self.certificates = certificates self.contactEmail = contactEmail self.formOfEmployment = formOfEmployment self.gsmAuthenticationScore = gsmAuthenticationScore self.introduce = introduce - self.languageCertificate = languageCertificate + self.languageCertificates = languageCertificates self.major = major self.militaryService = militaryService self.portfolioURL = portfolioURL self.profileImgURL = profileImgURL - self.region = region + self.regions = regions self.salary = salary - self.techStack = techStack + self.techStacks = techStacks self.projects = projects self.prizes = prizes } enum CodingKeys: String, CodingKey { - case certificate + case certificates case contactEmail case formOfEmployment case gsmAuthenticationScore case introduce - case languageCertificate + case languageCertificates case major case militaryService case portfolioURL = "portfolioUrl" case profileImgURL = "profileImgUrl" - case region + case regions case salary - case techStack + case techStacks case projects case prizes } diff --git a/Projects/Domain/StudentDomain/Interface/Entity/SingleStudentEntity.swift b/Projects/Domain/StudentDomain/Interface/Entity/SingleStudentEntity.swift index fcbf58da..cc96bd69 100644 --- a/Projects/Domain/StudentDomain/Interface/Entity/SingleStudentEntity.swift +++ b/Projects/Domain/StudentDomain/Interface/Entity/SingleStudentEntity.swift @@ -5,13 +5,13 @@ public struct SingleStudentEntity: Equatable { public let profileImageURL: String public let name: String public let major: String - public let techStack: [String] + public let techStacks: [String] - public init(id: String, profileImageURL: String, name: String, major: String, techStack: [String]) { + public init(id: String, profileImageURL: String, name: String, major: String, techStacks: [String]) { self.id = id self.profileImageURL = profileImageURL self.name = name self.major = major - self.techStack = techStack + self.techStacks = techStacks } } diff --git a/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByGuestResponseDTO.swift b/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByGuestResponseDTO.swift index d107646d..a920163c 100644 --- a/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByGuestResponseDTO.swift +++ b/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByGuestResponseDTO.swift @@ -6,7 +6,7 @@ public struct FetchStudentDetailByGuestResponseDTO: Decodable { public let introduce: String public let major: String public let profileImg: String - public let techStack: [String] + public let techStacks: [String] public let projects: [ProjectResponseDTO] public let prizes: [PrizeResponseDTO] @@ -15,7 +15,7 @@ public struct FetchStudentDetailByGuestResponseDTO: Decodable { introduce: String, major: String, profileImg: String, - techStack: [String], + techStacks: [String], projects: [ProjectResponseDTO], prizes: [PrizeResponseDTO] ) { @@ -23,7 +23,7 @@ public struct FetchStudentDetailByGuestResponseDTO: Decodable { self.introduce = introduce self.major = major self.profileImg = profileImg - self.techStack = techStack + self.techStacks = techStacks self.projects = projects self.prizes = prizes } @@ -36,7 +36,7 @@ public extension FetchStudentDetailByGuestResponseDTO { introduce: introduce, major: major, profileImageURL: profileImg, - techStacks: techStack, + techStacks: techStacks, projects: projects.map { $0.toDomain() }, prizes: prizes.map { $0.toDomain() } ) diff --git a/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByStudentResponseDTO.swift b/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByStudentResponseDTO.swift index 8ee7180f..3b64a26e 100644 --- a/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByStudentResponseDTO.swift +++ b/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentDetailByStudentResponseDTO.swift @@ -10,7 +10,7 @@ public struct FetchStudentDetailByStudentResponseDTO: Decodable { public let department: DepartmentType public let major: String public let profileImg: String - public let techStack: [String] + public let techStacks: [String] public let projects: [ProjectResponseDTO] public let prizes: [PrizeResponseDTO] } @@ -22,7 +22,7 @@ public extension FetchStudentDetailByStudentResponseDTO { introduce: introduce, major: major, profileImageURL: profileImg, - techStacks: techStack, + techStacks: techStacks, projects: projects.map { $0.toDomain() }, prizes: prizes.map { $0.toDomain() }, detailInfoByStudent: .init( diff --git a/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentListResponseDTO.swift b/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentListResponseDTO.swift index a37c8ee6..de391ba8 100644 --- a/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentListResponseDTO.swift +++ b/Projects/Domain/StudentDomain/Sources/DTO/Response/FetchStudentListResponseDTO.swift @@ -11,7 +11,7 @@ public struct FetchStudentListResponseDTO: Decodable { public let profileImg: String public let name: String public let major: String - public let techStack: [String] + public let techStacks: [String] } enum CodingKeys: String, CodingKey { @@ -27,7 +27,7 @@ public extension FetchStudentListResponseDTO.SingleStudentResponseDTO { profileImageURL: profileImg, name: name, major: major, - techStack: techStack + techStacks: techStacks ) } } diff --git a/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift b/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift index 3c7be162..580c220b 100644 --- a/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift +++ b/Projects/Domain/StudentDomain/Testing/UseCase/InputInformationUseCaseSpy.swift @@ -1,9 +1,8 @@ -// -// InputInformationUseCaseSpy.swift -// StudentDomainTesting -// -// Created by sunghun on 8/17/23. -// Copyright © 2023 com.msg. All rights reserved. -// +import StudentDomainInterface -import Foundation +final class InputInformationUseCaseSpy: InputInformationUseCase { + var executeCallCount = 0 + func execute(req: InputStudentInformationRequestDTO) async throws { + executeCallCount += 1 + } +} diff --git a/Projects/Domain/TechStackDomain/Sources/DTO/Response/FetchTechStackListResponseDTO.swift b/Projects/Domain/TechStackDomain/Sources/DTO/Response/FetchTechStackListResponseDTO.swift index b1e5cd69..d3ca1d3c 100644 --- a/Projects/Domain/TechStackDomain/Sources/DTO/Response/FetchTechStackListResponseDTO.swift +++ b/Projects/Domain/TechStackDomain/Sources/DTO/Response/FetchTechStackListResponseDTO.swift @@ -1,9 +1,9 @@ import Foundation public struct FetchTechStackListResponseDTO: Decodable { - public let techStack: [String] + public let techStacks: [String] - public init(techStack: [String]) { - self.techStack = techStack + public init(techStacks: [String]) { + self.techStacks = techStacks } } diff --git a/Projects/Domain/TechStackDomain/Sources/DataSource/RemoteTechStackDataSourceImpl.swift b/Projects/Domain/TechStackDomain/Sources/DataSource/RemoteTechStackDataSourceImpl.swift index 8feaa054..c3e1737f 100644 --- a/Projects/Domain/TechStackDomain/Sources/DataSource/RemoteTechStackDataSourceImpl.swift +++ b/Projects/Domain/TechStackDomain/Sources/DataSource/RemoteTechStackDataSourceImpl.swift @@ -7,6 +7,6 @@ final class RemoteTechStackDataSourceImpl: RemoteTechStackDataSource { func fetchTechStackList(keyword: String) async throws -> [String] { try await request(.fetchTechStackList(keyword: keyword), dto: FetchTechStackListResponseDTO.self) - .techStack + .techStacks } } diff --git a/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift b/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift index 0a4910f1..a228059e 100644 --- a/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift +++ b/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift @@ -21,6 +21,27 @@ struct FetchMyProfileResponseDTO: Decodable { let languageCertificates: [LanguageCertificateResponseDTO] let certificates: [String] let techStacks: [String] + + enum CodingKeys: String, CodingKey { + case name + case introduce + case portfolioURL = "portfolioUrl" + case grade + case classNum + case number + case department + case major + case profileImageURL = "profileImg" + case contactEmail + case gsmAuthenticationScore + case formOfEmployment + case regions + case militaryService + case salary + case languageCertificates + case certificates + case techStacks + } } extension FetchMyProfileResponseDTO { From a545e08a027fe6c638ff4f84bab5ff30472a5854 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:55:09 +0900 Subject: [PATCH 37/62] =?UTF-8?q?:fire:=20[#220]=20InputInformationFeature?= =?UTF-8?q?=20/=20hwp=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/InputInformationIntent.swift | 15 +++++++-------- .../Sources/Model/InputInformationModel.swift | 6 +++--- .../Model/InputInformationModelProtocol.swift | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift index 3fc5b777..a8bc6972 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Intent/InputInformationIntent.swift @@ -34,7 +34,6 @@ final class InputInformationIntent: InputInformationIntentProtocol { func completeToInputAllInfo(state: any InputInformationStateProtocol) { guard let inputProfileInfo = state.inputProfileInformationObject, - let inputSchoolLifeInfo = state.inputSchoolLifeInformationObject, let inputWorkInfo = state.inputWorkInfomationObject, let militaryServiceType = state.militaryServiceType else { @@ -51,19 +50,19 @@ final class InputInformationIntent: InputInformationIntentProtocol { ) let inputInformationRequest = try await InputStudentInformationRequestDTO( - certificate: state.certificates, + certificates: state.certificates, contactEmail: inputProfileInfo.contactEmail, formOfEmployment: FormOfEmployment(rawValue: inputWorkInfo.formOfEmployment) ?? .fullTime, - gsmAuthenticationScore: inputSchoolLifeInfo.gsmAuthenticationScore, + gsmAuthenticationScore: state.gsmAuthenticationScore, introduce: inputProfileInfo.introduce, - languageCertificate: state.languages, + languageCertificates: state.languages, major: inputProfileInfo.major, militaryService: militaryServiceType, portfolioURL: inputProfileInfo.portfoiloURL, profileImgURL: profileImageURL, - region: inputWorkInfo.workRegion, + regions: inputWorkInfo.workRegion, salary: inputWorkInfo.salary, - techStack: inputProfileInfo.techStack, + techStacks: inputProfileInfo.techStacks, projects: state.projects.concurrentMap { async let imageURL = self.imageUploadUseCase.execute( image: $0.iconImage?.data ?? .init(), @@ -117,8 +116,8 @@ extension InputInformationIntent: InputSchoolLifeDelegate { model?.prevButtonDidTap() } - func completeToInputSchoolLife(input: InputSchoolLifeInformationObject) { - model?.updateInputSchoolLifeInformationObject(object: input) + func completeToInputSchoolLife(gsmAuthenticationScore: Int) { + model?.updateInputSchoolLifeInformationObject(gsmAuthenticationScore: gsmAuthenticationScore) model?.nextButtonDidTap() } } diff --git a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift index 9cf8430a..9b60b8dc 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModel.swift @@ -12,7 +12,7 @@ final class InputInformationModel: ObservableObject, InputInformationStateProtoc @Published var isError: Bool = false var errorMessage: String = "알 수 없는 오류가 발생했습니다." var inputProfileInformationObject: InputProfileInformationObject? - var inputSchoolLifeInformationObject: InputSchoolLifeInformationObject? + var gsmAuthenticationScore: Int = 0 var inputWorkInfomationObject: InputWorkInformationObject? var certificates: [String] = [] var militaryServiceType: MilitaryServiceType? @@ -41,8 +41,8 @@ extension InputInformationModel: InputInformationActionProtocol { self.inputProfileInformationObject = object } - func updateInputSchoolLifeInformationObject(object: InputSchoolLifeInformationObject) { - self.inputSchoolLifeInformationObject = object + func updateInputSchoolLifeInformationObject(gsmAuthenticationScore: Int) { + self.gsmAuthenticationScore = gsmAuthenticationScore } func updateInputWorkInformationObject(object: InputWorkInformationObject) { diff --git a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift index a69540e7..1df39155 100644 --- a/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift +++ b/Projects/Feature/InputInformationFeature/Sources/Model/InputInformationModelProtocol.swift @@ -23,7 +23,7 @@ protocol InputInformationStateProtocol { var isError: Bool { get } var errorMessage: String { get } var inputProfileInformationObject: InputProfileInformationObject? { get } - var inputSchoolLifeInformationObject: InputSchoolLifeInformationObject? { get } + var gsmAuthenticationScore: Int { get } var inputWorkInfomationObject: InputWorkInformationObject? { get } var certificates: [String] { get } var militaryServiceType: MilitaryServiceType? { get } @@ -37,7 +37,7 @@ protocol InputInformationActionProtocol: AnyObject { func prevButtonDidTap() func nextButtonDidTap() func updateInputProfileInformationObject(object: InputProfileInformationObject) - func updateInputSchoolLifeInformationObject(object: InputSchoolLifeInformationObject) + func updateInputSchoolLifeInformationObject(gsmAuthenticationScore: Int) func updateInputWorkInformationObject(object: InputWorkInformationObject) func updateCertificates(certificates: [String]) func updateMilitaryServiceType(type: MilitaryServiceType) From 6d3775710285685dd930f009deb15b1323f81c96 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:57:40 +0900 Subject: [PATCH 38/62] =?UTF-8?q?:recycle:=20[#220]=20InputProfileInfoFeat?= =?UTF-8?q?ure=20/=20S=20=EB=B6=99=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/InputProfileDelegate.swift | 6 +++--- .../Sources/Intent/InputProfileInfoIntent.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Projects/Feature/InputProfileInfoFeature/Interface/InputProfileDelegate.swift b/Projects/Feature/InputProfileInfoFeature/Interface/InputProfileDelegate.swift index d00285f0..11187218 100644 --- a/Projects/Feature/InputProfileInfoFeature/Interface/InputProfileDelegate.swift +++ b/Projects/Feature/InputProfileInfoFeature/Interface/InputProfileDelegate.swift @@ -11,7 +11,7 @@ public struct InputProfileInformationObject { public let contactEmail: String public let major: String public let portfoiloURL: String - public let techStack: [String] + public let techStacks: [String] public init( profileImageData: Data, @@ -20,7 +20,7 @@ public struct InputProfileInformationObject { contactEmail: String, major: String, portfoiloURL: String, - techStack: [String] + techStacks: [String] ) { self.profileImageData = profileImageData self.profileImageFilename = profileImageFilename @@ -28,6 +28,6 @@ public struct InputProfileInformationObject { self.contactEmail = contactEmail self.major = major self.portfoiloURL = portfoiloURL - self.techStack = techStack + self.techStacks = techStacks } } diff --git a/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift b/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift index 5b4ae559..4b14872e 100644 --- a/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift +++ b/Projects/Feature/InputProfileInfoFeature/Sources/Intent/InputProfileInfoIntent.swift @@ -120,7 +120,7 @@ final class InputProfileInfoIntent: InputProfileInfoIntentProtocol { contactEmail: state.email, major: state.major, portfoiloURL: state.portfolioURL, - techStack: state.techStacks + techStacks: state.techStacks ) inputProfileDelegate?.completeToInputProfile(input: input) } From b0c1b935ae5ccbe39620e511d7c6c3ca0d7250ef Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:59:05 +0900 Subject: [PATCH 39/62] =?UTF-8?q?:lipstick:=20[#220]=20InputProfileInfoFea?= =?UTF-8?q?ture=20/=20BottomSheet=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/View/ImageMethodPickerView.swift | 4 ++-- .../Sources/Scene/View/ImageMethodRowView.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodPickerView.swift b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodPickerView.swift index 395e5477..729e673d 100644 --- a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodPickerView.swift +++ b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodPickerView.swift @@ -16,11 +16,11 @@ struct ImageMethodPickerView: View { var body: some View { VStack(spacing: 28) { Group { - ImageMethodRowView(title: "앨범에서 가져오기", icon: .photo) { + ImageMethodRowView(title: "앨범", icon: .photo) { albumAction() } - ImageMethodRowView(title: "카메라에서 촬영하기", icon: .camera) { + ImageMethodRowView(title: "카메라", icon: .camera) { cameraAction() } } diff --git a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodRowView.swift b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodRowView.swift index 12846ab9..1384e9d4 100644 --- a/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodRowView.swift +++ b/Projects/Feature/InputProfileInfoFeature/Sources/Scene/View/ImageMethodRowView.swift @@ -21,7 +21,7 @@ struct ImageMethodRowView: View { action() } label: { Label { - SMSText(title, font: .body1) + SMSText(title, font: .title2) } icon: { SMSIcon(icon) } From 5453cd5c73e15ca594bfa2f27c475ca291ed45cf Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:02:42 +0900 Subject: [PATCH 40/62] =?UTF-8?q?:fire:=20[#220]=20InputSchoolLifeInfoFeat?= =?UTF-8?q?ure=20/=20hwp=20=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=8B=A4=20=EC=A7=80=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/InputSchoolLifeDelegate.swift | 14 +------- .../Intent/InputSchoolLifeInfoIntent.swift | 33 +------------------ .../InputSchoolLifeInfoIntentProtocol.swift | 4 --- .../Model/InputSchoolLifeInfoModel.swift | 21 +----------- .../InputSchoolLifeInfoModelProtocol.swift | 5 --- .../Scene/InputSchoolLifeInfoView.swift | 33 ------------------- .../InputSchoolLifeInfoFeatureTest.swift | 22 ------------- 7 files changed, 3 insertions(+), 129 deletions(-) diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Interface/InputSchoolLifeDelegate.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Interface/InputSchoolLifeDelegate.swift index abc7582c..feaa82e7 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Interface/InputSchoolLifeDelegate.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Interface/InputSchoolLifeDelegate.swift @@ -2,17 +2,5 @@ import Foundation public protocol InputSchoolLifeDelegate: AnyObject { func schoolLifePrevButtonDidTap() - func completeToInputSchoolLife(input: InputSchoolLifeInformationObject) -} - -public struct InputSchoolLifeInformationObject { - public let hwpFilename: String - public let gsmAuthenticationScore: Int - public let hwpData: Data - - public init(hwpFilename: String, gsmAuthenticationScore: Int, hwpData: Data) { - self.hwpFilename = hwpFilename - self.gsmAuthenticationScore = gsmAuthenticationScore - self.hwpData = hwpData - } + func completeToInputSchoolLife(gsmAuthenticationScore: Int) } diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntent.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntent.swift index c17e8da8..d4d02b5d 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntent.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntent.swift @@ -18,22 +18,6 @@ final class InputSchoolLifeInfoIntent: InputSchoolLifeInfoIntentProtocol { model?.updateAuthenticationScore(score: score) } - func hwpFileImporterIsRequred() { - model?.updateIsPresentedHWPFileImporter(isPresented: true) - } - - func hwpFileImporterDismissed() { - model?.updateIsPresentedHWPFileImporter(isPresented: false) - } - - func hwpFileDidSelect(url: URL) { - model?.updateHWPFileURL(url: url) - } - - func failedToImportHWPFile() { - model?.updateErrorField(field: [.hwp]) - } - func prevButtonDidTap() { inputSchoolLifeDelegate?.schoolLifePrevButtonDidTap() } @@ -45,29 +29,14 @@ final class InputSchoolLifeInfoIntent: InputSchoolLifeInfoIntentProtocol { errorSet.insert(.gsmAuthentication) } - if state.hwpFileURL == nil { - errorSet.insert(.hwp) - } - model?.updateErrorField(field: errorSet) guard let gsmScore = Int(state.authenticationScore), - let hwpURL = state.hwpFileURL, - hwpURL.startAccessingSecurityScopedResource(), - let hwpData = try? Data(contentsOf: hwpURL), errorSet.isEmpty else { - state.hwpFileURL?.stopAccessingSecurityScopedResource() return } - hwpURL.stopAccessingSecurityScopedResource() - - let input = InputSchoolLifeInformationObject( - hwpFilename: state.hwpFilename, - gsmAuthenticationScore: gsmScore, - hwpData: hwpData - ) - inputSchoolLifeDelegate?.completeToInputSchoolLife(input: input) + inputSchoolLifeDelegate?.completeToInputSchoolLife(gsmAuthenticationScore: gsmScore) } } diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntentProtocol.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntentProtocol.swift index 4ee321f3..bed6c79e 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntentProtocol.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Intent/InputSchoolLifeInfoIntentProtocol.swift @@ -2,10 +2,6 @@ import Foundation protocol InputSchoolLifeInfoIntentProtocol { func updateAuthenticationScore(score: String) - func hwpFileImporterIsRequred() - func hwpFileImporterDismissed() - func hwpFileDidSelect(url: URL) - func failedToImportHWPFile() func prevButtonDidTap() func nextButtonDidTap(state: any InputSchoolLifeInfoStateProtocol) } diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModel.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModel.swift index 42e9e43f..3fadd8e2 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModel.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModel.swift @@ -2,23 +2,13 @@ import Foundation enum InputSchoolLifeErrorField: Hashable { case gsmAuthentication - case hwp } final class InputSchoolLifeInfoModel: ObservableObject, InputSchoolLifeInfoStateProtocol { @Published var authenticationScore: String = "" - @Published var isPresentedHWPFileImporter: Bool = false - @Published var hwpFileURL: URL? - var hwpFilename: String { - guard let hwpFileURL else { - return "" - } - return hwpFileURL.lastPathComponent - } @Published var errorField: Set = [] var isDisabledNextButton: Bool { - authenticationScore.isEmpty || - hwpFileURL == nil + authenticationScore.isEmpty } } @@ -27,15 +17,6 @@ extension InputSchoolLifeInfoModel: InputSchoolLifeInfoActionProtocol { self.authenticationScore = score } - func updateIsPresentedHWPFileImporter(isPresented: Bool) { - self.isPresentedHWPFileImporter = isPresented - } - - func updateHWPFileURL(url: URL) { - self.hwpFileURL?.stopAccessingSecurityScopedResource() - self.hwpFileURL = url - } - func updateErrorField(field: Set) { self.errorField = field } diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModelProtocol.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModelProtocol.swift index 1ef108ac..4f126f76 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModelProtocol.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Model/InputSchoolLifeInfoModelProtocol.swift @@ -2,16 +2,11 @@ import Foundation protocol InputSchoolLifeInfoStateProtocol { var authenticationScore: String { get } - var isPresentedHWPFileImporter: Bool { get } - var hwpFileURL: URL? { get } - var hwpFilename: String { get } var errorField: Set { get } var isDisabledNextButton: Bool { get } } protocol InputSchoolLifeInfoActionProtocol: AnyObject { func updateAuthenticationScore(score: String) - func updateIsPresentedHWPFileImporter(isPresented: Bool) - func updateHWPFileURL(url: URL) func updateErrorField(field: Set) } diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift index 85d59110..c3044c5e 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Sources/Scene/InputSchoolLifeInfoView.swift @@ -29,21 +29,6 @@ struct InputSchoolLifeInfoView: View { ) .keyboardType(.numberPad) .titleWrapper("인증제 점수") - - SMSTextField( - "+ hwp 파일 추가", - text: Binding( - get: { state.hwpFilename }, - set: { _ in } - ), - errorText: "hwp, hwpx 확장자인 파일만 가능해요", - isError: state.errorField.contains(.hwp) - ) - .disabled(true) - .titleWrapper("드림북") - .onTapGesture { - intent.hwpFileImporterIsRequred() - } } Spacer() @@ -66,23 +51,5 @@ struct InputSchoolLifeInfoView: View { } } .hideKeyboardWhenTap() - .fileImporter( - isPresented: Binding( - get: { state.isPresentedHWPFileImporter }, - set: { _ in intent.hwpFileImporterDismissed() } - ), - allowedContentTypes: [ - UTType(filenameExtension: "hwp") ?? .pdf, - UTType(filenameExtension: "hwpx") ?? .pdf - ] - ) { result in - switch result { - case let .success(url): - intent.hwpFileDidSelect(url: url) - - case .failure: - intent.failedToImportHWPFile() - } - } } } diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift index f54d4333..0f715bb5 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift @@ -41,26 +41,4 @@ final class InputSchoolLifeInfoFeatureTests: XCTestCase { sut.intent.updateAuthenticationScore(score: "\(randomScore)") XCTAssertEqual(sut.model.authenticationScore, "\(randomScore)") } - - func test_file_importer_sheet() { - sut.intent.hwpFileImporterIsRequred() - XCTAssertTrue(sut.model.isPresentedHWPFileImporter) - - sut.intent.hwpFileImporterDismissed() - XCTAssertFalse(sut.model.isPresentedHWPFileImporter) - } - - func test_hwp_file_select() { - guard let url = URL(string: "localhost:8080/index.html") else { - XCTFail("failed to load url") - return - } - sut.intent.hwpFileDidSelect(url: url) - XCTAssertEqual(sut.model.hwpFileURL, url) - } - - func test_failed_to_import_hwp() { - sut.intent.failedToImportHWPFile() - XCTAssertEqual(sut.model.errorField, [.hwp]) - } } From f35feb649cebcdc2d6e0feba3a905d1ac0909bfa Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:04:20 +0900 Subject: [PATCH 41/62] =?UTF-8?q?:recycle:=20[#220]=20MainPageFeature=20/?= =?UTF-8?q?=20S=20=EB=B6=99=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/MainFeature/Sources/Model/MainModel.swift | 2 +- Projects/Feature/MainFeature/Sources/Scene/MainView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift index 9a4cc630..b02cdaf3 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift @@ -19,7 +19,7 @@ final class MainModel: ObservableObject, MainStateProtocol { profileImageURL: $0.profileImageURL, name: $0.name.replacingOccurrences(of: "**", with: "소금"), major: $0.major, - techStack: $0.techStack + techStacks: $0.techStacks ) } } diff --git a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift index bc46dd99..be8928c2 100644 --- a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift +++ b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift @@ -58,7 +58,7 @@ struct MainView: View { profileImageUrl: item.profileImageURL, name: item.name, major: item.major, - techStack: item.techStack + techStack: item.techStacks ) .foregroundColor(.sms(.system(.black))) .buttonWrapper { From c4f89b021bb57d5de18dff36afe72dd49651d75b Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:07:28 +0900 Subject: [PATCH 42/62] =?UTF-8?q?:lipstick:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20MVIContainer=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MyPageMilitaryView.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift index 94b2a441..cac51c3e 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageMilitaryView.swift @@ -1,10 +1,18 @@ import DesignSystem import SwiftUI import ViewUtil +import BaseFeature struct MyPageMilitaryView: View { - let intent: MyPageMilitaryIntentProtocol - let state: MyPageMilitaryStateProtocol + @StateObject var container: MVIContainer + var intent: MyPageMilitaryIntentProtocol { container.intent } + var state: MyPageMilitaryStateProtocol { container.model } + + init( + container: MVIContainer + ) { + self._container = StateObject(wrappedValue: container) + } var body: some View { Section { @@ -28,7 +36,7 @@ struct MyPageMilitaryView: View { } } } header: { - SectionHeaderView(title: "학교 생활") + SectionHeaderView(title: "병역") } .padding(.horizontal, 20) } From d8aa705e5812cb26227b88289e2d6677f682b528 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:08:53 +0900 Subject: [PATCH 43/62] =?UTF-8?q?:lipstick:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20=ED=95=84=EC=9A=94=ED=95=9C=20View=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scene/View/ImageMethodPickerView.swift | 43 +++++++++++++++---- .../Scene/View/ImageMethodRowView.swift | 39 +++++++++++++---- .../Sources/Scene/View/MajorRowView.swift | 35 +++++++++++---- 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift index 7a2e0685..395e5477 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodPickerView.swift @@ -1,9 +1,34 @@ -// -// ImageMethodPickerView.swift -// MyPageFeature -// -// Created by sunghun on 8/16/23. -// Copyright © 2023 com.msg. All rights reserved. -// - -import Foundation +import DesignSystem +import SwiftUI + +struct ImageMethodPickerView: View { + private var albumAction: () -> Void + private var cameraAction: () -> Void + + init( + albumAction: @escaping () -> Void, + cameraAction: @escaping () -> Void + ) { + self.albumAction = albumAction + self.cameraAction = cameraAction + } + + var body: some View { + VStack(spacing: 28) { + Group { + ImageMethodRowView(title: "앨범에서 가져오기", icon: .photo) { + albumAction() + } + + ImageMethodRowView(title: "카메라에서 촬영하기", icon: .camera) { + cameraAction() + } + } + .buttonStyle(.plain) + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 20) + .padding(.top, 16) + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift index f65d3cd0..12846ab9 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/ImageMethodRowView.swift @@ -1,9 +1,32 @@ -// -// ImageMethodRowView.swift -// MyPageFeature -// -// Created by sunghun on 8/16/23. -// Copyright © 2023 com.msg. All rights reserved. -// +import DesignSystem +import SwiftUI -import Foundation +struct ImageMethodRowView: View { + private var title: String + private var icon: SMSIcon.Icon + private var action: () -> Void + + init( + title: String, + icon: SMSIcon.Icon, + action: @escaping () -> Void + ) { + self.title = title + self.icon = icon + self.action = action + } + + var body: some View { + Button { + action() + } label: { + Label { + SMSText(title, font: .body1) + } icon: { + SMSIcon(icon) + } + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.sms(.system(.white))) + } + } +} diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift index 992bf175..5ecc9f11 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/View/MajorRowView.swift @@ -1,9 +1,26 @@ -// -// MajorRowView.swift -// MyPageFeature -// -// Created by sunghun on 8/16/23. -// Copyright © 2023 com.msg. All rights reserved. -// - -import Foundation +import DesignSystem +import SwiftUI + +struct MajorRowView: View { + private var text: String + @Binding private var isSeleted: Bool + + init(text: String, isSeleted: Binding) { + self.text = text + _isSeleted = isSeleted + } + + var body: some View { + HStack { + SMSText(text, font: .body1) + + Spacer() + + SMSRadioButton(isSelected: $isSeleted) + .buttonWrapper {} + } + .padding(.horizontal, 20) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + } +} From 7b632811f7486ac3a2533895a8c115ab6169c83d Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:09:19 +0900 Subject: [PATCH 44/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20UI=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MyPagePrizeView.swift | 19 +- .../Sources/Scene/MyPageProfileView.swift | 1 + .../Sources/Scene/MyPageProjectView.swift | 114 ++++++----- .../Sources/Scene/MyPageView.swift | 178 +++++++++++++++++- .../Sources/Scene/MyPageWorkInfoView.swift | 19 +- 5 files changed, 255 insertions(+), 76 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift index 31cb72b7..1ebe2505 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift @@ -22,13 +22,22 @@ struct MyPagePrizeView: View { } } - SMSChip("추가") { - withAnimation { - intent.prizeAppendButtonDidTap() - } + Spacer().frame(height: 16) + + HStack(spacing: 4) { + SMSIcon(.plus, width: 12, height: 12) + .foregroundColor(.sms(.system(.black))) + + SMSText("추가") + .foregroundColor(.sms(.system(.black))) + .font(.sms(.title2)) } - .foregroundColor(.sms(.system(.black))) .aligned(.trailing) + .buttonWrapper { + intent.prizeAppendButtonDidTap() + } + + Spacer().frame(height: 32) } header: { SectionHeaderView(title: "수상") } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift index 8f6b398f..73fe9f59 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProfileView.swift @@ -29,6 +29,7 @@ struct MyPageProfileView: View { if let image = image.image { image.resizable() .frame(width: 100, height: 100) + .cornerRadius(8) } else { Color.sms(.neutral(.n30)) .frame(width: 100, height: 100) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift index 12a84dea..6564ce8d 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift @@ -22,21 +22,28 @@ struct MyPageProjectView: View { var body: some View { Section { - VStack(spacing: 24) { - ForEach(state.projectList.indices, id: \.self) { index in - projectListRowView(index: index, geometry: geometry) + VStack(spacing: 16) { + VStack(spacing: 24) { + ForEach(state.projectList.indices, id: \.self) { index in + projectListRowView(index: index, geometry: geometry) - SMSSeparator(height: 1) + SMSSeparator(height: 1) + } } - } - SMSChip("추가") { - withAnimation { + HStack(spacing: 4) { + SMSIcon(.plus, width: 12, height: 12) + .foregroundColor(.sms(.system(.black))) + + SMSText("추가") + .foregroundColor(.sms(.system(.black))) + .font(.sms(.title2)) + } + .aligned(.trailing) + .buttonWrapper { intent.projectAppendButtonDidTap() } } - .foregroundColor(.sms(.system(.black))) - .aligned(.trailing) } header: { SectionHeaderView(title: "프로젝트") } @@ -47,20 +54,22 @@ struct MyPageProjectView: View { func projectListRowView(index: Int, geometry: GeometryProxy) -> some View { let collapsed = state.collapsedProject[safe: index] ?? false Section { - ConditionView(!collapsed) { - projectName(index: index) + VStack(alignment: .leading, spacing: 24) { + ConditionView(!collapsed) { + projectName(index: index) - projectIcon(index: index) + projectIcon(index: index) - projectPreviewImageList(index: index) + projectPreviewImageList(index: index) - projectContentTextEditor(index: index) + projectContentTextEditor(index: index) - projectTechStack(geometry: geometry, index: index) + projectTechStack(geometry: geometry, index: index) - projectDuration(index: index) + projectDuration(index: index) - projectRelatedLink(index: index, geometry: geometry) + projectRelatedLink(index: index, geometry: geometry) + } } } header: { HStack(spacing: 16) { @@ -145,43 +154,44 @@ private extension MyPageProjectView { @ViewBuilder func projectPreviewImageList(index: Int) -> some View { - LazyHStack(spacing: 8) { - let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] - ConditionView(projectPreviewImages.count < 4) { - imagePlaceholder(size: 132) - .overlay { - VStack(spacing: 4) { - SMSIcon(.photo) - - SMSText( - "\(projectPreviewImages.count)/4", - font: .body2 - ) - .foregroundColor(.sms(.system(.black))) + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 8) { + let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] + ConditionView(projectPreviewImages.count < 4) { + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) + } } - } - .buttonWrapper { - intent.appendPreviewImageButtonDidTap(index: index) - } - } + .buttonWrapper { + intent.appendPreviewImageButtonDidTap(index: index) + } + } - ForEach(projectPreviewImages.indices, id: \.self) { previewIndex in - let url = URL(string: projectPreviewImages[previewIndex]) - LazyImage(url: url) { image in - if let image = image.image { - image - .resizable() - .frame(width: 132, height: 132) - .cornerRadius(8) - .overlay(alignment: .topTrailing) { - SMSIcon(.xmark) - .padding(4) - .buttonWrapper { - intent.removePreviewImageDidTap(index: index, previewIndex: previewIndex) - } - } - } else { - imagePlaceholder(size: 132) + ForEach(projectPreviewImages.indices, id: \.self) { previewIndex in + let url = URL(string: projectPreviewImages[previewIndex]) + LazyImage(url: url) { image in + if let image = image.image { + image + .resizable() + .frame(width: 132, height: 132) + .cornerRadius(8) + .overlay(alignment: .topTrailing) { + SMSIcon(.xmark) + .padding(4) + .buttonWrapper { + intent.removePreviewImageDidTap(index: index, previewIndex: previewIndex) + } + } + } else { + imagePlaceholder(size: 132) + } } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index c7ab5383..a05aceed 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -3,6 +3,7 @@ import SwiftUI import DesignSystem import ViewUtil import TechStackAppendFeatureInterface +import StudentDomainInterface struct MyPageView: View { @Environment(\.dismiss) var dismiss @@ -53,7 +54,13 @@ struct MyPageView: View { SMSSeparator() .padding(.vertical, 16) - MyPageMilitaryView(intent: intent, state: state) + MyPageMilitaryView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) } Group { @@ -107,7 +114,7 @@ struct MyPageView: View { } CTAButton(text: "저장") { - #warning("저장 로직 추가") + intent.modifyToInputAllInfo(state: state) } .padding(.horizontal, 20) .padding(.bottom, safeAreaInsets.bottom + 16) @@ -133,14 +140,14 @@ struct MyPageView: View { intent.existActionSheetDismissed() } label: { HStack(spacing: 12) { - SMSIcon(.redLogout) + SMSIcon(.logout) - SMSText("로그아웃", font: .body1) - .foregroundStyle(Color.sms(.system(.error))) + SMSText("로그아웃", font: .title2) + .foregroundStyle(Color.sms(.neutral(.n50))) Spacer() } - } + } Button { intent.withdrawalDialogIsRequired() @@ -149,7 +156,7 @@ struct MyPageView: View { HStack(spacing: 12) { SMSIcon(.redPerson) - SMSText("회원탈퇴", font: .body1) + SMSText("회원탈퇴", font: .title2) .foregroundStyle(Color.sms(.system(.error))) Spacer() @@ -160,6 +167,26 @@ struct MyPageView: View { .padding(.horizontal, 20) } .animation(.default, value: state.isPresentedExistActionSheet) + .smsBottomSheet( + isShowing: Binding( + get: { state.isPresentedMilitarySheet }, + set: { _ in intent.militarySheetDismissed() } + ) + ) { + militaryListView() + } + .animation(.default, value: state.isPresentedMilitarySheet) + .smsBottomSheet( + isShowing: Binding( + get: { state.isPresentedFormOfEmployeementSheet }, + set: { _ in intent.formOfEmployeementSheetDismissed() } + ) + ) { + DeferView { + formOfEmployeementList() + } + } + .animation(.default, value: state.isPresentedFormOfEmployeementSheet) .smsAlert( title: "로그아웃", description: "정말로 로그아웃 하시겠습니까?", @@ -252,6 +279,63 @@ struct MyPageView: View { .eraseToAnyView() } } + .smsBottomSheet( + isShowing: Binding( + get: { state.isPresentedImageMethodPicker }, + set: { _ in intent.imageMethodPickerDismissed() } + ) + ) { + ImageMethodPickerView { + intent.imagePickerIsRequired() + intent.imageMethodPickerDismissed() + } cameraAction: { + intent.cameraIsRequired() + intent.imageMethodPickerDismissed() + } + } + .imagePicker( + isShow: Binding( + get: { state.isPresentedProfileImage }, + set: { _ in intent.imagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { intent.imageDidSelected(imageResult: $0) } + ) + ) + .cameraPicker( + isShow: Binding( + get: { state.isPresentedProfileCamera }, + set: { _ in intent.cameraDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { intent.imageDidSelected(imageResult: $0) } + ) + ) + .smsBottomSheet( + isShowing: Binding( + get: { state.isPresentedMajorSheet }, + set: { _ in intent.majorSheetDismissed() } + ), + topPadding: 150 + ) { + majorListView() + } + .fullScreenCover( + isPresented: Binding( + get: { state.isPresentedTechStackAppend }, + set: { _ in intent.techStackAppendDismissed() } + ) + ) { + DeferView { + techStackAppendBuildable.makeView(initial: state.techStacks) { techStacks in + intent.techStackAppendDidComplete(techStacks: techStacks) + } + .eraseToAnyView() + } + } + .animation(.default, value: state.isPresentedImageMethodPicker) .hideKeyboardWhenTap() .navigationTitle("마이페이지") .smsBackButton( @@ -259,4 +343,84 @@ struct MyPageView: View { ) .navigationBarTitleDisplayMode(.inline) } + + @ViewBuilder + func majorListView() -> some View { + ScrollView { + LazyVStack(spacing: 8) { + ForEach(state.majorList, id: \.self) { major in + MajorRowView( + text: major, + isSeleted: Binding( + get: { state.major == major }, + set: { + $0 ? intent.updateMajor(major: major) : () + $0 ? intent.majorSheetDismissed() : () + } + ) + ) + } + + MajorRowView( + text: "직접입력", + isSeleted: Binding( + get: { state.isSelfEntering }, + set: { + $0 ? intent.updateMajor(major: "") : () + $0 ? intent.activeSelfEntering() : () + $0 ? intent.majorSheetDismissed() : () + } + ) + ) + } + } + } + + @ViewBuilder + func formOfEmployeementList() -> some View { + VStack(spacing: 16) { + ForEach(FormOfEmployment.allCases, id: \.self) { formOfEmployment in + HStack { + Text(formOfEmployment.display()) + .smsFont(.body1, color: .neutral(.n50)) + + Spacer() + + SMSRadioButton( + isSelected: Binding( + get: { state.formOfEmployment == formOfEmployment }, + set: { $0 ? intent.updateFormOfEmployment(form: formOfEmployment) : () } + ) + ) + .buttonWrapper {} + } + .animation(.default, value: state.formOfEmployment) + .padding(.horizontal, 20) + } + } + } + + @ViewBuilder + func militaryListView() -> some View { + VStack(spacing: 16) { + ForEach(MilitaryServiceType.allCases, id: \.self) { militaryServiceType in + HStack { + Text(militaryServiceType.display()) + .smsFont(.body1, color: .neutral(.n50)) + + Spacer() + + SMSRadioButton( + isSelected: Binding( + get: { state.selectedMilitaryServiceType == militaryServiceType }, + set: { $0 ? intent.militaryServiceTypeDidSelected(type: militaryServiceType) : () } + ) + ) + .buttonWrapper {} + } + .animation(.default, value: state.selectedMilitaryServiceType) + .padding(.horizontal, 20) + } + } + } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift index e14cdd37..83fe5dd6 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageWorkInfoView.swift @@ -33,19 +33,14 @@ struct MyPageWorkInfoView: View { intent.formOfEmployeementSheetIsRequired() } - VStack(alignment: .leading, spacing: 4) { - SMSTextField( - "희망 연봉 (10,000원 단위)", - text: Binding( - get: { state.salary }, - set: intent.updateSalary(salary:) - ) + SMSTextField( + "희망 연봉 (10,000원 단위)", + text: Binding( + get: { state.salary }, + set: intent.updateSalary(salary:) ) - .keyboardType(.numberPad) - - Text(state.salaryDisplay) - .smsFont(.caption1, color: .neutral(.n30)) - } + ) + .keyboardType(.numberPad) .titleWrapper("희망 연봉") workRegionList() From d479f53c102c3276c0dc5f980844878c353bc0f2 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:09:51 +0900 Subject: [PATCH 45/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=9E=91=EC=97=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/DI/MyPageComponent.swift | 8 +- .../Sources/Intent/MyPageIntent.swift | 115 +++++++++++++++++- .../Sources/Intent/MyPageIntentProtocol.swift | 1 + .../Sources/Intent/MyPageProfileIntent.swift | 8 +- .../Sources/Intent/MyPageProjectIntent.swift | 10 +- .../Sources/Model/MyPageProfileModel.swift | 5 + .../Sources/Model/MyPageWorkInfoModel.swift | 1 - 7 files changed, 131 insertions(+), 17 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift index 6192e7a8..82babb74 100644 --- a/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift +++ b/Projects/Feature/MyPageFeature/Sources/DI/MyPageComponent.swift @@ -2,6 +2,8 @@ import AuthDomainInterface import BaseFeature import MyPageFeatureInterface import FileDomainInterface +import StudentDomainInterface +import MajorDomainInterface import NeedleFoundation import SwiftUI import UserDomainInterface @@ -12,6 +14,8 @@ public protocol MyPageDependency: Dependency { var authDomainBuildable: any AuthDomainBuildable { get } var techStackAppendBuildable: any TechStackAppendBuildable { get } var fileDomainBuildable: any FileDomainBuildable { get } + var studentDomainBuildable: any StudentDomainBuildable { get } + var majorDomainBuildable: any MajorDomainBuildable { get } } public final class MyPageComponent: Component, MyPageBuildable { @@ -23,7 +27,9 @@ public final class MyPageComponent: Component, MyPageBuildable fetchMyProfileUseCase: dependency.userDomainBuildable.fetchMyProfileUseCase, logoutUseCase: dependency.authDomainBuildable.logoutUseCase, withdrawalUseCase: dependency.authDomainBuildable.withdrawalUseCase, - imageUploadUseCase: dependency.fileDomainBuildable.imageUploadUseCase + imageUploadUseCase: dependency.fileDomainBuildable.imageUploadUseCase, + modifyInformationUseCase: dependency.studentDomainBuildable.modifyInformationUseCase, + fetchMajorListUseCase: dependency.majorDomainBuildable.fetchMajorListUseCase ) let container = MVIContainer( intent: intent as MyPageIntentProtocol, diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index ed776b7f..85496124 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -1,8 +1,13 @@ import Foundation +import DesignSystem import UserDomainInterface import AuthDomainInterface import FileDomainInterface import MyPageFeatureInterface +import StudentDomainInterface +import MajorDomainInterface +import ConcurrencyUtil +import DateUtil final class MyPageIntent: MyPageIntentProtocol { weak var model: (any MyPageActionProtocol)? @@ -10,7 +15,9 @@ final class MyPageIntent: MyPageIntentProtocol { private let fetchMyProfileUseCase: any FetchMyProfileUseCase private let logoutUseCase: any LogoutUseCase private let withdrawalUseCase: any WithdrawalUseCase - let imageUploadUseCase: any ImageUploadUseCase + private let imageUploadUseCase: any ImageUploadUseCase + private let modifyInformationUseCase: any ModifyInformationUseCase + private let fetchMajorListUseCase: any FetchMajorListUseCase init( model: any MyPageActionProtocol, @@ -18,7 +25,9 @@ final class MyPageIntent: MyPageIntentProtocol { fetchMyProfileUseCase: any FetchMyProfileUseCase, logoutUseCase: any LogoutUseCase, withdrawalUseCase: any WithdrawalUseCase, - imageUploadUseCase: any ImageUploadUseCase + imageUploadUseCase: any ImageUploadUseCase, + modifyInformationUseCase: any ModifyInformationUseCase, + fetchMajorListUseCase: any FetchMajorListUseCase ) { self.model = model self.myPageDelegate = myPageDelegate @@ -26,14 +35,19 @@ final class MyPageIntent: MyPageIntentProtocol { self.logoutUseCase = logoutUseCase self.withdrawalUseCase = withdrawalUseCase self.imageUploadUseCase = imageUploadUseCase + self.modifyInformationUseCase = modifyInformationUseCase + self.fetchMajorListUseCase = fetchMajorListUseCase } func onAppear() { Task { do { + let majorList = try await fetchMajorListUseCase.execute() + model?.updateMajorList(majorList: majorList) let profile = try await fetchMyProfileUseCase.execute() model?.updateProfileURL(url: profile.profileImageURL) model?.updateIntroduce(introduce: profile.introduce) + model?.updatePortfolioURL(portfolioURL: profile.portfolioURL) model?.updateMajor(major: profile.major) model?.updateEmail(email: profile.contactEmail) model?.updateGSMScore(gsmScore: "\(profile.gsmAuthenticationScore)") @@ -102,4 +116,101 @@ final class MyPageIntent: MyPageIntentProtocol { model?.updateIsPresentedWithdrawalDialog(isPresented: false) } + func modifyToInputAllInfo(state: any MyPageStateProtocol) { + Task { + do { + let modifyInformationRequest = ModifyStudentInformationRequestDTO( + certificate: state.certificates, + contactEmail: state.email, + formOfEmployment: FormOfEmployment(rawValue: state.formOfEmployment.rawValue) ?? .fullTime, + gsmAuthenticationScore: Int(state.gsmScore) ?? 0, + introduce: state.introduce, + languageCertificate: state.languageList.map { $0.toDTO() }, + major: state.major, + militaryService: state.selectedMilitaryServiceType, + portfolioURL: state.portfolioURL, + profileImgURL: state.profileURL, + region: state.workRegionList, + salary: Int(state.salary) ?? 0, + techStacks: state.techStacks, + projects: state.projectList.map { + let startAtString = $0.startAt.toStringCustomFormat(format: "yyyy.MM") + let endAtString = $0.endAt?.toStringCustomFormat(format: "yyyy.MM") ?? "" + + return $0.toDTO( + iconURL: $0.iconImage, + previewImageURLS: $0.previewImages, + startAt: startAtString, + endAt: endAtString + ) + }, + prizes: state.prizeList.map { $0.toDTO() } + ) + + try await modifyInformationUseCase.execute(req: modifyInformationRequest) + } catch { + } + } + } + + func imageUpload(imageResult: PickedImageResult) async throws -> String { + try await Task { + try await imageUploadUseCase.execute( + image: imageResult.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), + fileName: imageResult.fileName + ) + } + .value + } +} + +extension LanguageModel { + func toDTO() -> ModifyStudentInformationRequestDTO.LanguageCertificate { + ModifyStudentInformationRequestDTO.LanguageCertificate( + languageCertificateName: name, + score: score + ) + } +} + +extension ProjectModel { + func toDTO( + iconURL: String, + previewImageURLS: [String], + startAt: String, + endAt: String + ) -> ModifyStudentInformationRequestDTO.Project { + ModifyStudentInformationRequestDTO.Project( + name: name, + iconImageURL: iconURL, + previewImageURLs: previewImageURLS, + description: content, + links: relatedLinks.map { $0.toDTO() }, + techStacks: Array(techStacks), + myActivity: mainTask, + inProgress: .init( + start: startAt, + end: endAt + ) + ) + } +} + +extension ProjectModel.RelatedLink { + func toDTO() -> ModifyStudentInformationRequestDTO.Project.Link { + ModifyStudentInformationRequestDTO.Project.Link( + name: name, + url: url + ) + } +} + +extension PrizeModel { + func toDTO() -> ModifyStudentInformationRequestDTO.Prize { + ModifyStudentInformationRequestDTO.Prize( + name: name, + type: prize, + date: prizeAtString + ) + } } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index 34155210..ad063d73 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -18,4 +18,5 @@ protocol MyPageIntentProtocol: func withdrawalDialogIsRequired() func withdrawalDialogDismissed() func withdrawalDialogIsComplete() + func modifyToInputAllInfo(state: MyPageStateProtocol) } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift index 34328876..5c176b9d 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProfileIntent.swift @@ -83,11 +83,9 @@ extension MyPageIntent: MyPageProfileIntentProtocol { guard let imageResult else { return } Task { do { - async let profileImageURL = imageUploadUseCase.execute( - image: imageResult.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), - fileName: imageResult.fileName - ) - try await model?.updateProfileURL(url: profileImageURL) + async let profileURL = imageUpload(imageResult: imageResult) + + try await model?.updateProfileURL(url: profileURL) } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift index f4984227..8d7aa4d4 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift @@ -44,10 +44,7 @@ extension MyPageIntent: MyPageProjectIntentProtocol { func updateIconImage(index: Int, image: PickedImageResult) { Task { do { - async let iconImageURL = imageUploadUseCase.execute( - image: image.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), - fileName: image.fileName - ) + async let iconImageURL = imageUpload(imageResult: image) try await model?.updateIconImage(index: index, imageURL: iconImageURL) } } @@ -61,10 +58,7 @@ extension MyPageIntent: MyPageProjectIntentProtocol { func appendPreviewImage(index: Int, image: PickedImageResult) { Task { do { - async let previewImageURL = imageUploadUseCase.execute( - image: image.uiImage.jpegData(compressionQuality: 0.2) ?? .init(), - fileName: image.fileName - ) + async let previewImageURL = imageUpload(imageResult: image) try await model?.appendPreviewImage(index: index, imageURL: previewImageURL) } } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModel.swift index a7f71c5b..82a23177 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProfileModel.swift @@ -21,6 +21,7 @@ protocol MyPageProfileActionProtocol: AnyObject { func updateIntroduce(introduce: String) func updateEmail(email: String) func updateMajor(major: String) + func updateMajorList(majorList: [String]) func updatePortfolioURL(portfolioURL: String) func updateTechStacks(techStacks: [String]) func removeTechStack(techStack: String) @@ -49,6 +50,10 @@ extension MyPageModel: MyPageProfileActionProtocol { self.major = major } + func updateMajorList(majorList: [String]) { + self.majorList = majorList + } + func updatePortfolioURL(portfolioURL: String) { self.portfolioURL = portfolioURL } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift index e882568e..ae8da0e6 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift @@ -4,7 +4,6 @@ import StudentDomainInterface protocol MyPageWorkInfoStateProtocol { var workRegionList: [String] { get } var salary: String { get } - var salaryDisplay: String { get } var formOfEmployment: FormOfEmployment { get } var isPresentedFormOfEmployeementSheet: Bool { get } } From e380cbd33ad75835adb894efb685d75fd2fb576b Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:10:16 +0900 Subject: [PATCH 46/62] =?UTF-8?q?:heavy=5Fplus=5Fsign:=20[#220]=20App=20/?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Sources/Application/NeedleGenerated.swift | 14 +++++++++++++- .../MyPageFeature/Demo/Sources/AppDelegate.swift | 8 +++++++- Projects/Feature/MyPageFeature/Project.swift | 3 ++- Tuist/Dependencies.swift | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index a5086724..ea73b906 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -117,6 +117,15 @@ private class MyPageDependency48d84b530313b3ee40feProvider: MyPageDependency { var techStackAppendBuildable: any TechStackAppendBuildable { return appComponent.techStackAppendBuildable } + var fileDomainBuildable: any FileDomainBuildable { + return appComponent.fileDomainBuildable + } + var studentDomainBuildable: any StudentDomainBuildable { + return appComponent.studentDomainBuildable + } + var majorDomainBuildable: any MajorDomainBuildable { + return appComponent.majorDomainBuildable + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -470,6 +479,9 @@ extension MyPageComponent: Registration { keyPathToName[\MyPageDependency.userDomainBuildable] = "userDomainBuildable-any UserDomainBuildable" keyPathToName[\MyPageDependency.authDomainBuildable] = "authDomainBuildable-any AuthDomainBuildable" keyPathToName[\MyPageDependency.techStackAppendBuildable] = "techStackAppendBuildable-any TechStackAppendBuildable" + keyPathToName[\MyPageDependency.fileDomainBuildable] = "fileDomainBuildable-any FileDomainBuildable" + keyPathToName[\MyPageDependency.studentDomainBuildable] = "studentDomainBuildable-any StudentDomainBuildable" + keyPathToName[\MyPageDependency.majorDomainBuildable] = "majorDomainBuildable-any MajorDomainBuildable" } } extension InputWorkInfoComponent: Registration { @@ -608,7 +620,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi #if !NEEDLE_DYNAMIC -@inline(never) private func register1() { +private func register1() { registerProviderFactory("^->AppComponent->JwtStoreComponent", factoryb27d5aae1eb7e73575a6f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) diff --git a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift index 059e2bf6..b1d19e75 100644 --- a/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MyPageFeature/Demo/Sources/AppDelegate.swift @@ -5,6 +5,8 @@ import TechStackAppendFeatureInterface @testable import MyPageFeature @testable import UserDomainTesting @testable import FileDomainTesting +@testable import StudentDomainTesting +@testable import MajorDomainTesting final class DummyMyPageDelegate: MyPageDelegate { func logout() {} @@ -25,13 +27,17 @@ struct MyPageDemoApp: App { let logoutUseCase = LogoutUseCaseSpy() let withdrawalUseCase = WithdrawalUseCaseSpy() let imageUploadUseCase = ImageUploadUseCaseSpy() + let modifyInformationUseCase = ModifyInformationUseCaseSpy() + let fetchMajorListUseCase = FetchMajorListUseCaseSpy() let intent = MyPageIntent( model: model, myPageDelegate: DummyMyPageDelegate(), fetchMyProfileUseCase: fetchMyProfileUseCase, logoutUseCase: logoutUseCase, withdrawalUseCase: withdrawalUseCase, - imageUploadUseCase: imageUploadUseCase + imageUploadUseCase: imageUploadUseCase, + modifyInformationUseCase: modifyInformationUseCase, + fetchMajorListUseCase: fetchMajorListUseCase ) MyPageView( container: .init( diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index e4676331..ca948c36 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -16,6 +16,7 @@ let project = Project.makeModule( .Domain.UserDomainTesting, .Domain.AuthDomainTesting, .Domain.FileDomainTesting, - .Domain.StudentDomainTesting + .Domain.StudentDomainTesting, + .Domain.MajorDomainTesting ] ) diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 90e41989..7dd13af0 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -12,7 +12,7 @@ let dependencies = Dependencies( .remote(url: "https://github.com/GSM-MSG/GAuthSignin-Swift", requirement: .exact("0.0.3")), .remote(url: "https://github.com/Quick/Nimble.git", requirement: .exact("11.2.2")), .remote(url: "https://github.com/Quick/Quick.git", requirement: .exact("6.1.0")), - .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.2.11")) + .remote(url: "https://github.com/GSM-MSG/Emdpoint.git", requirement: .exact("3.5.0")) ], baseSettings: .settings( From a9a27f49c6c86bde56c54b0c96fe5377e1c52b14 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:49:39 +0900 Subject: [PATCH 47/62] =?UTF-8?q?:sparkles:=20[#220]=20MypageFeature=20/?= =?UTF-8?q?=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Icons.xcassets/Camera.imageset/Camera.svg | 8 +-- .../MainFeature/Sources/Scene/MainView.swift | 71 ------------------- .../Sources/Scene/MyPageView.swift | 20 +++--- 3 files changed, 14 insertions(+), 85 deletions(-) diff --git a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Camera.imageset/Camera.svg b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Camera.imageset/Camera.svg index fcbea1e6..966fc29c 100644 --- a/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Camera.imageset/Camera.svg +++ b/Projects/Core/DesignSystem/Resources/Icon/Icons.xcassets/Camera.imageset/Camera.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift index 2e779be8..be8928c2 100644 --- a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift +++ b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift @@ -143,77 +143,6 @@ struct MainView: View { SMSIcon(.smsLogo, width: 80, height: 29) } } - .smsBottomSheet(isShowing: Binding( - get: { state.isPresentedExistActionSheet }, - set: { _ in intent.existActionSheetDismissed() } - )) { - VStack(alignment: .leading, spacing: 32) { - Button { - intent.logoutDialogIsRequired() - intent.existActionSheetDismissed() - } label: { - HStack(spacing: 12) { - SMSIcon(.redLogout) - - SMSText("로그아웃", font: .body1) - .foregroundStyle(Color.sms(.error(.e2))) - - Spacer() - } - } - - Button { - intent.withdrawalDialogIsRequired() - intent.existActionSheetDismissed() - } label: { - HStack(spacing: 12) { - SMSIcon(.redPerson) - - SMSText("회원탈퇴", font: .body1) - .foregroundStyle(Color.sms(.error(.e2))) - - Spacer() - } - } - .conditional(state.currentUserRole != .guest) - } - .padding(.top, 12) - .padding(.horizontal, 20) - } - .animation(.default, value: state.isPresentedExistActionSheet) - .smsAlert( - title: "로그아웃", - description: "정말로 로그아웃 하시겠습니까?", - isShowing: - Binding( - get: { state.isPresentedLogoutDialog }, - set: { _ in intent.logoutDialogDismissed() } - ), - alertActions: [ - .init(text: "확인", style: .outline) { - intent.logoutDialogIsComplete() - }, - .init(text: "취소") { - intent.logoutDialogDismissed() - } - ] - ) - .smsAlert( - title: "회원탈퇴", - description: "정말로 회원탈퇴 하시겠습니까?", - isShowing: - Binding( - get: { state.isPresentedWithdrawalDialog }, - set: { _ in intent.withdrawalDialogDismissed() } - ), - alertActions: [ - .init(text: "확인", style: .outline) { - intent.withdrawalDialogIsComplete() - }, - .init(text: "취소") { - intent.withdrawalDialogDismissed() - } - ]) } .navigationViewStyle(.stack) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index a05aceed..17b75b56 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -147,7 +147,7 @@ struct MyPageView: View { Spacer() } - } + } Button { intent.withdrawalDialogIsRequired() @@ -157,7 +157,7 @@ struct MyPageView: View { SMSIcon(.redPerson) SMSText("회원탈퇴", font: .title2) - .foregroundStyle(Color.sms(.system(.error))) + .foregroundStyle(Color.sms(.error(.e2))) Spacer() } @@ -195,11 +195,11 @@ struct MyPageView: View { set: { _ in intent.logoutDialogDismissed() } ), alertActions: [ - .init(text: "확인", style: .outline) { - intent.logoutDialogIsComplete() - }, - .init(text: "취소") { + .init(text: "취소", style: .outline) { intent.logoutDialogDismissed() + }, + .init(text: "확인", style: .error) { + intent.logoutDialogIsComplete() } ] ) @@ -211,11 +211,11 @@ struct MyPageView: View { set: { _ in intent.withdrawalDialogDismissed() } ), alertActions: [ - .init(text: "확인", style: .outline) { - intent.withdrawalDialogIsComplete() - }, - .init(text: "취소") { + .init(text: "취소", style: .outline) { intent.withdrawalDialogDismissed() + }, + .init(text: "확인", style: .error) { + intent.withdrawalDialogIsComplete() } ] ) From f557e14e13a898a6a89361e1536b7516bc37fdd4 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:26:58 +0900 Subject: [PATCH 48/62] =?UTF-8?q?:sparkles:=20[#220]=20MainFeature=20/=20G?= =?UTF-8?q?uest=20=EB=B2=84=EC=A0=84=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/MainIntent.swift | 8 ++++++++ .../Sources/Intent/MainIntentProtocol.swift | 2 ++ .../MainFeature/Sources/Model/MainModel.swift | 5 +++++ .../Sources/Model/MainModelProtocol.swift | 2 ++ .../MainFeature/Sources/Scene/MainView.swift | 19 ++++++++++++++++++- 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift b/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift index 9996a545..d6c27d4a 100644 --- a/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift +++ b/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift @@ -85,6 +85,14 @@ final class MainIntent: MainIntentProtocol { func logout() { mainDelegate?.logout() } + + func existIsRequired() { + model?.updateIsPresentedExistDialog(isPresented: true) + } + + func existIsDismissed() { + model?.updateIsPresentedExistDialog(isPresented: false) + } } extension MainIntent: FilterDelegate { diff --git a/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift b/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift index b97f4e5f..6454ba65 100644 --- a/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift +++ b/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift @@ -12,4 +12,6 @@ protocol MainIntentProtocol: FilterDelegate, MyPageDelegate { func myPageDismissed() func studentDidSelect(userID: String) func studentDetailDismissed() + func existIsRequired() + func existIsDismissed() } diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift index b02cdaf3..b487c25a 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift @@ -27,6 +27,7 @@ final class MainModel: ObservableObject, MainStateProtocol { @Published var _content: [SingleStudentEntity] = [] @Published var isPresentedFilterPage: Bool = false @Published var isPresentedMyPage: Bool = false + @Published var isPresntedExist: Bool = false @Published var selectedUserID: String? @Published var currentUserRole: UserRoleType = .guest @Published var filterOption: FilterOption? @@ -54,6 +55,10 @@ extension MainModel: MainActionProtocol { self.isPresentedMyPage = isPresented } + func updateIsPresentedExistDialog(isPresented: Bool) { + self.isPresntedExist = isPresented + } + func appendContent(content: [SingleStudentEntity]) { self.content.append(contentsOf: content) } diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift b/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift index 90b52996..cc0ade90 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift @@ -8,6 +8,7 @@ protocol MainStateProtocol { var isRefresh: Bool { get } var isPresentedFilterPage: Bool { get } var isPresentedMyPage: Bool { get } + var isPresntedExist: Bool { get } var content: [SingleStudentEntity] { get } var selectedUserID: String? { get } var currentUserRole: UserRoleType { get } @@ -20,6 +21,7 @@ protocol MainActionProtocol: AnyObject { func updateIsLast(isLast: Bool) func updateIsPresentedFilterPage(isPresented: Bool) func updateIsPresentedMypagePage(isPresented: Bool) + func updateIsPresentedExistDialog(isPresented: Bool) func appendContent(content: [SingleStudentEntity]) func updateContent(content: [SingleStudentEntity]) func updateIsRefresh(isRefresh: Bool) diff --git a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift index be8928c2..cb63975d 100644 --- a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift +++ b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift @@ -134,7 +134,7 @@ struct MainView: View { SMSIcon(.profile, width: 32, height: 32) .clipShape(Circle()) .onTapGesture { - intent.myPageIsRequired() + state.currentUserRole == .student ? intent.myPageIsRequired() : intent.existIsRequired() } } } @@ -143,6 +143,23 @@ struct MainView: View { SMSIcon(.smsLogo, width: 80, height: 29) } } + .smsAlert( + title: "게스트 종료", + description: "게스트 이용을 종료하시겠습니까?", + isShowing: Binding( + get: { state.isPresntedExist }, + set: { _ in intent.existIsDismissed() } + ), + alertActions: [ + .init(text: "취소", style: .outline) { + intent.existIsDismissed() + }, + .init(text: "확인", style: .default) { + intent.existIsDismissed() + intent.logout() + } + ] + ) } .navigationViewStyle(.stack) } From d5b95f9cd04ff8ad97e2e28943d5703fccd36087 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:30:19 +0900 Subject: [PATCH 49/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20previewImages=204=EA=B0=9C=20=EC=9D=B4=EC=83=81=EC=9D=BC?= =?UTF-8?q?=EB=95=8C=20toast=20=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/MyPageProjectIntent.swift | 17 ++++-- .../Sources/Model/MyPageModel.swift | 1 + .../Sources/Model/MyPageProjectModel.swift | 6 +++ .../Sources/Scene/MyPageProjectView.swift | 52 +++++++++---------- .../Sources/Scene/MyPageView.swift | 7 +++ 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift index 8d7aa4d4..5b21ec10 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageProjectIntent.swift @@ -5,7 +5,7 @@ protocol MyPageProjectIntentProtocol { func projectToggleButtonDidTap(index: Int) func updateProjectName(index: Int, name: String) func updateIconImage(index: Int, image: PickedImageResult) - func appendPreviewImageButtonDidTap(index: Int) + func appendPreviewImageButtonDidTap(index: Int, previewsCount: Int) func appendPreviewImage(index: Int, image: PickedImageResult) func removePreviewImageDidTap(index: Int, previewIndex: Int) func updateProjectContent(index: Int, content: String) @@ -30,6 +30,7 @@ protocol MyPageProjectIntentProtocol { func projectEndAtDatePickerDismissed() func projectTechStackAppendButtonDidTap(index: Int) func projectTechStackAppendDismissed() + func projectToastDismissed() } extension MyPageIntent: MyPageProjectIntentProtocol { @@ -50,9 +51,13 @@ extension MyPageIntent: MyPageProjectIntentProtocol { } } - func appendPreviewImageButtonDidTap(index: Int) { - model?.updateFocusedProjectIndex(index: index) - model?.updateIsPresentedPreviewImagePicker(isPresented: true) + func appendPreviewImageButtonDidTap(index: Int, previewsCount: Int) { + if previewsCount == 4 { + model?.updateIsPresentedProjectToast(isPresented: true) + } else { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedPreviewImagePicker(isPresented: true) + } } func appendPreviewImage(index: Int, image: PickedImageResult) { @@ -159,4 +164,8 @@ extension MyPageIntent: MyPageProjectIntentProtocol { func projectTechStackAppendDismissed() { model?.updateIsPresentedProjectTechStackAppend(isPresented: false) } + + func projectToastDismissed() { + model?.updateIsPresentedProjectToast(isPresented: false) + } } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index 91bfea4e..27d41e5d 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -52,6 +52,7 @@ final class MyPageModel: ObservableObject, MyPageStateProtocol { @Published var isPresentedProjectStartAtDatePicker: Bool = false @Published var isPresentedProjectEndAtDatePicker: Bool = false @Published var isPresentedProjectTechStackAppend: Bool = false + @Published var isPresentedProjectToast: Bool = false // MARK: Prize @Published var prizeList: [PrizeModel] = [] diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift index 976ef893..e4abdf6a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift @@ -38,6 +38,7 @@ protocol MyPageProjectStateProtocol { var isPresentedProjectStartAtDatePicker: Bool { get } var isPresentedProjectEndAtDatePicker: Bool { get } var isPresentedProjectTechStackAppend: Bool { get } + var isPresentedProjectToast: Bool { get } } protocol MyPageProjectActionProtocol: AnyObject { @@ -65,6 +66,7 @@ protocol MyPageProjectActionProtocol: AnyObject { func updateIsPresentedProjectStartAtDatePicker(isPresented: Bool) func updateIsPresentedProjectEndAtDatePicker(isPresented: Bool) func updateIsPresentedProjectTechStackAppend(isPresented: Bool) + func updateIsPresentedProjectToast(isPresented: Bool) } extension MyPageModel: MyPageProjectActionProtocol { @@ -199,4 +201,8 @@ extension MyPageModel: MyPageProjectActionProtocol { func updateIsPresentedProjectTechStackAppend(isPresented: Bool) { isPresentedProjectTechStackAppend = isPresented } + + func updateIsPresentedProjectToast(isPresented: Bool) { + isPresentedProjectToast = isPresented + } } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift index 6564ce8d..e589f129 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift @@ -157,38 +157,36 @@ private extension MyPageProjectView { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 8) { let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] - ConditionView(projectPreviewImages.count < 4) { - imagePlaceholder(size: 132) - .overlay { - VStack(spacing: 4) { - SMSIcon(.photo) - SMSText( - "\(projectPreviewImages.count)/4", - font: .body2 - ) - .foregroundColor(.sms(.system(.black))) - } - } - .buttonWrapper { - intent.appendPreviewImageButtonDidTap(index: index) + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) } - } + } + .buttonWrapper { + intent.appendPreviewImageButtonDidTap(index: index, previewsCount: projectPreviewImages.count) + } ForEach(projectPreviewImages.indices, id: \.self) { previewIndex in let url = URL(string: projectPreviewImages[previewIndex]) LazyImage(url: url) { image in - if let image = image.image { - image - .resizable() - .frame(width: 132, height: 132) - .cornerRadius(8) - .overlay(alignment: .topTrailing) { - SMSIcon(.xmark) - .padding(4) - .buttonWrapper { - intent.removePreviewImageDidTap(index: index, previewIndex: previewIndex) - } - } + if let image = image.image { + image + .resizable() + .frame(width: 132, height: 132) + .cornerRadius(8) + .overlay(alignment: .topTrailing) { + SMSIcon(.xmark) + .padding(4) + .buttonWrapper { + intent.removePreviewImageDidTap(index: index, previewIndex: previewIndex) + } + } } else { imagePlaceholder(size: 132) } diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 17b75b56..c7d7bbe5 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -187,6 +187,13 @@ struct MyPageView: View { } } .animation(.default, value: state.isPresentedFormOfEmployeementSheet) + .smsToast( + text: "이미지는 최대 4개까지만 추가 할 수 있어요.", + isShowing: Binding( + get: { state.isPresentedProjectToast }, + set: { _ in intent.projectToastDismissed() } + ) + ) .smsAlert( title: "로그아웃", description: "정말로 로그아웃 하시겠습니까?", From 4ca3dc6d2ad7e4a66795f2179e9db9de838578b5 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:31:38 +0900 Subject: [PATCH 50/62] =?UTF-8?q?:fire:=20[#220]=20StudentDetailFeature=20?= =?UTF-8?q?/=20=EB=93=9C=EB=A6=BC=EB=B6=81=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=8B=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Model/StudentDetailModel.swift | 5 ----- .../Sources/Model/StudentDetailModelProtocol.swift | 2 -- .../Sources/Scene/StudentDetailView.swift | 10 ---------- 3 files changed, 17 deletions(-) diff --git a/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModel.swift b/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModel.swift index 53683190..7b7a5fdd 100644 --- a/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModel.swift +++ b/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModel.swift @@ -25,7 +25,6 @@ final class StudentDetailModel: ObservableObject, StudentDetailStateProtocol { } @Published var _studentDetailEntity: StudentDetailEntity? @Published var isLoading: Bool = false - @Published var isDownloading: Bool = false } // swiftlint: enable identifier_name @@ -41,8 +40,4 @@ extension StudentDetailModel: StudentDetailActionProtocol { func updateIsLoading(isLoading: Bool) { self.isLoading = isLoading } - - func updateIsDownloading(isDownloading: Bool) { - self.isDownloading = isDownloading - } } diff --git a/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModelProtocol.swift b/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModelProtocol.swift index ed14a12a..dbe02943 100644 --- a/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModelProtocol.swift +++ b/Projects/Feature/StudentDetailFeature/Sources/Model/StudentDetailModelProtocol.swift @@ -6,12 +6,10 @@ protocol StudentDetailStateProtocol { var userRole: UserRoleType { get } var studentDetailEntity: StudentDetailEntity? { get } var isLoading: Bool { get } - var isDownloading: Bool { get } } protocol StudentDetailActionProtocol: AnyObject { func updateUserRole(role: UserRoleType) func updateStudentDetailEntity(entity: StudentDetailEntity) func updateIsLoading(isLoading: Bool) - func updateIsDownloading(isDownloading: Bool) } diff --git a/Projects/Feature/StudentDetailFeature/Sources/Scene/StudentDetailView.swift b/Projects/Feature/StudentDetailFeature/Sources/Scene/StudentDetailView.swift index 17833886..660c6436 100644 --- a/Projects/Feature/StudentDetailFeature/Sources/Scene/StudentDetailView.swift +++ b/Projects/Feature/StudentDetailFeature/Sources/Scene/StudentDetailView.swift @@ -93,16 +93,6 @@ struct StudentDetailView: View { .onAppear { intent.onAppear() } - .smsToast( - text: "드림북을 다운로드 중입니다...", - isShowing: Binding( - get: { state.isDownloading }, - set: { _ in } - ) - ) { - LottieView(asset: .smsLoading) - .frame(width: 24, height: 24) - } .navigationBarHidden(true) } From e39d96b2419562e1c0e7fcc4af022e352e08b775 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:45:06 +0900 Subject: [PATCH 51/62] =?UTF-8?q?:sparkles:=20[#220]=20InputProjectInfo=20?= =?UTF-8?q?/=20preview=20=EC=9D=B4=EB=AF=B8=EC=A7=80=204=EA=B0=9C=20?= =?UTF-8?q?=EB=84=98=EA=B8=B0=EB=A9=B4=20=EA=B2=BD=EA=B3=A0=20=EB=9D=84?= =?UTF-8?q?=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Intent/InputProjectInfoIntent.swift | 14 +++- .../InputProjectInfoIntentProtocol.swift | 3 +- .../Sources/Model/InputProjectInfoModel.swift | 5 ++ .../Model/InputProjectInfoModelProtocol.swift | 2 + .../Sources/Scene/InputProjectInfoView.swift | 71 +++++++++++-------- .../Sources/Model/MyPageWorkInfoModel.swift | 2 +- 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift index fb98d4fa..c552a602 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntent.swift @@ -71,9 +71,13 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { model?.updateIconImage(index: index, image: image) } - func appendPreviewImageButtonDidTap(index: Int) { - model?.updateFocusedProjectIndex(index: index) - model?.updateIsPresentedPreviewImagePicker(isPresented: true) + func appendPreviewImageButtonDidTap(index: Int, previewsCount: Int) { + if previewsCount == 4 { + model?.updateIsPresentedToast(isPresented: true) + } else { + model?.updateFocusedProjectIndex(index: index) + model?.updateIsPresentedPreviewImagePicker(isPresented: true) + } } func appendPreviewImage(index: Int, image: PickedImageResult) { @@ -175,4 +179,8 @@ final class InputProjectInfoIntent: InputProjectInfoIntentProtocol { func techStackAppendDismissed() { model?.updateIsPresentedTechStackAppend(isPresented: false) } + + func toastDismissed() { + model?.updateIsPresentedToast(isPresented: false) + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift index 16e895ff..8e089af8 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Intent/InputProjectInfoIntentProtocol.swift @@ -7,7 +7,7 @@ protocol InputProjectInfoIntentProtocol { func projectToggleButtonDidTap(index: Int) func updateProjectName(index: Int, name: String) func updateIconImage(index: Int, image: PickedImageResult) - func appendPreviewImageButtonDidTap(index: Int) + func appendPreviewImageButtonDidTap(index: Int, previewsCount: Int) func appendPreviewImage(index: Int, image: PickedImageResult) func removePreviewImageDidTap(index: Int, previewIndex: Int) func updateProjectContent(index: Int, content: String) @@ -32,4 +32,5 @@ protocol InputProjectInfoIntentProtocol { func endAtDatePickerDismissed() func techStackAppendButtonDidTap(index: Int) func techStackAppendDismissed() + func toastDismissed() } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift index bf715843..6a635fa5 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModel.swift @@ -11,6 +11,7 @@ final class InputProjectInfoModel: ObservableObject, InputProjectInfoStateProtoc @Published var isPresentedStartAtDatePicker: Bool = false @Published var isPresentedEndAtDatePicker: Bool = false @Published var isPresentedTechStackAppend: Bool = false + @Published var isPresentedToast: Bool = false var focusedProjectIndex: Int = 0 } @@ -152,4 +153,8 @@ extension InputProjectInfoModel: InputProjectInfoActionProtocol { func updateIsPresentedTechStackAppend(isPresented: Bool) { self.isPresentedTechStackAppend = isPresented } + + func updateIsPresentedToast(isPresented: Bool) { + self.isPresentedToast = isPresented + } } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift index d87208f5..dbf20e33 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Model/InputProjectInfoModelProtocol.swift @@ -49,6 +49,7 @@ protocol InputProjectInfoStateProtocol { var isPresentedStartAtDatePicker: Bool { get } var isPresentedEndAtDatePicker: Bool { get } var isPresentedTechStackAppend: Bool { get } + var isPresentedToast: Bool { get } } protocol InputProjectInfoActionProtocol: AnyObject { @@ -76,4 +77,5 @@ protocol InputProjectInfoActionProtocol: AnyObject { func updateIsPresentedStartAtDatePicker(isPresented: Bool) func updateIsPresentedEndAtDatePicker(isPresented: Bool) func updateIsPresentedTechStackAppend(isPresented: Bool) + func updateIsPresentedToast(isPresented: Bool) } diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index 2f4a910e..cbfb54af 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -77,6 +77,13 @@ struct InputProjectInfoView: View { } ) ) + .smsToast( + text: "이미지는 최대 4개까지만 추가 할 수 있어요.", + isShowing: Binding( + get: { state.isPresentedToast }, + set: { _ in intent.toastDismissed() } + ) + ) .datePicker( isShowing: Binding( get: { state.isPresentedStartAtDatePicker }, @@ -202,38 +209,40 @@ private extension InputProjectInfoView { @ViewBuilder func projectPreviewImageList(index: Int) -> some View { - LazyHStack(spacing: 8) { - let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] - ConditionView(projectPreviewImages.count < 4) { - imagePlaceholder(size: 132) - .overlay { - VStack(spacing: 4) { - SMSIcon(.photo) - - SMSText( - "\(projectPreviewImages.count)/4", - font: .body2 - ) - .foregroundColor(.sms(.system(.black))) - } - } - .buttonWrapper { - intent.appendPreviewImageButtonDidTap(index: index) - } - } - - 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) + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 8) { + let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] +// ConditionView(projectPreviewImages.count < 4) { + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) } - } + } + .buttonWrapper { + intent.appendPreviewImageButtonDidTap(index: index, previewsCount: projectPreviewImages.count) + } +// } + + 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("미리보기 사진") diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift index ae8da0e6..bf02421f 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageWorkInfoModel.swift @@ -38,7 +38,7 @@ extension MyPageModel: MyPageWorkInfoActionProtocol { func updateSalary(salary: String) { guard let salaryInt = Int(salary).map({ String(min($0, 9999)) }) else { return } - self.salary = salaryInt + self.salary = salaryInt == "0" ? "상관없음" : "\(salaryInt)만원" } func updateFormOfEmployment(form: FormOfEmployment) { From ed155efc9281098ec0bea7bbd4995e502829161a Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:46:33 +0900 Subject: [PATCH 52/62] =?UTF-8?q?:lipstick:=20[#220]=20InputProjectFeature?= =?UTF-8?q?=20/=20ContionView=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/InputProjectInfoView.swift | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index cbfb54af..d95e6585 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -212,23 +212,21 @@ private extension InputProjectInfoView { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 8) { let projectPreviewImages = state.projectList[safe: index]?.previewImages ?? [] -// ConditionView(projectPreviewImages.count < 4) { - imagePlaceholder(size: 132) - .overlay { - VStack(spacing: 4) { - SMSIcon(.photo) - - SMSText( - "\(projectPreviewImages.count)/4", - font: .body2 - ) - .foregroundColor(.sms(.system(.black))) - } - } - .buttonWrapper { - intent.appendPreviewImageButtonDidTap(index: index, previewsCount: projectPreviewImages.count) + imagePlaceholder(size: 132) + .overlay { + VStack(spacing: 4) { + SMSIcon(.photo) + + SMSText( + "\(projectPreviewImages.count)/4", + font: .body2 + ) + .foregroundColor(.sms(.system(.black))) } -// } + } + .buttonWrapper { + intent.appendPreviewImageButtonDidTap(index: index, previewsCount: projectPreviewImages.count) + } ForEach(projectPreviewImages.indices, id: \.self) { previewIndex in Image(uiImage: projectPreviewImages[previewIndex].uiImage) From 5a2ba4dfea8897bb3159c42aafe4b2d9ac113f98 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 11:36:25 +0900 Subject: [PATCH 53/62] =?UTF-8?q?:sparkles:=20[#220]=20RootFeature=20/=20d?= =?UTF-8?q?efault=20=EA=B0=92=20splash=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/RootFeature/Sources/Model/RootModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/RootFeature/Sources/Model/RootModel.swift b/Projects/Feature/RootFeature/Sources/Model/RootModel.swift index a190ee71..db08c1ea 100644 --- a/Projects/Feature/RootFeature/Sources/Model/RootModel.swift +++ b/Projects/Feature/RootFeature/Sources/Model/RootModel.swift @@ -1,7 +1,7 @@ import Foundation final class RootModel: ObservableObject, RootStateProtocol { - @Published var sceneType: RootSceneType = .signin + @Published var sceneType: RootSceneType = .splash } extension RootModel: RootActionProtocol { From 84c4a1cda960fa20d0c3b2f0db38399eabe4d6e0 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:51:31 +0900 Subject: [PATCH 54/62] =?UTF-8?q?:sparkles:=20[#220]=20MyPageFeature=20/?= =?UTF-8?q?=20json=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ModifyStudentInformationRequestDTO.swift | 24 +++++++++---------- .../Sources/Endpoint/StudentEndpoint.swift | 3 +++ .../Sources/Scene/InputPrizeInfoView.swift | 4 ++-- .../Sources/Scene/InputProjectInfoView.swift | 4 +++- .../Sources/Intent/MyPageIntent.swift | 6 ++--- .../Sources/Scene/MyPagePrizeView.swift | 3 ++- .../Sources/Scene/MyPageProjectView.swift | 3 ++- 7 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift b/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift index 2f592e93..4169ec4c 100644 --- a/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift +++ b/Projects/Domain/StudentDomain/Interface/DTO/Request/ModifyStudentInformationRequestDTO.swift @@ -1,50 +1,50 @@ import Foundation public struct ModifyStudentInformationRequestDTO: Encodable { - public let certificate: [String] + public let certificates: [String] public let contactEmail: String public let formOfEmployment: FormOfEmployment public let gsmAuthenticationScore: Int public let introduce: String - public let languageCertificate: [LanguageCertificate] + public let languageCertificates: [LanguageCertificate] public let major: String public let militaryService: MilitaryServiceType public let portfolioURL: String public let profileImgURL: String - public let region: [String] + public let regions: [String] public let salary: Int public let techStacks: [String] public let projects: [Project] public let prizes: [Prize] public init( - certificate: [String], + certificates: [String], contactEmail: String, formOfEmployment: FormOfEmployment, gsmAuthenticationScore: Int, introduce: String, - languageCertificate: [LanguageCertificate], + languageCertificates: [LanguageCertificate], major: String, militaryService: MilitaryServiceType, portfolioURL: String, profileImgURL: String, - region: [String], + regions: [String], salary: Int, techStacks: [String], projects: [Project] = [], prizes: [Prize] = [] ) { - self.certificate = certificate + self.certificates = certificates self.contactEmail = contactEmail self.formOfEmployment = formOfEmployment self.gsmAuthenticationScore = gsmAuthenticationScore self.introduce = introduce - self.languageCertificate = languageCertificate + self.languageCertificates = languageCertificates self.major = major self.militaryService = militaryService self.portfolioURL = portfolioURL self.profileImgURL = profileImgURL - self.region = region + self.regions = regions self.salary = salary self.techStacks = techStacks self.projects = projects @@ -52,17 +52,17 @@ public struct ModifyStudentInformationRequestDTO: Encodable { } enum CodingKeys: String, CodingKey { - case certificate + case certificates case contactEmail case formOfEmployment case gsmAuthenticationScore case introduce - case languageCertificate + case languageCertificates case major case militaryService case portfolioURL = "portfolioUrl" case profileImgURL = "profileImgUrl" - case region + case regions case salary case techStacks case projects diff --git a/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift b/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift index 23bde0a7..b3426b43 100644 --- a/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift +++ b/Projects/Domain/StudentDomain/Sources/Endpoint/StudentEndpoint.swift @@ -72,6 +72,9 @@ extension StudentEndpoint: SMSEndpoint { let requestDictionary = Dictionary(uniqueKeysWithValues: requestQuery) return .requestParameters(query: requestDictionary) + case let .modifyInformation(req): + return .requestJSONEncodable(req) + default: return .requestPlain } diff --git a/Projects/Feature/InputPrizeInfoFeature/Sources/Scene/InputPrizeInfoView.swift b/Projects/Feature/InputPrizeInfoFeature/Sources/Scene/InputPrizeInfoView.swift index 6c880358..8ceb0293 100644 --- a/Projects/Feature/InputPrizeInfoFeature/Sources/Scene/InputPrizeInfoView.swift +++ b/Projects/Feature/InputPrizeInfoFeature/Sources/Scene/InputPrizeInfoView.swift @@ -69,9 +69,9 @@ struct InputPrizeInfoView: View { let collapsed = state.collapsedPrize[safe: index] ?? false VStack(alignment: .leading, spacing: 24) { HStack(spacing: 16) { - SMSText("수상", font: .title1) + let prizeName = state.prizeList[safe: index]?.name ?? "" + SMSText(prizeName.isEmpty ? "수상" : prizeName, font: .title1) .foregroundColor(.sms(.system(.black))) - Spacer() SMSIcon(.downChevron) diff --git a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift index d95e6585..32e0ae36 100644 --- a/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift +++ b/Projects/Feature/InputProjectInfoFeature/Sources/Scene/InputProjectInfoView.swift @@ -122,7 +122,9 @@ struct InputProjectInfoView: View { let collapsed = state.collapsedProject[safe: index] ?? false VStack(alignment: .leading, spacing: 24) { HStack(spacing: 16) { - SMSText("프로젝트", font: .title1) + let projectName = state.projectList[safe: index]?.name ?? "" + + SMSText(projectName.isEmpty ? "프로젝트" : projectName, font: .title1) .foregroundColor(.sms(.system(.black))) Spacer() diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 85496124..1dd890b0 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -120,17 +120,17 @@ final class MyPageIntent: MyPageIntentProtocol { Task { do { let modifyInformationRequest = ModifyStudentInformationRequestDTO( - certificate: state.certificates, + certificates: state.certificates, contactEmail: state.email, formOfEmployment: FormOfEmployment(rawValue: state.formOfEmployment.rawValue) ?? .fullTime, gsmAuthenticationScore: Int(state.gsmScore) ?? 0, introduce: state.introduce, - languageCertificate: state.languageList.map { $0.toDTO() }, + languageCertificates: state.languageList.map { $0.toDTO() }, major: state.major, militaryService: state.selectedMilitaryServiceType, portfolioURL: state.portfolioURL, profileImgURL: state.profileURL, - region: state.workRegionList, + regions: state.workRegionList, salary: Int(state.salary) ?? 0, techStacks: state.techStacks, projects: state.projectList.map { diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift index 1ebe2505..a0417bad 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPagePrizeView.swift @@ -57,7 +57,8 @@ struct MyPagePrizeView: View { } } header: { HStack(spacing: 16) { - SMSText(state.prizeList[safe: index]?.name ?? "", font: .title1) + let prizeName = state.prizeList[safe: index]?.name ?? "" + SMSText(prizeName.isEmpty ? "수상" : prizeName, font: .title1) .foregroundColor(.sms(.system(.black))) Spacer() diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift index e589f129..6941f861 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageProjectView.swift @@ -73,7 +73,8 @@ struct MyPageProjectView: View { } } header: { HStack(spacing: 16) { - SMSText(state.projectList[safe: index]?.name ?? "", font: .title1) + let projectName = state.projectList[safe: index]?.name ?? "" + SMSText(projectName.isEmpty ? "프로젝트" : projectName, font: .title1) .foregroundColor(.sms(.system(.black))) Spacer() From c20025dec65d50361a6e91bb10d5b530f14d761a Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:01:23 +0900 Subject: [PATCH 55/62] Update Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift Co-authored-by: baegteun --- .../AuthDomain/Sources/Repository/AuthRepositoryImpl.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift b/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift index 1c41f5b1..0a5aaeda 100644 --- a/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift +++ b/Projects/Domain/AuthDomain/Sources/Repository/AuthRepositoryImpl.swift @@ -18,7 +18,6 @@ struct AuthRepositoryImpl: AuthRepository { } func logout() async throws { - #warning("통신 오류 남") try await remoteAuthDataSource.logout() try await localAuthDataSource.logout() } From 1e637a94f35ea40a32c68193d7781b0c8189be28 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:04:45 +0900 Subject: [PATCH 56/62] =?UTF-8?q?:test=5Ftube:=20[#220]=20InputSchoolInfoF?= =?UTF-8?q?eature=20/=20testcode=20=EC=88=98=EC=A0=95'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tests/InputSchoolLifeInfoFeatureTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift b/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift index 0f715bb5..a241f50c 100644 --- a/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift +++ b/Projects/Feature/InputSchoolLifeInfoFeature/Tests/InputSchoolLifeInfoFeatureTest.swift @@ -5,7 +5,7 @@ import InputSchoolLifeInfoFeatureInterface final class DummyInputSchoolLifeDelegate: InputSchoolLifeDelegate { func schoolLifePrevButtonDidTap() {} - func completeToInputSchoolLife(input: InputSchoolLifeInfoFeatureInterface.InputSchoolLifeInformationObject) {} + func completeToInputSchoolLife(gsmAuthenticationScore: Int) {} } final class InputSchoolLifeInfoFeatureTests: XCTestCase { From 08778052cd97404b6aaa203cd1b10b6f38a73aed Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:21:40 +0900 Subject: [PATCH 57/62] =?UTF-8?q?:recycle:=20[#220]=20MypageFeature=20/=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=86=8D=EB=8F=84=20=EC=A4=84=EC=9D=B4?= =?UTF-8?q?=EA=B8=B0=EC=9C=84=ED=95=B4=20=EB=85=B8=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MyPageView.swift | 175 +++++++++--------- 1 file changed, 90 insertions(+), 85 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index c7d7bbe5..bb829336 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -27,91 +27,7 @@ struct MyPageView: View { Spacer() .frame(height: 1) - ScrollView { - LazyVStack(pinnedViews: [.sectionHeaders]) { - Group { - SMSSeparator() - .padding(.vertical, 16) - - MyPageProfileView(intent: intent, state: state, geometry: geometry) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageSchoolLifeView(intent: intent, state: state) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageWorkInfoView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageMilitaryView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - } - - Group { - SMSSeparator() - .padding(.vertical, 16) - - MyPageCertificateView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageLanguageView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ), - geometry: geometry - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageProjectView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ), - geometry: geometry - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPagePrizeView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - } - } - } + myPageView(geometry: geometry) CTAButton(text: "저장") { intent.modifyToInputAllInfo(state: state) @@ -430,4 +346,93 @@ struct MyPageView: View { } } } + + @ViewBuilder + func myPageView(geometry: GeometryProxy) -> some View { + ScrollView { + LazyVStack(pinnedViews: [.sectionHeaders]) { + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageProfileView(intent: intent, state: state, geometry: geometry) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageSchoolLifeView(intent: intent, state: state) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageWorkInfoView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageMilitaryView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } + + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageCertificateView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageLanguageView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageProjectView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPagePrizeView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } + } + } + } } From 9e6f33391d99772343ca0ceb9bac2b4280b3bd3b Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 24 Aug 2023 08:50:09 +0900 Subject: [PATCH 58/62] =?UTF-8?q?:recycle:=20[#220]=20Comment=20/=20?= =?UTF-8?q?=EC=BD=94=EB=A9=98=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/Entity/MyPageEntity.swift | 8 +- .../Response/FetchMyProfileResponseDTO.swift | 122 +++++++++++- .../UseCase/FetchMyProfileUseCaseSpy.swift | 24 +++ .../Sources/Intent/MainIntent.swift | 12 +- .../Sources/Intent/MainIntentProtocol.swift | 4 +- .../MainFeature/Sources/Model/MainModel.swift | 8 +- .../Sources/Model/MainModelProtocol.swift | 6 +- .../MainFeature/Sources/Scene/MainView.swift | 10 +- Projects/Feature/MyPageFeature/Project.swift | 1 + .../Sources/Intent/MyPageIntent.swift | 30 ++- .../Sources/Intent/MyPageIntentProtocol.swift | 4 +- .../Sources/Model/MyPageModel.swift | 6 +- .../Sources/Model/MyPageModelProtocol.swift | 4 +- .../Sources/Model/MyPagePrizeModel.swift | 5 + .../Sources/Model/MyPageProjectModel.swift | 5 + .../Sources/Scene/MyPageView.swift | 187 +++++++++--------- 16 files changed, 305 insertions(+), 131 deletions(-) diff --git a/Projects/Domain/UserDomain/Interface/Entity/MyPageEntity.swift b/Projects/Domain/UserDomain/Interface/Entity/MyPageEntity.swift index f221e3aa..eb782156 100644 --- a/Projects/Domain/UserDomain/Interface/Entity/MyPageEntity.swift +++ b/Projects/Domain/UserDomain/Interface/Entity/MyPageEntity.swift @@ -20,6 +20,8 @@ public struct MyPageEntity: Equatable { public let languageCertificates: [LanguageCertificateEntity] public let certificates: [String] public let techStacks: [String] + public let proejcts: [ProjectEntity] + public let prizes: [PrizeEntity] public init( name: String, @@ -39,7 +41,9 @@ public struct MyPageEntity: Equatable { salary: Int, languageCertificates: [LanguageCertificateEntity], certificates: [String], - techStacks: [String] + techStacks: [String], + projects: [ProjectEntity], + prizes: [PrizeEntity] ) { self.name = name self.introduce = introduce @@ -59,5 +63,7 @@ public struct MyPageEntity: Equatable { self.languageCertificates = languageCertificates self.certificates = certificates self.techStacks = techStacks + self.proejcts = projects + self.prizes = prizes } } diff --git a/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift b/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift index a228059e..c323e424 100644 --- a/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift +++ b/Projects/Domain/UserDomain/Sources/DTO/Response/FetchMyProfileResponseDTO.swift @@ -21,6 +21,8 @@ struct FetchMyProfileResponseDTO: Decodable { let languageCertificates: [LanguageCertificateResponseDTO] let certificates: [String] let techStacks: [String] + let projects: [ProjectResponseDTO] + let prizes: [PrizeResponseDTO] enum CodingKeys: String, CodingKey { case name @@ -41,6 +43,8 @@ struct FetchMyProfileResponseDTO: Decodable { case languageCertificates case certificates case techStacks + case projects + case prizes } } @@ -54,14 +58,128 @@ extension FetchMyProfileResponseDTO { case score } } + + struct ProjectResponseDTO: Decodable { + public let name: String + public let iconImageURL: String + public let previewImageURLs: [String] + public let description: String + public let links: [Link] + public let techStacks: [String] + public let myActivity: String + public let inProgress: InProgress + + public init( + name: String, + iconImageURL: String, + previewImageURLs: [String], + description: String, + links: [Link], + techStacks: [String], + myActivity: String, + inProgress: InProgress + ) { + self.name = name + self.iconImageURL = iconImageURL + self.previewImageURLs = previewImageURLs + self.description = description + self.links = links + self.techStacks = techStacks + self.myActivity = myActivity + self.inProgress = inProgress + } + + enum CodingKeys: String, CodingKey { + case name + case iconImageURL = "icon" + case previewImageURLs = "previewImages" + case description + case links + case techStacks + case myActivity + case inProgress + } + } + + struct PrizeResponseDTO: Decodable { + public let name: String + public let type: String + public let date: String + + public init( + name: String, + type: String, + date: String + ) { + self.name = name + self.type = type + self.date = date + } + } } +extension FetchMyProfileResponseDTO.ProjectResponseDTO { + struct Link: Decodable { + public let name: String + public let url: String + + public init(name: String, url: String) { + self.name = name + self.url = url + } + } + + struct InProgress: Decodable { + public let start: String + public let end: String? + + public init(start: String, end: String?) { + self.start = start + self.end = end + } + } +} + + extension FetchMyProfileResponseDTO.LanguageCertificateResponseDTO { func toDomain() -> LanguageCertificateEntity { LanguageCertificateEntity(name: name, score: score) } } +extension FetchMyProfileResponseDTO.ProjectResponseDTO { + func toDomain() -> ProjectEntity { + ProjectEntity( + name: name, + iconImageURL: iconImageURL, + previewImageURLs: previewImageURLs, + description: description, + links: links.map { $0.toDomain() }, + techStacks: techStacks, + myActivity: myActivity, + inProgress: inProgress.toDomain() + ) + } +} + +extension FetchMyProfileResponseDTO.PrizeResponseDTO { + func toDomain() -> PrizeEntity { + PrizeEntity(name: name, type: type, date: date) + } +} + +extension FetchMyProfileResponseDTO.ProjectResponseDTO.Link { + func toDomain() -> ProjectEntity.LinkEntity { + ProjectEntity.LinkEntity(name: name, url: url) + } +} + +extension FetchMyProfileResponseDTO.ProjectResponseDTO.InProgress { + func toDomain() -> ProjectEntity.InProgressEntity { + ProjectEntity.InProgressEntity(start: start, end: end) + } +} + extension FetchMyProfileResponseDTO { func toDomain() -> MyPageEntity { MyPageEntity( @@ -82,7 +200,9 @@ extension FetchMyProfileResponseDTO { salary: salary, languageCertificates: languageCertificates.map { $0.toDomain() }, certificates: certificates, - techStacks: techStacks + techStacks: techStacks, + projects: projects.map { $0.toDomain() }, + prizes: prizes.map { $0.toDomain() } ) } } diff --git a/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift b/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift index 8dd138ad..26374f94 100644 --- a/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift +++ b/Projects/Domain/UserDomain/Testing/UseCase/FetchMyProfileUseCaseSpy.swift @@ -29,6 +29,30 @@ final class FetchMyProfileUseCaseSpy: FetchMyProfileUseCase { "Swift", "Tuist", "MicroFeatures" + ], + projects: [ + .init( + name: "asdf", + iconImageURL: "https://avatars.githubusercontent.com/u/74440939?v=4", + previewImageURLs: [ + "https://avatars.githubusercontent.com/u/74440939?v=4" + ], + description: "최형우다", + links: [ + .init( + name: "와우", + url: "https://www.github.com" + ) + ], + techStacks: [ + "iOS" + ], + myActivity: "나는 짱이다", + inProgress: .init(start: "", end: "") + ) + ], + prizes: [ + .init(name: "1234", type: "와우", date: "2023.12.11") ] ) } diff --git a/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift b/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift index d6c27d4a..11dbda20 100644 --- a/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift +++ b/Projects/Feature/MainFeature/Sources/Intent/MainIntent.swift @@ -67,11 +67,11 @@ final class MainIntent: MainIntentProtocol { } func myPageIsRequired() { - model?.updateIsPresentedMypagePage(isPresented: true) + model?.updateIsPresentedMypage(isPresented: true) } func myPageDismissed() { - model?.updateIsPresentedMypagePage(isPresented: false) + model?.updateIsPresentedMypage(isPresented: false) } func studentDidSelect(userID: String) { @@ -86,12 +86,12 @@ final class MainIntent: MainIntentProtocol { mainDelegate?.logout() } - func existIsRequired() { - model?.updateIsPresentedExistDialog(isPresented: true) + func exitIsRequired() { + model?.updateIsPresentedExitDialog(isPresented: true) } - func existIsDismissed() { - model?.updateIsPresentedExistDialog(isPresented: false) + func exitIsDismissed() { + model?.updateIsPresentedExitDialog(isPresented: false) } } diff --git a/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift b/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift index 6454ba65..5dfcaeb6 100644 --- a/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift +++ b/Projects/Feature/MainFeature/Sources/Intent/MainIntentProtocol.swift @@ -12,6 +12,6 @@ protocol MainIntentProtocol: FilterDelegate, MyPageDelegate { func myPageDismissed() func studentDidSelect(userID: String) func studentDetailDismissed() - func existIsRequired() - func existIsDismissed() + func exitIsRequired() + func exitIsDismissed() } diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift index b487c25a..e1e83d0d 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModel.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModel.swift @@ -27,7 +27,7 @@ final class MainModel: ObservableObject, MainStateProtocol { @Published var _content: [SingleStudentEntity] = [] @Published var isPresentedFilterPage: Bool = false @Published var isPresentedMyPage: Bool = false - @Published var isPresntedExist: Bool = false + @Published var isPresntedExit: Bool = false @Published var selectedUserID: String? @Published var currentUserRole: UserRoleType = .guest @Published var filterOption: FilterOption? @@ -51,12 +51,12 @@ extension MainModel: MainActionProtocol { self.isPresentedFilterPage = isPresented } - func updateIsPresentedMypagePage(isPresented: Bool) { + func updateIsPresentedMypage(isPresented: Bool) { self.isPresentedMyPage = isPresented } - func updateIsPresentedExistDialog(isPresented: Bool) { - self.isPresntedExist = isPresented + func updateIsPresentedExitDialog(isPresented: Bool) { + self.isPresntedExit = isPresented } func appendContent(content: [SingleStudentEntity]) { diff --git a/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift b/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift index cc0ade90..a1c57ced 100644 --- a/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift +++ b/Projects/Feature/MainFeature/Sources/Model/MainModelProtocol.swift @@ -8,7 +8,7 @@ protocol MainStateProtocol { var isRefresh: Bool { get } var isPresentedFilterPage: Bool { get } var isPresentedMyPage: Bool { get } - var isPresntedExist: Bool { get } + var isPresntedExit: Bool { get } var content: [SingleStudentEntity] { get } var selectedUserID: String? { get } var currentUserRole: UserRoleType { get } @@ -20,8 +20,8 @@ protocol MainActionProtocol: AnyObject { func updateTotalSize(totalSize: Int) func updateIsLast(isLast: Bool) func updateIsPresentedFilterPage(isPresented: Bool) - func updateIsPresentedMypagePage(isPresented: Bool) - func updateIsPresentedExistDialog(isPresented: Bool) + func updateIsPresentedMypage(isPresented: Bool) + func updateIsPresentedExitDialog(isPresented: Bool) func appendContent(content: [SingleStudentEntity]) func updateContent(content: [SingleStudentEntity]) func updateIsRefresh(isRefresh: Bool) diff --git a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift index cb63975d..7a83c2a6 100644 --- a/Projects/Feature/MainFeature/Sources/Scene/MainView.swift +++ b/Projects/Feature/MainFeature/Sources/Scene/MainView.swift @@ -134,7 +134,7 @@ struct MainView: View { SMSIcon(.profile, width: 32, height: 32) .clipShape(Circle()) .onTapGesture { - state.currentUserRole == .student ? intent.myPageIsRequired() : intent.existIsRequired() + state.currentUserRole == .student ? intent.myPageIsRequired() : intent.exitIsRequired() } } } @@ -147,15 +147,15 @@ struct MainView: View { title: "게스트 종료", description: "게스트 이용을 종료하시겠습니까?", isShowing: Binding( - get: { state.isPresntedExist }, - set: { _ in intent.existIsDismissed() } + get: { state.isPresntedExit }, + set: { _ in intent.exitIsDismissed() } ), alertActions: [ .init(text: "취소", style: .outline) { - intent.existIsDismissed() + intent.exitIsDismissed() }, .init(text: "확인", style: .default) { - intent.existIsDismissed() + intent.exitIsDismissed() intent.logout() } ] diff --git a/Projects/Feature/MyPageFeature/Project.swift b/Projects/Feature/MyPageFeature/Project.swift index ca948c36..d6db3183 100644 --- a/Projects/Feature/MyPageFeature/Project.swift +++ b/Projects/Feature/MyPageFeature/Project.swift @@ -8,6 +8,7 @@ let project = Project.makeModule( targets: [.interface, .unitTest, .demo], internalDependencies: [ .Feature.BaseFeature, + .Feature.TechStackAppendFeatureInterface, .Domain.UserDomainInterface, .Domain.StudentDomainInterface, .Domain.AuthDomainInterface diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 1dd890b0..9c2ed76c 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -61,19 +61,36 @@ final class MyPageIntent: MyPageIntentProtocol { } ) model?.updateCertificates(certificates: profile.certificates) - model?.updateTechStacks(techStacks: profile.techStacks) + model?.updateTechStacks(techStacks: profile.techStacks) + model?.updateProjectList(projectList: profile.proejcts.map { + ProjectModel( + name: $0.name, + iconImage: $0.iconImageURL, + previewImages: $0.previewImageURLs, + content: $0.description, + techStacks: $0.techStacks, + mainTask: $0.myActivity, + startAt: $0.inProgress.start, + endAt: $0.inProgress.end, + isInProgress: $0.inProgress, + relatedLinks: $0.links + ) + }) + model?.updatePrizeList(prizeList: profile.prizes.map { + PrizeModel(name: $0.name, prize: $0.type, prizeAt: $0.date.) + }) } catch { model?.updateIsError(isError: true) } } } - func existActionSheetIsRequired() { - model?.updateIsPresentedExistActionSheet(isPresented: true) + func exitActionSheetIsRequired() { + model?.updateIsPresentedExitBottomSheet(isPresented: true) } - func existActionSheetDismissed() { - model?.updateIsPresentedExistActionSheet(isPresented: false) + func exitActionSheetDismissed() { + model?.updateIsPresentedExitBottomSheet(isPresented: false) } func logoutDialogIsRequired() { @@ -135,7 +152,7 @@ final class MyPageIntent: MyPageIntentProtocol { techStacks: state.techStacks, projects: state.projectList.map { let startAtString = $0.startAt.toStringCustomFormat(format: "yyyy.MM") - let endAtString = $0.endAt?.toStringCustomFormat(format: "yyyy.MM") ?? "" + let endAtString = $0.endAt?.toStringCustomFormat(format: "yyyy.MM") ?? "개발중" return $0.toDTO( iconURL: $0.iconImage, @@ -149,6 +166,7 @@ final class MyPageIntent: MyPageIntentProtocol { try await modifyInformationUseCase.execute(req: modifyInformationRequest) } catch { + model?.updateIsError(isError: true) } } } diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift index ad063d73..04332a61 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntentProtocol.swift @@ -10,8 +10,8 @@ protocol MyPageIntentProtocol: MyPageProjectIntentProtocol, MyPagePrizeIntentProtocol { func onAppear() - func existActionSheetIsRequired() - func existActionSheetDismissed() + func exitActionSheetIsRequired() + func exitActionSheetDismissed() func logoutDialogIsRequired() func logoutDialogDismissed() func logoutDialogIsComplete() diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift index 27d41e5d..99272fe8 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModel.swift @@ -4,7 +4,7 @@ import StudentDomainInterface final class MyPageModel: ObservableObject, MyPageStateProtocol { // MARK: MyPage @Published var isError: Bool = false - @Published var isPresentedExistActionSheet: Bool = false + @Published var isPresentedExitBottomSheet: Bool = false @Published var isPresentedLogoutDialog: Bool = false @Published var isPresentedWithdrawalDialog: Bool = false @@ -66,8 +66,8 @@ extension MyPageModel: MyPageActionProtocol { self.isError = isError } - func updateIsPresentedExistActionSheet(isPresented: Bool) { - self.isPresentedExistActionSheet = isPresented + func updateIsPresentedExitBottomSheet(isPresented: Bool) { + self.isPresentedExitBottomSheet = isPresented } func updateIsPresentedLogoutDialog(isPresented: Bool) { diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift index 3dd410be..1e405191 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageModelProtocol.swift @@ -8,7 +8,7 @@ protocol MyPageStateProtocol: MyPageProjectStateProtocol, MyPagePrizeStateProtocol { var isError: Bool { get } - var isPresentedExistActionSheet: Bool { get } + var isPresentedExitBottomSheet: Bool { get } var isPresentedLogoutDialog: Bool { get } var isPresentedWithdrawalDialog: Bool { get } } @@ -24,7 +24,7 @@ protocol MyPageActionProtocol: MyPageProjectActionProtocol, MyPagePrizeActionProtocol { func updateIsError(isError: Bool) - func updateIsPresentedExistActionSheet(isPresented: Bool) + func updateIsPresentedExitBottomSheet(isPresented: Bool) func updateIsPresentedLogoutDialog(isPresented: Bool) func updateIsPresentedWithdrawalDialog(isPresented: Bool) } diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift index e126fe20..b0611d3a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPagePrizeModel.swift @@ -20,6 +20,7 @@ protocol MyPagePrizeStateProtocol { protocol MyPagePrizeActionProtocol: AnyObject { func toggleCollapsedPrize(index: Int) + func updatePrizeList(prizeList: [PrizeModel]) func updatePrizeName(index: Int, name: String) func updatePrizePrize(index: Int, prize: String) func updatePrizePrizeAt(index: Int, prizeAt: Date) @@ -35,6 +36,10 @@ extension MyPageModel: MyPagePrizeActionProtocol { self.collapsedPrize[index].toggle() } + func updatePrizeList(prizeList: [PrizeModel]) { + self.prizeList = prizeList + } + func updatePrizeName(index: Int, name: String) { guard prizeList[safe: index] != nil else { return } self.prizeList[index].name = name diff --git a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift index e4abdf6a..1674a158 100644 --- a/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift +++ b/Projects/Feature/MyPageFeature/Sources/Model/MyPageProjectModel.swift @@ -43,6 +43,7 @@ protocol MyPageProjectStateProtocol { protocol MyPageProjectActionProtocol: AnyObject { func toggleCollapsedProject(index: Int) + func updateProjectList(projectList: [ProjectModel]) func updateProjectName(index: Int, name: String) func updateIconImage(index: Int, imageURL: String) func appendPreviewImage(index: Int, imageURL: String) @@ -75,6 +76,10 @@ extension MyPageModel: MyPageProjectActionProtocol { collapsedProject[index].toggle() } + func updateProjectList(projectList: [ProjectModel]) { + self.projectList = projectList + } + func updateProjectName(index: Int, name: String) { guard projectList[safe: index] != nil else { return } projectList[index].name = name diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index bb829336..0f7efc39 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -27,7 +27,91 @@ struct MyPageView: View { Spacer() .frame(height: 1) - myPageView(geometry: geometry) + ScrollView { + LazyVStack(pinnedViews: [.sectionHeaders]) { + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageProfileView(intent: intent, state: state, geometry: geometry) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageSchoolLifeView(intent: intent, state: state) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageWorkInfoView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageMilitaryView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } + + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageCertificateView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageLanguageView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageProjectView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPagePrizeView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } + } + } CTAButton(text: "저장") { intent.modifyToInputAllInfo(state: state) @@ -46,14 +130,14 @@ struct MyPageView: View { .edgesIgnoringSafeArea([.bottom]) .smsBottomSheet( isShowing: Binding( - get: { state.isPresentedExistActionSheet }, - set: { _ in intent.existActionSheetDismissed() } + get: { state.isPresentedExitBottomSheet }, + set: { _ in intent.exitActionSheetDismissed() } ) ) { VStack(alignment: .leading, spacing: 32) { Button { intent.logoutDialogIsRequired() - intent.existActionSheetDismissed() + intent.exitActionSheetDismissed() } label: { HStack(spacing: 12) { SMSIcon(.logout) @@ -67,7 +151,7 @@ struct MyPageView: View { Button { intent.withdrawalDialogIsRequired() - intent.existActionSheetDismissed() + intent.exitActionSheetDismissed() } label: { HStack(spacing: 12) { SMSIcon(.redPerson) @@ -82,7 +166,7 @@ struct MyPageView: View { .padding(.top, 12) .padding(.horizontal, 20) } - .animation(.default, value: state.isPresentedExistActionSheet) + .animation(.default, value: state.isPresentedExitBottomSheet) .smsBottomSheet( isShowing: Binding( get: { state.isPresentedMilitarySheet }, @@ -146,7 +230,7 @@ struct MyPageView: View { ToolbarItem(placement: .navigationBarTrailing) { SMSIcon(.logoutLine) .onTapGesture { - intent.existActionSheetIsRequired() + intent.exitActionSheetIsRequired() } } } @@ -346,93 +430,4 @@ struct MyPageView: View { } } } - - @ViewBuilder - func myPageView(geometry: GeometryProxy) -> some View { - ScrollView { - LazyVStack(pinnedViews: [.sectionHeaders]) { - Group { - SMSSeparator() - .padding(.vertical, 16) - - MyPageProfileView(intent: intent, state: state, geometry: geometry) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageSchoolLifeView(intent: intent, state: state) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageWorkInfoView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageMilitaryView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - } - - Group { - SMSSeparator() - .padding(.vertical, 16) - - MyPageCertificateView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageLanguageView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ), - geometry: geometry - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageProjectView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ), - geometry: geometry - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPagePrizeView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - } - } - } - } } From 5b5d34e9f5caf1af08bfd39f011a026d952fbdf3 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 24 Aug 2023 09:15:58 +0900 Subject: [PATCH 59/62] =?UTF-8?q?:recycle:=20[#220]=20MypageFeature=20/=20?= =?UTF-8?q?ToModel=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Intent/MyPageIntent.swift | 82 ++++++++++++++----- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift index 9c2ed76c..84eaa11a 100644 --- a/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift +++ b/Projects/Feature/MyPageFeature/Sources/Intent/MyPageIntent.swift @@ -57,28 +57,21 @@ final class MyPageIntent: MyPageIntentProtocol { model?.updateSalary(salary: "\(profile.salary)") model?.updateLanguageScores( languages: profile.languageCertificates.map { - LanguageModel(name: $0.name, score: $0.score) + $0.toModel() } ) model?.updateCertificates(certificates: profile.certificates) - model?.updateTechStacks(techStacks: profile.techStacks) - model?.updateProjectList(projectList: profile.proejcts.map { - ProjectModel( - name: $0.name, - iconImage: $0.iconImageURL, - previewImages: $0.previewImageURLs, - content: $0.description, - techStacks: $0.techStacks, - mainTask: $0.myActivity, - startAt: $0.inProgress.start, - endAt: $0.inProgress.end, - isInProgress: $0.inProgress, - relatedLinks: $0.links - ) - }) - model?.updatePrizeList(prizeList: profile.prizes.map { - PrizeModel(name: $0.name, prize: $0.type, prizeAt: $0.date.) - }) + model?.updateTechStacks(techStacks: profile.techStacks) + model?.updateProjectList( + projectList: profile.proejcts.map { + $0.toModel() + } + ) + model?.updatePrizeList( + prizeList: profile.prizes.map { + $0.toModel() + } + ) } catch { model?.updateIsError(isError: true) } @@ -232,3 +225,54 @@ extension PrizeModel { ) } } + +extension ProjectEntity { + func toModel() -> ProjectModel { + ProjectModel.init( + name: name, + iconImage: iconImageURL, + previewImages: previewImageURLs, + content: description, + techStacks: Set(techStacks), + mainTask: myActivity, + startAt: inProgress.start.toISODate( + timeZone: .init(identifier: "GMT") ?? .current + ), + endAt: inProgress.end?.toISODate( + timeZone: .init(identifier: "GMT") ?? .current + ), + isInProgress: ((inProgress.end?.isEmpty) != nil), + relatedLinks: links.map { $0.toModel() } + ) + } +} + +extension ProjectEntity.LinkEntity { + func toModel() -> ProjectModel.RelatedLink { + ProjectModel.RelatedLink.init( + name: name, + url: url + ) + } +} + +extension PrizeEntity { + func toModel() -> PrizeModel { + PrizeModel.init( + name: name, + prize: type, + prizeAt: date.toISODate( + timeZone: .init(identifier: "GMT") ?? .current + ) + ) + } +} + +extension LanguageCertificateEntity { + func toModel() -> LanguageModel { + LanguageModel.init( + name: name, + score: score + ) + } +} From 26ba61d145d25865d921ba49435b2e49fa4b3e7e Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 24 Aug 2023 09:48:34 +0900 Subject: [PATCH 60/62] =?UTF-8?q?:recycle:=20[#220]=20MyPageFeature=20/=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/MyPageFeature/Sources/Scene/MyPageView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 0f7efc39..3f857600 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -123,9 +123,10 @@ struct MyPageView: View { } .ignoresSafeArea() } - } - .onAppear { - intent.onAppear() + .onAppear { + intent.onAppear() + } + .hideKeyboardWhenTap() } .edgesIgnoringSafeArea([.bottom]) .smsBottomSheet( @@ -343,7 +344,6 @@ struct MyPageView: View { } } .animation(.default, value: state.isPresentedImageMethodPicker) - .hideKeyboardWhenTap() .navigationTitle("마이페이지") .smsBackButton( dismiss: dismiss From d991ac8d95e2f1b679ffd93aa7a77f03b1f6c54c Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:15:49 +0900 Subject: [PATCH 61/62] =?UTF-8?q?:recycle:=20[#220]=20MyPageFeature=20/=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MyPageView.swift | 241 +++++++++--------- 1 file changed, 123 insertions(+), 118 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 3f857600..2634a5e6 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -27,91 +27,7 @@ struct MyPageView: View { Spacer() .frame(height: 1) - ScrollView { - LazyVStack(pinnedViews: [.sectionHeaders]) { - Group { - SMSSeparator() - .padding(.vertical, 16) - - MyPageProfileView(intent: intent, state: state, geometry: geometry) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageSchoolLifeView(intent: intent, state: state) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageWorkInfoView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageMilitaryView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - } - - Group { - SMSSeparator() - .padding(.vertical, 16) - - MyPageCertificateView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageLanguageView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ), - geometry: geometry - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPageProjectView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ), - geometry: geometry - ) - - SMSSeparator() - .padding(.vertical, 16) - - MyPagePrizeView( - container: .init( - intent: intent, - model: state, - modelChangePublisher: container.objectWillChange - ) - ) - } - } - } + myPageView(geometry: geometry) CTAButton(text: "저장") { intent.modifyToInputAllInfo(state: state) @@ -243,19 +159,6 @@ struct MyPageView: View { ) { date in intent.prizePrizeAtDidSelect(index: state.focusedPrizeIndex, prizeAt: date) } - .imagePicker( - isShow: Binding( - get: { state.isPresentedPreviewImagePicker }, - set: { _ in intent.projectPreviewImagePickerDismissed() } - ), - pickedImageResult: Binding( - get: { .none }, - set: { - guard let image = $0 else { return } - intent.appendPreviewImage(index: state.focusedProjectIndex, image: image) - } - ) - ) .datePicker( isShowing: Binding( get: { state.isPresentedProjectStartAtDatePicker }, @@ -301,26 +204,6 @@ struct MyPageView: View { intent.imageMethodPickerDismissed() } } - .imagePicker( - isShow: Binding( - get: { state.isPresentedProfileImage }, - set: { _ in intent.imagePickerDismissed() } - ), - pickedImageResult: Binding( - get: { .none }, - set: { intent.imageDidSelected(imageResult: $0) } - ) - ) - .cameraPicker( - isShow: Binding( - get: { state.isPresentedProfileCamera }, - set: { _ in intent.cameraDismissed() } - ), - pickedImageResult: Binding( - get: { .none }, - set: { intent.imageDidSelected(imageResult: $0) } - ) - ) .smsBottomSheet( isShowing: Binding( get: { state.isPresentedMajorSheet }, @@ -430,4 +313,126 @@ struct MyPageView: View { } } } + + @ViewBuilder + func myPageView(geometry: GeometryProxy) -> some View { + ScrollView { + LazyVStack(pinnedViews: [.sectionHeaders]) { + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageProfileView(intent: intent, state: state, geometry: geometry) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageSchoolLifeView(intent: intent, state: state) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageWorkInfoView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageMilitaryView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } + + Group { + SMSSeparator() + .padding(.vertical, 16) + + MyPageCertificateView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageLanguageView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPageProjectView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ), + geometry: geometry + ) + + SMSSeparator() + .padding(.vertical, 16) + + MyPagePrizeView( + container: .init( + intent: intent, + model: state, + modelChangePublisher: container.objectWillChange + ) + ) + } + } + } + .imagePicker( + isShow: Binding( + get: { state.isPresentedProfileImage }, + set: { _ in intent.imagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { intent.imageDidSelected(imageResult: $0) } + ) + ) + .cameraPicker( + isShow: Binding( + get: { state.isPresentedProfileCamera }, + set: { _ in intent.cameraDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { intent.imageDidSelected(imageResult: $0) } + ) + ) + .imagePicker( + isShow: Binding( + get: { state.isPresentedPreviewImagePicker }, + set: { _ in intent.projectPreviewImagePickerDismissed() } + ), + pickedImageResult: Binding( + get: { .none }, + set: { + guard let image = $0 else { return } + intent.appendPreviewImage(index: state.focusedProjectIndex, image: image) + } + ) + ) + } } From ae3275b8564280dea65bdb540deb23898038e453 Mon Sep 17 00:00:00 2001 From: Sunghun Kim <81547954+kimsh153@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:35:42 +0900 Subject: [PATCH 62/62] =?UTF-8?q?:recycle:=20[#220]=20MyPageFeature=20/=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MyPageView.swift | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift index 2634a5e6..542de139 100644 --- a/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift +++ b/Projects/Feature/MyPageFeature/Sources/Scene/MyPageView.swift @@ -175,21 +175,6 @@ struct MyPageView: View { ) { date in intent.projectEndAtDidSelect(index: state.focusedProjectIndex, endAt: date) } - .fullScreenCover( - isPresented: Binding( - get: { state.isPresentedProjectTechStackAppend }, - set: { _ in intent.projectTechStackAppendDismissed() } - ) - ) { - DeferView { - techStackAppendBuildable.makeView( - initial: Array(state.projectList[safe: state.focusedProjectIndex]?.techStacks ?? []) - ) { - intent.techStacksDidSelect(index: state.focusedProjectIndex, techStacks: $0) - } - .eraseToAnyView() - } - } .smsBottomSheet( isShowing: Binding( get: { state.isPresentedImageMethodPicker }, @@ -213,19 +198,6 @@ struct MyPageView: View { ) { majorListView() } - .fullScreenCover( - isPresented: Binding( - get: { state.isPresentedTechStackAppend }, - set: { _ in intent.techStackAppendDismissed() } - ) - ) { - DeferView { - techStackAppendBuildable.makeView(initial: state.techStacks) { techStacks in - intent.techStackAppendDidComplete(techStacks: techStacks) - } - .eraseToAnyView() - } - } .animation(.default, value: state.isPresentedImageMethodPicker) .navigationTitle("마이페이지") .smsBackButton( @@ -434,5 +406,33 @@ struct MyPageView: View { } ) ) + .fullScreenCover( + isPresented: Binding( + get: { state.isPresentedTechStackAppend }, + set: { _ in intent.techStackAppendDismissed() } + ) + ) { + DeferView { + techStackAppendBuildable.makeView(initial: state.techStacks) { techStacks in + intent.techStackAppendDidComplete(techStacks: techStacks) + } + .eraseToAnyView() + } + } + .fullScreenCover( + isPresented: Binding( + get: { state.isPresentedProjectTechStackAppend }, + set: { _ in intent.projectTechStackAppendDismissed() } + ) + ) { + DeferView { + techStackAppendBuildable.makeView( + initial: Array(state.projectList[safe: state.focusedProjectIndex]?.techStacks ?? []) + ) { + intent.techStacksDidSelect(index: state.focusedProjectIndex, techStacks: $0) + } + .eraseToAnyView() + } + } } }