From 2b67d06cd13ca7ed158d69a6e4a1d1e01d40638c Mon Sep 17 00:00:00 2001 From: kangddong Date: Wed, 14 Jan 2026 23:38:26 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20API=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Data/DTO/BoardResponse.swift | 6 ++-- .../Sources/Data/DTO/CommentResponse.swift | 2 +- Community/Sources/Data/DTO/Querys.swift | 9 +----- Community/Sources/Domain/Entities/Board.swift | 4 +-- .../Community/CommunityView.swift | 30 +++---------------- .../Request/CursorPagingQuery.swift | 8 ++--- 6 files changed, 15 insertions(+), 44 deletions(-) diff --git a/Community/Sources/Data/DTO/BoardResponse.swift b/Community/Sources/Data/DTO/BoardResponse.swift index a18c290..b8577d6 100644 --- a/Community/Sources/Data/DTO/BoardResponse.swift +++ b/Community/Sources/Data/DTO/BoardResponse.swift @@ -16,8 +16,8 @@ public struct BoardResponseDTO: Decodable, Sendable { public let content: String public let category: String public let imageUrls: [String] - public let authorNickname: String? - public let authorId: Int? + public let authorNickname: String + public let authorId: Int public let createdAt: String public let updatedAt: String public let viewCount: Int @@ -47,7 +47,7 @@ public struct BoardListDataDTO: Decodable, Sendable { private enum CodingKeys: String, CodingKey { case content - case nextCursorId = "netCursorId" + case nextCursorId case nextPage } } diff --git a/Community/Sources/Data/DTO/CommentResponse.swift b/Community/Sources/Data/DTO/CommentResponse.swift index 73f2421..920ea45 100644 --- a/Community/Sources/Data/DTO/CommentResponse.swift +++ b/Community/Sources/Data/DTO/CommentResponse.swift @@ -38,7 +38,7 @@ public struct CommentListDataDTO: Decodable, Sendable { private enum CodingKeys: String, CodingKey { case content - case nextCursorId = "netCursorId" + case nextCursorId case nextPage } } diff --git a/Community/Sources/Data/DTO/Querys.swift b/Community/Sources/Data/DTO/Querys.swift index 5863f29..84d4de2 100644 --- a/Community/Sources/Data/DTO/Querys.swift +++ b/Community/Sources/Data/DTO/Querys.swift @@ -6,14 +6,7 @@ // import Foundation - - -/// 재사용 되는 페이지네이션 쿼리 -public struct CursorPagingQuery: Encodable, Sendable { - let lastId: Int? - let limit: Int - let order: String -} +import NetworkInterface /// CategoryPagingQuery public struct CategoryPagingQuery: Encodable, Sendable { diff --git a/Community/Sources/Domain/Entities/Board.swift b/Community/Sources/Domain/Entities/Board.swift index e53f3bb..9f7e17c 100644 --- a/Community/Sources/Domain/Entities/Board.swift +++ b/Community/Sources/Domain/Entities/Board.swift @@ -38,7 +38,7 @@ public struct Board: Identifiable, Equatable, Sendable { public let content: String public let category: BoardCategory public let imageUrls: [String] - public let authorNickname: String? + public let authorNickname: String public let authorProfileImageUrl: String? public let authorId: Int? public let createdAt: String @@ -54,7 +54,7 @@ public struct Board: Identifiable, Equatable, Sendable { content: String, category: BoardCategory, imageUrls: [String], - authorNickname: String?, + authorNickname: String, authorProfileImageUrl: String? = nil, authorId: Int?, createdAt: Date, diff --git a/Community/Sources/Presentation/Community/CommunityView.swift b/Community/Sources/Presentation/Community/CommunityView.swift index 900d888..e38f542 100644 --- a/Community/Sources/Presentation/Community/CommunityView.swift +++ b/Community/Sources/Presentation/Community/CommunityView.swift @@ -50,9 +50,9 @@ public struct CommunityView: View { VStack(spacing: 0) { // 헤더 - CommunityHeader() - .padding(.horizontal, 16) - .padding(.bottom, 20) + HeaderBar(type: .community) + .safeAreaPadding(.vertical, 18) + .safeAreaPadding(.horizontal, 15) VStack(spacing: 0) { // 카테고리 필터와 뷰 토글 @@ -122,33 +122,11 @@ public struct CommunityView: View { .refreshable { viewModel.refreshBoards() } - .navigationBarHidden(true) + .toolbar(.hidden, for: .navigationBar) .tabBarHidden(false) } } -// MARK: - Category Header -struct CommunityHeader: View { - var body: some View { - HStack { - Text("커뮤니티") - .pretendard(.title(.t2)) - .foregroundColor(.white) - - Spacer() - - Button(action: { - print("Notification tapped") - }) { - Image(.naviBell) - .resizable() - .foregroundColor(.white) - .frame(width: 20, height: 20) - } - } - } -} - // MARK: - Category Filter fileprivate struct CategoryFilterListView: View { let categories: [CommunityViewModel.Category] diff --git a/Infrastructure/Sources/NetworkInterface/Request/CursorPagingQuery.swift b/Infrastructure/Sources/NetworkInterface/Request/CursorPagingQuery.swift index 9ef1e58..ad57e9b 100644 --- a/Infrastructure/Sources/NetworkInterface/Request/CursorPagingQuery.swift +++ b/Infrastructure/Sources/NetworkInterface/Request/CursorPagingQuery.swift @@ -10,12 +10,12 @@ public struct CursorPagingQuery: Encodable, Sendable { public let lastId: Int? public let limit: Int - public let order: String + public let order: String? public init( - lastId: Int?, - limit: Int, - order: String + lastId: Int? = nil, + limit: Int = 20, + order: String? = nil ) { self.lastId = lastId self.limit = limit From 4ed82da1601c2c7de35059d492d6695124d78a4d Mon Sep 17 00:00:00 2001 From: kangddong Date: Sat, 17 Jan 2026 21:47:13 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(refactor):=20HeaderBar=EC=97=90=20?= =?UTF-8?q?=EC=A0=9C=EB=84=88=EB=A6=AD=20=ED=83=80=EC=9E=85=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 --- Common/Sources/DesignSystem/HeaderBar.swift | 38 +++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/Common/Sources/DesignSystem/HeaderBar.swift b/Common/Sources/DesignSystem/HeaderBar.swift index 29643ec..7bb1be0 100644 --- a/Common/Sources/DesignSystem/HeaderBar.swift +++ b/Common/Sources/DesignSystem/HeaderBar.swift @@ -22,15 +22,33 @@ public enum HeaderType { return "마이페이지" } } + + var hasNotification: Bool { + switch self { + case .home, .community: return true + case .myPage: return false + } + } } // 상단 헤더 - 로고, 알림 -public struct HeaderBar: View { +public struct HeaderBar: View { private let fontStyle: FontStyle private let type: HeaderType + private let destination: Destination? - public init(type: HeaderType) { + public init( + type: HeaderType, + @ViewBuilder destination: () -> Destination + ) where Destination: View { fontStyle = .init(.custom("GeekbleMalang2"), size: 24.0) self.type = type + self.destination = destination() + } + + public init(type: HeaderType) where Destination == EmptyView { + fontStyle = .init(.custom("GeekbleMalang2"), size: 24.0) + self.type = type + self.destination = nil } @ViewBuilder @@ -50,7 +68,6 @@ public struct HeaderBar: View { case .community, .myPage: Text(type.displayText) -// .padding(.bottom, 15) .pretendard(.title(.t2)) .foregroundColor(type == .community ? .white : Color.textG900) } @@ -58,11 +75,9 @@ public struct HeaderBar: View { @ViewBuilder var notificationSection: some View { - switch type { - case .home, .community: + if let destination { NavigationLink { - AlarmListView() - Text("asd") + destination } label: { Image("notification") .resizable() @@ -70,10 +85,9 @@ public struct HeaderBar: View { .frame(width: 30, height: 30) .foregroundStyle(type == .home ? .black : .white) } - case .myPage: - EmptyView() } } + public var body: some View { HStack { titleSection @@ -84,5 +98,9 @@ public struct HeaderBar: View { } #Preview { - HeaderBar(type: .home) + HeaderBar(type: .home) { + AnyView(Text("Destination")) + } + + HeaderBar(type: .myPage) } From e2506f1ea8e4801fa1e7b5a6a5581ef1ff3b516a Mon Sep 17 00:00:00 2001 From: kangddong Date: Sat, 17 Jan 2026 21:48:12 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Alarm/.gitignore | 8 ++ Alarm/Package.swift | 117 ++++++++++++++++++ Alarm/Sources/DI/AlarmDIContainer.swift | 61 +++++++++ Alarm/Sources/Data/AlarmAPI.swift | 56 +++++++++ Alarm/Sources/Data/AlarmRepositoryImpl.swift | 28 +++++ Alarm/Sources/Data/NotificationDTO.swift | 81 ++++++++++++ Alarm/Sources/Domain/AlarmEntities.swift | 56 +++++++++ Alarm/Sources/Domain/AlarmRepository.swift | 12 ++ .../Sources/Domain/GetAlarmListUseCase.swift | 24 ++++ .../Sources/Presentation/AlarmListView.swift | 110 ++++++++++++++++ .../Presentation/AlarmListViewModel.swift | 28 +++++ Community/Package.swift | 2 + Hambug.xcodeproj/project.pbxproj | 2 + Hambug/Info.plist | 2 + Home/Package.swift | 2 + 15 files changed, 589 insertions(+) create mode 100644 Alarm/.gitignore create mode 100644 Alarm/Package.swift create mode 100644 Alarm/Sources/DI/AlarmDIContainer.swift create mode 100644 Alarm/Sources/Data/AlarmAPI.swift create mode 100644 Alarm/Sources/Data/AlarmRepositoryImpl.swift create mode 100644 Alarm/Sources/Data/NotificationDTO.swift create mode 100644 Alarm/Sources/Domain/AlarmEntities.swift create mode 100644 Alarm/Sources/Domain/AlarmRepository.swift create mode 100644 Alarm/Sources/Domain/GetAlarmListUseCase.swift create mode 100644 Alarm/Sources/Presentation/AlarmListView.swift create mode 100644 Alarm/Sources/Presentation/AlarmListViewModel.swift diff --git a/Alarm/.gitignore b/Alarm/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Alarm/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Alarm/Package.swift b/Alarm/Package.swift new file mode 100644 index 0000000..53eea3b --- /dev/null +++ b/Alarm/Package.swift @@ -0,0 +1,117 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +enum Config: String, CaseIterable { + static let name: String = "Alarm" + + case di = "DI" + case data = "Data" + case domain = "Domain" + case presentation = "Presentation" + + var name: String { + Config.name + rawValue + } + + var path: String { + "Sources/\(rawValue)" + } +} + +let package = Package( + name: Config.name, + platforms: [.iOS(.v17)], + products: [ + .library( + name: Config.name, + targets: Config.allCases.map(\.name) + ), + .library( + name: Config.di.name, + targets: [ + Config.di.name, + Config.presentation.name + ] + ) + ], + dependencies: [ + .package(name: "DI", path: "../DI"), + .package(name: "Common", path: "../Common"), + .package(name: "Infrastructure", path: "../Infrastructure"), + ], + targets: [ + .target( + config: .di, + dependencies: [ + .target(config: .domain), + .target(config: .data), + .target(config: .presentation), + .product(name: "DI", package: "DI"), + .product(name: "AppDI", package: "DI"), + .product(name: "NetworkInterface", package: "Infrastructure"), + ], + ), + // Domain: 독립적 (외부 의존성 없음) + .target(config: .domain), + + // Data + .target( + config: .data, + dependencies: [ + .target(config: .domain), + .product(name: "NetworkInterface", package: "Infrastructure"), + .product(name: "Util", package: "Common"), + ] + ), + + // Presentation: Domain, DesignSystem, Layout에 의존 + .target( + config: .presentation, + dependencies: [ + .target(config: .domain), + .product(name: "DesignSystem", package: "Common"), + ], + ) + + ] +) + +extension Target { + static func target( + config: Config, + dependencies: [Dependency] = [], + exclude: [String] = [], + sources: [String]? = nil, + resources: [Resource]? = nil, + publicHeadersPath: String? = nil, + packageAccess: Bool = false, + cSettings: [CSetting]? = nil, + cxxSettings: [CXXSetting]? = nil, + swiftSettings: [SwiftSetting]? = nil, + linkerSettings: [LinkerSetting]? = nil, + plugins: [PluginUsage]? = nil, + ) -> Target { + return .target( + name: config.name, + dependencies: dependencies, + path: config.path, + exclude: exclude, + sources: sources, + resources: resources, + publicHeadersPath: publicHeadersPath, + packageAccess: packageAccess, + cSettings: cSettings, + cxxSettings: cxxSettings, + swiftSettings: swiftSettings, + linkerSettings: linkerSettings, + plugins: plugins) + } +} + +extension Target.Dependency { + static func target(config: Config) -> Self { + return .target(name: config.name) + } +} diff --git a/Alarm/Sources/DI/AlarmDIContainer.swift b/Alarm/Sources/DI/AlarmDIContainer.swift new file mode 100644 index 0000000..3c08f17 --- /dev/null +++ b/Alarm/Sources/DI/AlarmDIContainer.swift @@ -0,0 +1,61 @@ +// +// AlarmDIContainer.swift +// Alarm +// +// Created by 강동영 on 10/17/25. +// + +import Foundation +import DIKit +import AppDI +import NetworkInterface +import AlarmDomain +import AlarmData +import AlarmPresentation + +struct AlarmAssembly: Assembly { + + func assemble(container: GenericDIContainer) { + // Repository registration + container.register(AlarmRepository.self) { resolver in + AlarmRepositoryImpl( + networkService: resolver.resolve(NetworkServiceInterface.self) + ) + } + + container.register(GetAlarmListUseCase.self) { resolver in + GetAlarmListUseCaseImpl( + repository: resolver.resolve(AlarmRepository.self) + ) + } + + container.register(AlarmListViewModel.self) { resolver in + AlarmListViewModel( + usecase: resolver.resolve(GetAlarmListUseCase.self) + ) + } + } +} + +// MARK: - Alarm DI Container +public final class AlarmDIContainer { + + // MARK: - Properties + private let container: GenericDIContainer + + // MARK: - Initialization + public init(appContainer: GenericDIContainer) { + self.container = appContainer + AlarmAssembly().assemble(container: container) + } +} + +extension AlarmDIContainer: AlarmListDependecy { + public func makeAlarmListViewModel() -> AlarmListViewModel { + return container.resolve(AlarmListViewModel.self) + } +} +//#Preview { +// let diContainer = CommunityDIContainer() +// CommunityView(viewModel: diContainer.makeCommunityViewModel(), diContainer: diContainer) +//} diff --git a/Alarm/Sources/Data/AlarmAPI.swift b/Alarm/Sources/Data/AlarmAPI.swift new file mode 100644 index 0000000..484df66 --- /dev/null +++ b/Alarm/Sources/Data/AlarmAPI.swift @@ -0,0 +1,56 @@ +// +// AlarmEndpoint.swift +// Hambug +// +// Created by 강동영 on 10/17/25. +// + +import Foundation +import NetworkInterface + +struct AlarmEndpoint: Endpoint { + var baseURL: String = NetworkConfig.baseURL + "/api/v1" + var path: String = "/notifications" + var method: NetworkInterface.HTTPMethod = .GET + var headers: [String : String] = [:] + var queryParameters: [String : Any] = [:] + var body: Data? = nil +} +public enum BoardEndpoint: Endpoint { + case fetchNotificaitons(CursorPagingQuery) + + public var baseURL: String { + return NetworkConfig.baseURL + "/api/v1" + } + + public var path: String { + switch self { + case .fetchNotificaitons: + return "/notifications" + } + } + + public var method: HTTPMethod { + switch self { + case .fetchNotificaitons: + return .GET + } + } + + public var headers: [String: String] { + return [:] + } + + public var queryParameters: [String: Any] { + switch self { + case let .fetchNotificaitons(dto): + return queryEncoder.encode(dto) + default: + return [:] + } + } + + public var body: Data? { + return nil + } +} diff --git a/Alarm/Sources/Data/AlarmRepositoryImpl.swift b/Alarm/Sources/Data/AlarmRepositoryImpl.swift new file mode 100644 index 0000000..18e9cfb --- /dev/null +++ b/Alarm/Sources/Data/AlarmRepositoryImpl.swift @@ -0,0 +1,28 @@ +// +// AlarmRepositoryImpl.swift +// Alarm +// +// Created by 강동영 on 1/17/26. +// + +import AlarmDomain +import NetworkInterface +import Util + +public final class AlarmRepositoryImpl: AlarmRepository { + private let networkService: NetworkServiceInterface + + public init(networkService: NetworkServiceInterface) { + self.networkService = networkService + } + + public func fetchAlarms() async throws -> NotificationListData { + return try await networkService.request( + AlarmEndpoint(), + responseType: SuccessResponse.self + ) + .map(\.data) + .map { $0.toDomain() } + .async() + } +} diff --git a/Alarm/Sources/Data/NotificationDTO.swift b/Alarm/Sources/Data/NotificationDTO.swift new file mode 100644 index 0000000..811d57e --- /dev/null +++ b/Alarm/Sources/Data/NotificationDTO.swift @@ -0,0 +1,81 @@ +// +// NotificationDTO.swift +// Alarm +// +// Created by 강동영 on 1/16/26. +// + +import Foundation +import AlarmDomain + +public struct NotificationListDataDTO: Decodable, Sendable { + public let content: [NotificationResponseDTO] + public let lastId: Int? + public let hasNext: Bool + + private enum CodingKeys: String, CodingKey { + case content + case lastId + case hasNext + } +} + +extension NotificationListDataDTO { + func toDomain() -> NotificationListData { + return NotificationListData( + content: content.map { $0.toDomain()}, + lastId: lastId, + hasNext: hasNext + ) + } +} + +public struct NotificationResponseDTO: Decodable, Sendable { + let id: Int64 + let title: String + let content: String + let type: NotificationType + let targetId: Int64 + let thumbnailUrl: String? + let isRead: Bool + let createdAt: String + + func toDomain() -> AlarmPayload { + let dateFormatter = DateFormatter.iso8601WithMicroseconds + + return .init( + id: id, + date: Self.timeAgoDisplay(dateFormatter.date(from: createdAt) ?? Date()), + content: "\(title)이 \(content)", + imageUrl: thumbnailUrl + ) + } + + private static func timeAgoDisplay(_ date: Date) -> String { + let now = Date() + let timeInterval = now.timeIntervalSince(date) + + if timeInterval < 60 { + return "방금 전" + } else if timeInterval < 3600 { + let minutes = Int(timeInterval / 60) + return "\(minutes)분 전" + } else if timeInterval < 86400 { + let hours = Int(timeInterval / 3600) + return "\(hours)시간 전" + } else if timeInterval < 604800 { + let days = Int(timeInterval / 86400) + return "\(days)일 전" + } else { + let formatter = DateFormatter() + formatter.dateFormat = "MM.dd" + return formatter.string(from: date) + } + } +} + +enum NotificationType: String, Decodable { + case login = "LOGIN_COMPLETE" + case comment = "COMMENT_NOTIFICATION" + case like = "LIKE_NOTIFICATION" +} diff --git a/Alarm/Sources/Domain/AlarmEntities.swift b/Alarm/Sources/Domain/AlarmEntities.swift new file mode 100644 index 0000000..cc36942 --- /dev/null +++ b/Alarm/Sources/Domain/AlarmEntities.swift @@ -0,0 +1,56 @@ +// +// AlarmEntities.swift +// Alarm +// +// Created by 강동영 on 1/16/26. +// + +import Foundation + +public struct AlarmPayload: Identifiable, Sendable { + public let id: Int64 + public let date: String + public let content: String + public let imageUrl: String? + + public init( + id: Int64, + date: String, + content: String, + imageUrl: String? + ) { + self.id = id + self.date = date + self.content = content + self.imageUrl = imageUrl + } +} + +extension [AlarmPayload] { + static let dummy: [AlarmPayload] = (1...50).map { + AlarmPayload( + id: $0, + date: "2026.01.1\($0)", + content: "안녕하세요\($0)", + imageUrl: nil + ) + } +} + +public struct NotificationListData: Sendable { + public let content: [AlarmPayload] + public let lastId: Int? + public let hasNext: Bool + + private enum CodingKeys: String, CodingKey { + case content + case lastId + case hasNext + } + + public init(content: [AlarmPayload], lastId: Int?, hasNext: Bool) { + self.content = content + self.lastId = lastId + self.hasNext = hasNext + } +} diff --git a/Alarm/Sources/Domain/AlarmRepository.swift b/Alarm/Sources/Domain/AlarmRepository.swift new file mode 100644 index 0000000..d07bfe3 --- /dev/null +++ b/Alarm/Sources/Domain/AlarmRepository.swift @@ -0,0 +1,12 @@ +// +// AlarmRepository.swift +// Alarm +// +// Created by 강동영 on 10/17/25. +// + +import Foundation + +public protocol AlarmRepository: Sendable { + func fetchAlarms() async throws -> NotificationListData +} diff --git a/Alarm/Sources/Domain/GetAlarmListUseCase.swift b/Alarm/Sources/Domain/GetAlarmListUseCase.swift new file mode 100644 index 0000000..8faec7c --- /dev/null +++ b/Alarm/Sources/Domain/GetAlarmListUseCase.swift @@ -0,0 +1,24 @@ +// +// GetAlarmListUseCase.swift +// Alarm +// +// Created by 강동영 on 1/16/26. +// + + +public protocol GetAlarmListUseCase: Sendable { + func execute() async throws -> NotificationListData +} + +public final class GetAlarmListUseCaseImpl: GetAlarmListUseCase { + + private let repository: AlarmRepository + + public init(repository: AlarmRepository) { + self.repository = repository + } + + public func execute() async throws -> NotificationListData { + return try await repository.fetchAlarms() + } +} diff --git a/Alarm/Sources/Presentation/AlarmListView.swift b/Alarm/Sources/Presentation/AlarmListView.swift new file mode 100644 index 0000000..5f83dcb --- /dev/null +++ b/Alarm/Sources/Presentation/AlarmListView.swift @@ -0,0 +1,110 @@ +// +// AlarmListView.swift +// Home +// +// Created by 강동영 on 1/14/26. +// + +import SwiftUI +import AlarmDomain +import DesignSystem + +public protocol AlarmListDependecy: AlarmListFactory {} + + +public protocol AlarmListFactory { + func makeAlarmListViewModel() -> AlarmListViewModel +} + +public struct AlarmListView: View { + @Environment(\.dismiss) private var dismiss + @State private var viewModel: AlarmListViewModel + private let dependency: AlarmListDependecy + + public init(dependency: AlarmListDependecy) { + self.dependency = dependency + self._viewModel = State(initialValue: dependency.makeAlarmListViewModel()) + } + + public var body: some View { + VStack { + navigationBar + + ScrollView { + ForEach(viewModel.palyload) { prop in + LazyVStack { + AlarmView(prop) + } + } + } + .task { + await viewModel.fetchAlarmList() + } + } + .toolbar(.hidden, for: .navigationBar) + } + + private var navigationBar: some View { + HStack { + Button { + dismiss() + } label: { + Image(systemName: "chevron.left") + .font(.system(size: 18, weight: .medium)) + .foregroundColor(.iconG800) + } + + Spacer() + + Text("알림") + .pretendard(.title(.t2)) + .foregroundColor(.textG900) + + Spacer() + + Color.clear + .frame(width: 18, height: 18) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(Color.bgWhite) + } +} + +struct AlarmView: View { + private let payload: AlarmPayload + + init(_ payload: AlarmPayload) { + self.payload = payload + } + var body: some View { + HStack(spacing: 10) { + VStack(alignment: .leading, spacing: 4) { + Text(payload.date) + .pretendard(.caption(.base)) + .foregroundColor(Color.textG600) + Text(payload.content) + .pretendard(.body(.sEmphasis)) + .foregroundColor(Color.textG800) + } + + if let imageURL = payload.imageUrl { + Spacer() + + AsyncThumbnailImage( + imageURL: imageURL, + width: 40, + height: 40, + cornerRadius: 0 + ) + } + + } + .padding(16) + } +} + +//#Preview { +// AlarmListView() +//} + diff --git a/Alarm/Sources/Presentation/AlarmListViewModel.swift b/Alarm/Sources/Presentation/AlarmListViewModel.swift new file mode 100644 index 0000000..300200c --- /dev/null +++ b/Alarm/Sources/Presentation/AlarmListViewModel.swift @@ -0,0 +1,28 @@ +// +// AlarmListViewModel.swift +// Alarm +// +// Created by 강동영 on 1/18/26. +// + +import Observation +import AlarmDomain + +@Observable +public class AlarmListViewModel { + private let usecase: GetAlarmListUseCase + var palyload: [AlarmPayload] = [] + + public init(usecase: GetAlarmListUseCase) { + self.usecase = usecase + } + + func fetchAlarmList() async { + do { + palyload = try await usecase.execute().content + + } catch { + print(error) + } + } +} diff --git a/Community/Package.swift b/Community/Package.swift index 9b1c01c..e6cb936 100644 --- a/Community/Package.swift +++ b/Community/Package.swift @@ -37,6 +37,7 @@ let package = Package( .package(name: "DI", path: "../DI"), .package(name: "Common", path: "../Common"), .package(name: "Infrastructure", path: "../Infrastructure"), + .package(name: "Alarm", path: "../Alarm"), ], targets: [ .target( @@ -77,6 +78,7 @@ let package = Package( .target(config: .domain), .product(name: "DesignSystem", package: "Common"), .product(name: "SharedUI", package: "Common"), + .product(name: "AlarmDI", package: "Alarm"), ], ), ] diff --git a/Hambug.xcodeproj/project.pbxproj b/Hambug.xcodeproj/project.pbxproj index dd3858d..639b147 100644 --- a/Hambug.xcodeproj/project.pbxproj +++ b/Hambug.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ B54134FA2EF57F4100CC4938 /* Home */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Home; sourceTree = ""; }; B54134FB2EF57F6400CC4938 /* Community */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Community; sourceTree = ""; }; B54135972EF5B58100CC4938 /* 3rdParty */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = 3rdParty; sourceTree = ""; }; + B5C7D1B32F1B7B6600C20E14 /* Alarm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Alarm; sourceTree = ""; }; B5E822EB2EA2791900F3E10E /* Intro */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Intro; sourceTree = ""; }; /* End PBXFileReference section */ @@ -135,6 +136,7 @@ 915BC5C92E3CB9B50062B78E = { isa = PBXGroup; children = ( + B5C7D1B32F1B7B6600C20E14 /* Alarm */, B54135972EF5B58100CC4938 /* 3rdParty */, B54134FB2EF57F6400CC4938 /* Community */, B54134FA2EF57F4100CC4938 /* Home */, diff --git a/Hambug/Info.plist b/Hambug/Info.plist index 58fca4c..57b5819 100644 --- a/Hambug/Info.plist +++ b/Hambug/Info.plist @@ -21,6 +21,8 @@ ${KAKAO_NATIVE_APP_KEY} BASE_URL $(BASE_URL) + UIUserInterfaceStyle + Light LSApplicationQueriesSchemes kakaokompassauth diff --git a/Home/Package.swift b/Home/Package.swift index 98d24b2..bd4e63a 100644 --- a/Home/Package.swift +++ b/Home/Package.swift @@ -34,6 +34,7 @@ let package = Package( .package(name: "DI", path: "../DI"), .package(name: "Infrastructure", path: "../Infrastructure"), .package(name: "Community", path: "../Community"), + .package(name: "Alarm", path: "../Alarm"), ], targets: [ .target( @@ -76,6 +77,7 @@ let package = Package( .product(name: "DesignSystem", package: "Common"), .product(name: "SharedUI", package: "Common"), .product(name: "Community", package: "Community"), + .product(name: "AlarmDI", package: "Alarm"), ], ), ]