diff --git a/Projects/App/Sources/Application/DI/AppComponent.swift b/Projects/App/Sources/Application/DI/AppComponent.swift index 979eb0de..cd3ec4d4 100644 --- a/Projects/App/Sources/Application/DI/AppComponent.swift +++ b/Projects/App/Sources/Application/DI/AppComponent.swift @@ -82,8 +82,8 @@ public extension AppComponent { var applyComponent: ApplyComponent { ApplyComponent(parent: self) } - var noticeComponent: NoticeComponent { - NoticeComponent(parent: self) + var noticeListComponent: NoticeListComponent { + NoticeListComponent(parent: self) } var myPageComponent: MyPageComponent { MyPageComponent(parent: self) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 5784403f..150b0c1d 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -166,6 +166,15 @@ private class MainTabDependency2826cdb310ed0b17a725Provider: MainTabDependency { var homeComponent: HomeComponent { return appComponent.homeComponent } + var applyComponent: ApplyComponent { + return appComponent.applyComponent + } + var noticeListComponent: NoticeListComponent { + return appComponent.noticeListComponent + } + var myPageComponent: MyPageComponent { + return appComponent.myPageComponent + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -296,16 +305,18 @@ private class EnterInformationDependency9204f24c784151f429ddProvider: EnterInfor private func factory359a960501e79e833f64f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return EnterInformationDependency9204f24c784151f429ddProvider(appComponent: parent1(component) as! AppComponent) } -private class NoticeDependencyaec92ef53617a421bdf3Provider: NoticeDependency { - - - init() { - +private class NoticeListDependency0e93eb53be8626c408e4Provider: NoticeListDependency { + var fetchNoticeListUseCase: any FetchNoticeListUseCase { + return appComponent.fetchNoticeListUseCase + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent } } -/// ^->AppComponent->NoticeComponent -private func factoryaf8e5665e5b9217918f5e3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { - return NoticeDependencyaec92ef53617a421bdf3Provider() +/// ^->AppComponent->NoticeListComponent +private func factorye14e687c08985bdffcd0f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return NoticeListDependency0e93eb53be8626c408e4Provider(appComponent: parent1(component) as! AppComponent) } private class FindIDDependencyb481fe947a844cc29913Provider: FindIDDependency { var findIDUseCase: any FindIDUseCase { @@ -353,7 +364,7 @@ extension AppComponent: Registration { localTable["mainTabComponent-MainTabComponent"] = { self.mainTabComponent as Any } localTable["homeComponent-HomeComponent"] = { self.homeComponent as Any } localTable["applyComponent-ApplyComponent"] = { self.applyComponent as Any } - localTable["noticeComponent-NoticeComponent"] = { self.noticeComponent as Any } + localTable["noticeListComponent-NoticeListComponent"] = { self.noticeListComponent as Any } localTable["myPageComponent-MyPageComponent"] = { self.myPageComponent as Any } localTable["remoteNoticeDataSource-any RemoteNoticeDataSource"] = { self.remoteNoticeDataSource as Any } localTable["noticeRepository-any NoticeRepository"] = { self.noticeRepository as Any } @@ -435,6 +446,9 @@ extension SignupProfileImageComponent: Registration { extension MainTabComponent: Registration { public func registerItems() { keyPathToName[\MainTabDependency.homeComponent] = "homeComponent-HomeComponent" + keyPathToName[\MainTabDependency.applyComponent] = "applyComponent-ApplyComponent" + keyPathToName[\MainTabDependency.noticeListComponent] = "noticeListComponent-NoticeListComponent" + keyPathToName[\MainTabDependency.myPageComponent] = "myPageComponent-MyPageComponent" } } extension MyPageComponent: Registration { @@ -484,9 +498,9 @@ extension EnterInformationComponent: Registration { keyPathToName[\EnterInformationDependency.authenticationEmailComponent] = "authenticationEmailComponent-AuthenticationEmailComponent" } } -extension NoticeComponent: Registration { +extension NoticeListComponent: Registration { public func registerItems() { - + keyPathToName[\NoticeListDependency.fetchNoticeListUseCase] = "fetchNoticeListUseCase-any FetchNoticeListUseCase" } } extension FindIDComponent: Registration { @@ -529,7 +543,7 @@ private func register1() { registerProviderFactory("^->AppComponent->AuthenticationEmailComponent", factory8798d0becd9d2870112af47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->ChangePasswordComponent", factoryab7c4d87dab53e0a51b9f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->EnterInformationComponent", factory359a960501e79e833f64f47b58f8f304c97af4d5) - registerProviderFactory("^->AppComponent->NoticeComponent", factoryaf8e5665e5b9217918f5e3b0c44298fc1c149afb) + registerProviderFactory("^->AppComponent->NoticeListComponent", factorye14e687c08985bdffcd0f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->FindIDComponent", factory8dd2f9e0b545ead35ecaf47b58f8f304c97af4d5) } #endif diff --git a/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift b/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift index f82a074e..c3e5b3da 100644 --- a/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift +++ b/Projects/Features/MainTabFeature/Sources/MainTabComponent.swift @@ -1,15 +1,24 @@ import NeedleFoundation import SwiftUI import HomeFeature +import ApplyFeature +import NoticeFeature +import MyPageFeature public protocol MainTabDependency: Dependency { var homeComponent: HomeComponent { get } + var applyComponent: ApplyComponent { get } + var noticeListComponent: NoticeListComponent { get } + var myPageComponent: MyPageComponent { get } } public final class MainTabComponent: Component { public func makeView() -> some View { MainTabView( - homeComponent: dependency.homeComponent + homeComponent: dependency.homeComponent, + applyComponent: dependency.applyComponent, + noticeComponent: dependency.noticeListComponent, + myPageComponent: dependency.myPageComponent ) } } diff --git a/Projects/Features/MainTabFeature/Sources/MainTabView.swift b/Projects/Features/MainTabFeature/Sources/MainTabView.swift index f801ecd5..55d74b8f 100644 --- a/Projects/Features/MainTabFeature/Sources/MainTabView.swift +++ b/Projects/Features/MainTabFeature/Sources/MainTabView.swift @@ -1,7 +1,10 @@ import SwiftUI -import DesignSystem import HomeFeature +import ApplyFeature +import NoticeFeature +import MyPageFeature import BaseFeature +import DesignSystem import Utility enum TabFlow: Int { @@ -15,9 +18,20 @@ struct MainTabView: View { @State var tabbarHidden = false private let homeComponent: HomeComponent + private let applyComponent: ApplyComponent + private let noticeComponent: NoticeListComponent + private let myPageComponent: MyPageComponent - init(homeComponent: HomeComponent) { + init( + homeComponent: HomeComponent, + applyComponent: ApplyComponent, + noticeComponent: NoticeListComponent, + myPageComponent: MyPageComponent + ) { self.homeComponent = homeComponent + self.applyComponent = applyComponent + self.noticeComponent = noticeComponent + self.myPageComponent = myPageComponent } var body: some View { @@ -26,13 +40,13 @@ struct MainTabView: View { homeComponent.makeView() .tag(TabFlow.home) - Text("1") + applyComponent.makeView() .tag(TabFlow.apply) - Text("2") + noticeComponent.makeView() .tag(TabFlow.notice) - Text("3") + myPageComponent.makeView() .tag(TabFlow.myPage) } .environment(\.tabbarHidden, $tabbarHidden) diff --git a/Projects/Features/NoticeFeature/Sources/NoticeComponent.swift b/Projects/Features/NoticeFeature/Sources/NoticeComponent.swift deleted file mode 100644 index 1697aac4..00000000 --- a/Projects/Features/NoticeFeature/Sources/NoticeComponent.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI -import NeedleFoundation - -public protocol NoticeDependency: Dependency {} - -public final class NoticeComponent: Component { - public func makeView() -> some View { - Text("Text") - } -} diff --git a/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListComponent.swift b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListComponent.swift new file mode 100644 index 00000000..e1ce6342 --- /dev/null +++ b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListComponent.swift @@ -0,0 +1,17 @@ +import DomainModule +import NeedleFoundation +import SwiftUI + +public protocol NoticeListDependency: Dependency { + var fetchNoticeListUseCase: any FetchNoticeListUseCase { get } +} + +public final class NoticeListComponent: Component { + public func makeView() -> some View { + NoticeListView( + viewModel: .init( + fetchNoticeListUseCase: dependency.fetchNoticeListUseCase + ) + ) + } +} diff --git a/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift new file mode 100644 index 00000000..99c9009e --- /dev/null +++ b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListView.swift @@ -0,0 +1,73 @@ +import DesignSystem +import Utility +import SwiftUI + +struct NoticeListView: View { + @StateObject var viewModel: NoticeListViewModel + + init( + viewModel: NoticeListViewModel + ) { + _viewModel = StateObject(wrappedValue: viewModel) + } + + var body: some View { + NavigationView { + VStack { + HStack { + Spacer() + + NoticeOrderButton(text: viewModel.noticeOrderType.display, color: .GrayScale.gray6) { + viewModel.orderTypeButtonDidTap() + } + .padding(.horizontal, 24) + } + .padding(.top, 12) + + ScrollView { + VStack { + Spacer() + .frame(height: 10) + + ForEach(viewModel.noticeList, id: \.self) { noticeList in + noticeListCellView( + title: noticeList.title, + content: noticeList.createdAt.toSmallDMSDateString() + ) + .padding(.top, 5) + .listRowInsets(EdgeInsets()) + + } + } + .padding(.horizontal, 24) + + } + } + .navigationTitle("공지") + .navigationBarTitleDisplayMode(.inline) + } + } + + @ViewBuilder + func noticeListCellView(title: String, content: String) -> some View { + ZStack { + Color.System.surface + .cornerRadius(6) + + HStack { + VStack(alignment: .leading) { + Text(title) + .dmsFont(.text(.medium), color: .System.title) + + Text(content) + .dmsFont(.text(.extraSmall), color: .System.text) + } + Spacer() + } + .padding(.horizontal, 16) + } + .frame(height: 68) + .shadow(color: .GrayScale.gray5.opacity(0.15), blur: 20) + + } +} diff --git a/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListViewModel.swift b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListViewModel.swift new file mode 100644 index 00000000..1475fd9e --- /dev/null +++ b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeListViewModel.swift @@ -0,0 +1,36 @@ +import BaseFeature +import Foundation +import DomainModule +import ErrorModule +import Combine +import DataMappingModule + +final class NoticeListViewModel: BaseViewModel { + @Published var noticeList: [NoticeEntity] = [] + @Published var noticeOrderType: NoticeOrderType = .new + + private let fetchNoticeListUseCase: any FetchNoticeListUseCase + + init( + fetchNoticeListUseCase: any FetchNoticeListUseCase + ) { + self.fetchNoticeListUseCase = fetchNoticeListUseCase + super.init() + fetchNoticeList() + } + + func fetchNoticeList() { + addCancellable( + fetchNoticeListUseCase.execute( + order: noticeOrderType + ) + ) { [weak self] noticeList in + self?.noticeList = noticeList + } + } + + func orderTypeButtonDidTap() { + self.noticeOrderType = noticeOrderType == .new ? .old : .new + fetchNoticeList() + } +} diff --git a/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeOrderButton.swift b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeOrderButton.swift new file mode 100644 index 00000000..20b8e28c --- /dev/null +++ b/Projects/Features/NoticeFeature/Sources/NoticeList/NoticeOrderButton.swift @@ -0,0 +1,58 @@ +import SwiftUI +import DesignSystem + +public struct NoticeOrderButton: View { + var text: String + var color: Color + var action: () -> Void + + public init( + text: String = "", + color: Color = .blue, + action: @escaping () -> Void = {} + ) { + self.text = text + self.color = color + self.action = action + } + + public var body: some View { + Button(action: action) { + Text(text) + } + .buttonStyle(NoticeOrderButtonStyle()) + } +} + +public struct NoticeOrderButtonStyle: ButtonStyle { + public func makeBody(configuration: Configuration) -> some View { + return AnyView(OutlinedButton(configuration: configuration, color: .GrayScale.gray6)) + } +} + +// MARK: - Outlined +extension NoticeOrderButtonStyle { + struct OutlinedButton: View { + let configuration: ButtonStyle.Configuration + let color: Color + @Environment(\.isEnabled) private var isEnabled: Bool + + var body: some View { + configuration.label + .padding(.vertical, 8.5) + .padding(.horizontal, 16) + .dmsFont(.button(.default)) + .background(.clear) + .foregroundColor(color) + .overlay { + RoundedRectangle(cornerRadius: 5) + .stroke(color, lineWidth: 1) + } + .opacity( + isEnabled ? + configuration.isPressed ? 0.7 : 1.0 : + 0.5 + ) + } + } +} diff --git a/Projects/Features/NoticeFeature/Sources/NoticeView.swift b/Projects/Features/NoticeFeature/Sources/NoticeView.swift deleted file mode 100644 index 8bea0f10..00000000 --- a/Projects/Features/NoticeFeature/Sources/NoticeView.swift +++ /dev/null @@ -1,16 +0,0 @@ -import DesignSystem -import SwiftUI - -struct NoticeView: View { - @StateObject var viewModel: NoticeViewModel - - init( - viewModel: NoticeViewModel - ) { - _viewModel = StateObject(wrappedValue: viewModel) - } - - var body: some View { - Text("Text") - } -} diff --git a/Projects/Features/NoticeFeature/Sources/NoticeViewModel.swift b/Projects/Features/NoticeFeature/Sources/NoticeViewModel.swift deleted file mode 100644 index f3a9cb01..00000000 --- a/Projects/Features/NoticeFeature/Sources/NoticeViewModel.swift +++ /dev/null @@ -1,5 +0,0 @@ -import BaseFeature -import Combine - -final class NoticeViewModel: BaseViewModel { -} diff --git a/Projects/Services/DataMappingModule/Sources/Base/NoticeOrderType.swift b/Projects/Services/DataMappingModule/Sources/Base/NoticeOrderType.swift index ee9854fe..0101b558 100644 --- a/Projects/Services/DataMappingModule/Sources/Base/NoticeOrderType.swift +++ b/Projects/Services/DataMappingModule/Sources/Base/NoticeOrderType.swift @@ -3,4 +3,13 @@ import Foundation public enum NoticeOrderType: String, Codable { case new = "NEW" case old = "OLD" + + public var display: String { + switch self { + case .new: + return "최신순" + case .old: + return "오래된순" + } + } } diff --git a/Projects/Services/DomainModule/Sources/Entities/NoticeEntity.swift b/Projects/Services/DomainModule/Sources/Entities/NoticeEntity.swift index eed0fccd..7d19f73b 100644 --- a/Projects/Services/DomainModule/Sources/Entities/NoticeEntity.swift +++ b/Projects/Services/DomainModule/Sources/Entities/NoticeEntity.swift @@ -1,6 +1,6 @@ import Foundation -public struct NoticeEntity: Equatable { +public struct NoticeEntity: Equatable, Hashable { public init(id: String, title: String, createdAt: Date) { self.id = id self.title = title