diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index aedf8f2d..1b47c7c4 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -185,15 +185,17 @@ private func factory1ab5a747ddf21e1393f9f47b58f8f304c97af4d5(_ component: Needle return MainTabDependency2826cdb310ed0b17a725Provider(appComponent: parent1(component) as! AppComponent) } private class MyPageDependency48d84b530313b3ee40feProvider: MyPageDependency { - - - init() { - + var fetchMyProfileUseCase: any FetchMyProfileUseCase { + return appComponent.fetchMyProfileUseCase + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent } } /// ^->AppComponent->MyPageComponent -private func factory0f6f456ebf157d02dfb3e3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { - return MyPageDependency48d84b530313b3ee40feProvider() +private func factory0f6f456ebf157d02dfb3f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return MyPageDependency48d84b530313b3ee40feProvider(appComponent: parent1(component) as! AppComponent) } private class RootDependency3944cc797a4a88956fb5Provider: RootDependency { var signinComponent: SigninComponent { @@ -470,7 +472,7 @@ extension MainTabComponent: Registration { } extension MyPageComponent: Registration { public func registerItems() { - + keyPathToName[\MyPageDependency.fetchMyProfileUseCase] = "fetchMyProfileUseCase-any FetchMyProfileUseCase" } } extension RootComponent: Registration { @@ -558,7 +560,7 @@ private func register1() { registerProviderFactory("^->AppComponent->SignupEmailVerifyComponent", factory3b1904c76335d70151ebf47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->SignupProfileImageComponent", factory6792674212c15df7e9cff47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->MainTabComponent", factory1ab5a747ddf21e1393f9f47b58f8f304c97af4d5) - registerProviderFactory("^->AppComponent->MyPageComponent", factory0f6f456ebf157d02dfb3e3b0c44298fc1c149afb) + registerProviderFactory("^->AppComponent->MyPageComponent", factory0f6f456ebf157d02dfb3f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->RootComponent", factory264bfc4d4cb6b0629b40f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->SigninComponent", factory2882a056d84a613debccf47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->HomeComponent", factory67229cdf0f755562b2b1f47b58f8f304c97af4d5) diff --git a/Projects/Features/HomeFeature/Sources/Component/NoticeView.swift b/Projects/Features/HomeFeature/Sources/Component/NoticeView.swift index 8c491c2b..97b85936 100644 --- a/Projects/Features/HomeFeature/Sources/Component/NoticeView.swift +++ b/Projects/Features/HomeFeature/Sources/Component/NoticeView.swift @@ -31,10 +31,6 @@ struct NoticeView: View { .cornerRadius(100) .padding(.horizontal, 24) .padding(.top, 12) - .shadow( - color: .GrayScale.gray4.opacity(0.24), - y: 1, - blur: 20 - ) + .dmsShadow() } } diff --git a/Projects/Features/MyPageFeature/Sources/MyPageComponent.swift b/Projects/Features/MyPageFeature/Sources/MyPageComponent.swift index 263ab35a..2c85229c 100644 --- a/Projects/Features/MyPageFeature/Sources/MyPageComponent.swift +++ b/Projects/Features/MyPageFeature/Sources/MyPageComponent.swift @@ -1,10 +1,19 @@ +import DomainModule import SwiftUI import NeedleFoundation -public protocol MyPageDependency: Dependency {} +public protocol MyPageDependency: Dependency { + var fetchMyProfileUseCase: any FetchMyProfileUseCase { get } +} public final class MyPageComponent: Component { public func makeView() -> some View { - Text("Text") + NavigationView { + MyPageView( + viewModel: .init( + fetchMyProfileUseCase: self.dependency.fetchMyProfileUseCase + ) + ) + } } } diff --git a/Projects/Features/MyPageFeature/Sources/MyPageView.swift b/Projects/Features/MyPageFeature/Sources/MyPageView.swift index 2d1445e6..e6a4dfae 100644 --- a/Projects/Features/MyPageFeature/Sources/MyPageView.swift +++ b/Projects/Features/MyPageFeature/Sources/MyPageView.swift @@ -11,6 +11,151 @@ struct MyPageView: View { } var body: some View { - Text("Text") + ScrollView(showsIndicators: false) { + VStack { + HStack { + VStack(alignment: .leading, spacing: 8) { + Text("\(viewModel.profile?.gcn ?? "") \(viewModel.profile?.name ?? "Loading...")") + .dmsFont(.title(.small), color: .GrayScale.gray7) + + Text(viewModel.profile?.schoolName ?? "") + .dmsFont(.text(.small), color: .GrayScale.gray6) + } + + Spacer() + + ZStack(alignment: .bottomTrailing) { + AsyncImage(url: URL(string: "")) { image in + image + .resizable() + .frame(width: 74, height: 74) + .clipShape(Circle()) + } placeholder: { + Color.GrayScale.gray5 + .frame(width: 74, height: 74) + .clipShape(Circle()) + } + + Circle() + .fill(Color.GrayScale.gray3) + .frame(width: 24, height: 24) + .overlay { + DMSImage(.pencil) + .frame(width: 16, height: 16) + } + } + } + .padding(.top, 48) + + Text(viewModel.profile?.phrase ?? "") + .dmsFont(.text(.small), color: .GrayScale.gray7) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .padding(.horizontal, 20) + .background { + Color.PrimaryVariant.lighten2 + .cornerRadius(5) + } + .padding(.top, 60) + + HStack(spacing: 12) { + rewardPointCardView(title: "상점", point: viewModel.profile?.bonusPoint ?? 0) + + rewardPointCardView(title: "벌점", point: viewModel.profile?.minusPoint ?? 0) + } + + VStack(alignment: .leading, spacing: 0) { + myPageOptionRowCardView(title: "상벌점 내역 확인") + .dmsFont(.text(.medium), color: .GrayScale.gray6) + .cornerRadius(10, corners: [.topLeft, .topRight]) + + Divider() + .padding(.horizontal, 10) + + myPageOptionRowCardView(title: "비밀번호 변경") + .dmsFont(.text(.medium), color: .GrayScale.gray6) + .cornerRadius(10, corners: [.bottomLeft, .bottomRight]) + } + .background { + RoundedRectangle(cornerRadius: 10) + .fill(Color.GrayScale.gray1) + .dmsShadow() + } + + VStack(alignment: .leading, spacing: 0) { + myPageOptionRowCardView(title: "로그아웃", action: viewModel.logoutButtonDidTap) + .dmsFont(.text(.medium), color: .System.error) + .onTapGesture(perform: viewModel.logoutButtonDidTap) + .cornerRadius(10) + } + .frame(maxWidth: .infinity) + .background { + RoundedRectangle(cornerRadius: 10) + .fill(Color.GrayScale.gray1) + .dmsShadow() + } + + Spacer() + } + .padding(.horizontal, 24) + .dmsBackground() + .navigationTitle("마이페이지") + .navigationBarTitleDisplayMode(.inline) + } + .onAppear(perform: viewModel.onAppear) + .alert("", isPresented: $viewModel.isPresentedLogoutAlert) { + Button("취소", role: .cancel) {} + Button("로그아웃", role: .destructive, action: viewModel.confirmLogoutButtonDidTap) + } message: { + Text("정말 로그아웃 하시겠습니까?") + .dmsFont(.text(.medium), color: .GrayScale.gray6) + } + } + + @ViewBuilder + func myPageOptionRowCardView(title: String, action: @escaping () -> Void = {}) -> some View { + Button(action: action) { + HStack { + Text(title) + + Spacer() + } + .padding(.vertical, 15) + .padding(.horizontal, 20) + .background { + Color.GrayScale.gray1 + .dmsShadow() + } + } + } + + @ViewBuilder + func rewardPointCardView(title: String, point: Int) -> some View { + VStack { + HStack { + Text(title) + .dmsFont(.text(.small), color: .GrayScale.gray6) + + Spacer() + } + .padding(.top, 16) + + HStack { + Spacer() + + Text("\(point)") + .dmsFont(.title(.large), color: .GrayScale.gray6) + } + .padding(.bottom, 16) + } + .padding(.horizontal, 20) + .frame(maxWidth: .infinity) + .frame(height: 95) + .background { + RoundedRectangle(cornerRadius: 10) + .stroke(Color.PrimaryVariant.primary, lineWidth: 1) + .dmsShadow() + } } } diff --git a/Projects/Features/MyPageFeature/Sources/MyPageViewModel.swift b/Projects/Features/MyPageFeature/Sources/MyPageViewModel.swift index b5639845..7cbdb8e6 100644 --- a/Projects/Features/MyPageFeature/Sources/MyPageViewModel.swift +++ b/Projects/Features/MyPageFeature/Sources/MyPageViewModel.swift @@ -1,5 +1,30 @@ import BaseFeature import Combine +import DomainModule final class MyPageViewModel: BaseViewModel { + @Published var profile: MyProfileEntity? + @Published var isPresentedLogoutAlert = false + + private let fetchMyProfileUseCase: any FetchMyProfileUseCase + + public init( + fetchMyProfileUseCase: any FetchMyProfileUseCase + ) { + self.fetchMyProfileUseCase = fetchMyProfileUseCase + } + + func onAppear() { + addCancellable( + fetchMyProfileUseCase.execute() + ) { [weak self] profile in + self?.profile = profile + } + } + + func logoutButtonDidTap() { + isPresentedLogoutAlert = true + } + + func confirmLogoutButtonDidTap() {} } diff --git a/Projects/UsertInterfaces/DesignSystem/Derived/Sources/TuistAssets+DesignSystem.swift b/Projects/UsertInterfaces/DesignSystem/Derived/Sources/TuistAssets+DesignSystem.swift index f6f2d74b..fc35a288 100644 --- a/Projects/UsertInterfaces/DesignSystem/Derived/Sources/TuistAssets+DesignSystem.swift +++ b/Projects/UsertInterfaces/DesignSystem/Derived/Sources/TuistAssets+DesignSystem.swift @@ -17,6 +17,9 @@ // swiftlint:disable identifier_name line_length nesting type_body_length type_name public enum DesignSystemAsset { + public enum Icons { + public static let pencil = DesignSystemImages(name: "pencil") + } public enum PrimaryColor { public static let darken1 = DesignSystemColors(name: "Darken-1") public static let darken2 = DesignSystemColors(name: "Darken-2") @@ -87,5 +90,45 @@ public extension DesignSystemColors.Color { } } +public struct DesignSystemImages { + public fileprivate(set) var name: String + + #if os(macOS) + public typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + public typealias Image = UIImage + #endif + + public var image: Image { + let bundle = DesignSystemResources.bundle + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let image = bundle.image(forResource: NSImage.Name(name)) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } +} + +public extension DesignSystemImages.Image { + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the DesignSystemImages.image property") + convenience init?(asset: DesignSystemImages) { + #if os(iOS) || os(tvOS) + let bundle = DesignSystemResources.bundle + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + // swiftlint:enable all // swiftformat:enable all diff --git a/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/Contents.json b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/Contents.json b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/Contents.json new file mode 100644 index 00000000..53f2672c --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "pencil.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pencil 1.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "pencil 2.svg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil 1.svg b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil 1.svg new file mode 100644 index 00000000..1ba4ee7b --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil 1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil 2.svg b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil 2.svg new file mode 100644 index 00000000..1ba4ee7b --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil 2.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil.svg b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil.svg new file mode 100644 index 00000000..1ba4ee7b --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Resources/Icons.xcassets/pencil.imageset/pencil.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Projects/UsertInterfaces/DesignSystem/Sources/Image/DMSImage.swift b/Projects/UsertInterfaces/DesignSystem/Sources/Image/DMSImage.swift new file mode 100644 index 00000000..748ebef5 --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Sources/Image/DMSImage.swift @@ -0,0 +1,38 @@ +import SwiftUI + +fileprivate extension DesignSystemImages { + var suiImage: SwiftUI.Image { + SwiftUI.Image(uiImage: self.image) + } +} + +public struct DMSImage: View { + public enum Image { + case pencil + } + + private var image: Image + private var renderingMode: SwiftUI.Image.TemplateRenderingMode + + public init( + _ image: Image, + renderingMode: SwiftUI.Image.TemplateRenderingMode = .original + ) { + self.image = image + self.renderingMode = renderingMode + } + + public var body: some View { + dmsImageToImage() + .resizable() + .renderingMode(renderingMode) + } + + @ViewBuilder + private func dmsImageToImage() -> SwiftUI.Image { + switch image { + case .pencil: + DesignSystemAsset.Icons.pencil.suiImage + } + } +} diff --git a/Projects/UsertInterfaces/DesignSystem/Sources/Shadow/DMSShadow.swift b/Projects/UsertInterfaces/DesignSystem/Sources/Shadow/DMSShadow.swift new file mode 100644 index 00000000..c60d8e49 --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Sources/Shadow/DMSShadow.swift @@ -0,0 +1,33 @@ +import SwiftUI + +public struct DMSShadow: ViewModifier { + public var style: DMSShadowStyle + + public func body(content: Content) -> some View { + switch style { + case .surface: + content + .shadow( + color: .GrayScale.gray3.opacity(0.8), + y: 1, + blur: 20 + ) + + case .button: + content + .shadow( + color: .GrayScale.gray9.opacity(0.08), + y: 2, + blur: 16 + ) + } + } +} + +public extension View { + func dmsShadow( + style: DMSShadowStyle = .surface + ) -> some View { + modifier(DMSShadow(style: style)) + } +} diff --git a/Projects/UsertInterfaces/DesignSystem/Sources/Shadow/DMSShadowStyle.swift b/Projects/UsertInterfaces/DesignSystem/Sources/Shadow/DMSShadowStyle.swift new file mode 100644 index 00000000..2a3212c9 --- /dev/null +++ b/Projects/UsertInterfaces/DesignSystem/Sources/Shadow/DMSShadowStyle.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum DMSShadowStyle { + case surface + case button +} diff --git a/graph.png b/graph.png index 55e1f23d..6f0a2d54 100644 Binary files a/graph.png and b/graph.png differ