Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
48427e9
[#45] 변경된 검색 결과창 수정
Funital Dec 29, 2025
0c75967
[#45] Mark 주석 추가
Funital Dec 29, 2025
1d0883f
[#45] 검색결과뷰 UI 수정
Funital Dec 29, 2025
0cb83e9
[#45] 알람 조회 api에 맞는 entity로 수정 및 mock data 수정
Funital Dec 30, 2025
7a85bbe
[#45] 신고 접수 안내 component 제작
Funital Dec 30, 2025
f9b234f
[#45] 신고 접수 안내 띄우기
Funital Dec 30, 2025
13713bc
[#45] 신고 유형 entity에 추가
Funital Dec 30, 2025
b63815f
[#45] 경고 없애기
Funital Dec 30, 2025
da54855
[#45] 읽지 않음 알람 선택 로직 추가
Funital Dec 30, 2025
a7ff22a
[#45] 알림 읽음 처리 api 로직 추가
Funital Dec 30, 2025
2fc2626
[#45] padding 값 수정
Funital Dec 30, 2025
9aa7267
[#45] 에러처리 로직 수정
Funital Dec 31, 2025
cce88a8
[#45] 불필요한 VStack 제거
Funital Dec 31, 2025
d5d2390
[#45] 읽음 상태 갱신 로직 개선하기
Funital Dec 31, 2025
14a9a3b
[#45] SearchResultView에 정의된 struct를 파일로 분리시키기
Funital Dec 31, 2025
9d95cc2
[#45] 최근 검색어 tag를 눌렀을때 검색하기 로직 추가
Funital Dec 31, 2025
4bb9e82
[#45] 테두리 여백 추가
Funital Dec 31, 2025
5129b67
[#45] 키보드 내려가기 제스처 추가
Funital Dec 31, 2025
2f01b88
[#45] 검색 버튼 동작 생성 및 검색 로직 부여
Funital Dec 31, 2025
1a71b0a
[#45] 아이콘 이미지 버그 수정
Funital Jan 1, 2026
0805f34
[#45] 검색 시에 네비게이션 스택 쌓이지 않도록 수정
Funital Jan 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Codive/Core/Resources/TextLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,22 @@ enum TextLiteral {
static let totalCount = "총"
static let countUnit = "개"
static let sortAll = "전체"
static let account = "계정"
static let hashtag = "해시태그"
}

enum Notification {
static let title = "알림"
static let read = "읽음"
static let notRead = "읽지 않음"
static let noNewNoti = "새로운 알림이 없습니다."
static let feedType = "게시글"
static let commentType = "댓글"
static let reportTitle = "신고 접수 안내"
static func reportBody1(_ target: String) -> String {
"회원님의 \(target)이 운영 정책 위반으로 신고되었습니다."
}
static let reportBody2 = "확인 및 조치는 영업일 기준 3~5일정도 소요됩니다."
}

enum LookBook {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ struct MyClosetView: View {
}
} : .none
)

CustomSearchBar(text: $viewModel.searchText, type: .normal)
.padding(.horizontal, 20)
.padding(.vertical, 10)
CustomSearchBar(
text: $viewModel.searchText,
type: .normal
) {
hideKeyboard()
}
.padding(.horizontal, 20)
.padding(.vertical, 10)

mainCategoryTab

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,58 @@ final class NotificationDataSource {
func fetchNotifications() -> [NotificationEntity] {
return [
NotificationEntity(
id: 1,
imageUrl: "https://picsum.photos/id/237/200/200",
message: "홍길동님이 회원님의 옷장을 팔로우하기 시작했습니다.",
isRead: false
notificationId: 1,
notificationImageUrl: "https://picsum.photos/id/237/200/200",
notificationContent: "홍길동님이 회원님의 옷장을 팔로우하기 시작했습니다.",
redirectInfo: "user_123",
redirectType: .member,
readStatus: .unread,
createdAt: "2025-11-18T10:00:00"
),
NotificationEntity(
id: 2,
imageUrl: nil,
message: "김철수님이 새로운 게시물을 업로드했습니다.",
isRead: false
notificationId: 2,
notificationImageUrl: nil,
notificationContent: "1년 전 오늘의 기록을 확인해보세요.",
redirectInfo: "post_456",
redirectType: .history,
readStatus: .unread,
createdAt: "2025-11-18T11:00:00"
),
NotificationEntity(
id: 3,
imageUrl: "invalid_url",
message: "이영희님이 회원님의 게시물에 좋아요를 눌렀습니다.",
isRead: false
notificationId: 3,
notificationImageUrl: "https://picsum.photos/id/100/200/200",
notificationContent: "내일은 비가 올 예정입니다. 우산을 챙기세요!",
redirectInfo: "seoul",
redirectType: .weather,
readStatus: .read,
createdAt: "2025-11-18T12:00:00"
),
NotificationEntity(
id: 4,
imageUrl: "https://picsum.photos/id/100/200/200",
message: "박민수님이 회원님의 댓글에 답글을 달았습니다.",
isRead: true
notificationId: 4,
notificationImageUrl: nil,
notificationContent: "홍길동님이 팔로우를 취소했습니다.",
redirectInfo: "user_123",
redirectType: .member,
readStatus: .unread,
createdAt: "2025-11-18T10:00:00"
),
NotificationEntity(
id: 5,
imageUrl: nil,
message: "코디 추천 시즌 이벤트가 시작되었습니다.",
isRead: true
notificationId: 6,
notificationImageUrl: nil,
notificationContent: "내일은 비가 올 예정입니다. 우산을 챙기세요!",
redirectInfo: "Busan",
redirectType: .weather,
readStatus: .unread,
createdAt: "2025-11-18T12:00:00"
)
]
}

func fetchReportStatus() -> ReportEntity {
return ReportEntity(isReported: true, reportType: .feed)
}

func patchNotificationRead(notificationId: Int) async throws {
print("서버에 알림 \(notificationId)번 읽음 처리 요청 전송")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ final class NotificationRepositoryImpl: NotificationRepository {
func fetchNotifications() -> [NotificationEntity] {
return datasource.fetchNotifications()
}

func fetchReportStatus() -> ReportEntity {
return datasource.fetchReportStatus()
}

func markNotificationAsRead(request: NotificationReadRequestEntity) async throws {
try await datasource.patchNotificationRead(notificationId: request.notificationId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,45 @@

import Foundation

struct NotificationEntity: Identifiable {
let id: Int
let imageUrl: String?
let message: String
let isRead: Bool
/// 알림 유형
enum RedirectType: String, Codable {
case member = "MEMBER"
case history = "HISTORY"
case weather = "WEATHER"
}

/// 알림 읽음/읽지 않음 유형
enum ReadStatus: String, Codable {
case read = "READ"
case unread = "UNREAD"
}

/// 알림 목록 조회 api
struct NotificationEntity: Codable, Identifiable {
let notificationId: Int
let notificationImageUrl: String?
let notificationContent: String
let redirectInfo: String
let redirectType: RedirectType
var readStatus: ReadStatus
let createdAt: String

var id: Int { notificationId }
}

/// 신고 접수 유형
enum ReportType: String, Codable {
case feed = "FEED"
case comment = "COMMENT"
}

/// 신고 접수 안내 api
struct ReportEntity: Codable {
let isReported: Bool
let reportType: ReportType?
}

/// 알림 읽음 처리 request api
struct NotificationReadRequestEntity: Codable {
let notificationId: Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

protocol NotificationRepository {
func fetchNotifications() -> [NotificationEntity]
func fetchReportStatus() -> ReportEntity
func markNotificationAsRead(request: NotificationReadRequestEntity) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ final class NotificationUseCase {
func fetchNotifications() -> [NotificationEntity] {
return repository.fetchNotifications()
}

func fetchReportStatus() -> ReportEntity {
return repository.fetchReportStatus()
}

func markNotificationAsRead(notificationId: Int) async throws {
let request = NotificationReadRequestEntity(notificationId: notificationId)
try await repository.markNotificationAsRead(request: request)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,97 @@
import SwiftUI

struct NotificationRow: View {
// MARK: - Properties
let profileImageUrl: String?
let message: String
let entity: NotificationEntity

// MARK: - Constants
private let profileImageSize: CGFloat = 36
private let messageFont = Font.codive_body2_regular
private let messageColor = Color.Codive.grayscale1

// MARK: - Asset Logic
/// 타입별 전용 에셋 이미지 이름 반환
private var typeSpecificImageName: String? {
switch entity.redirectType {
case .history:
return "history"
case .weather:
return "weather"
case .member:
return nil
}
}

var body: some View {
HStack(spacing: 15) {
if let urlString = profileImageUrl, let url = URL(string: urlString) {
if let imageName = typeSpecificImageName {
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: profileImageSize, height: profileImageSize)
.clipShape(Circle())
} else if let urlString = entity.notificationImageUrl, let url = URL(string: urlString) {
AsyncImage(url: url) { phase in
if let image = phase.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
image.resizable().aspectRatio(contentMode: .fill)
} else if phase.error != nil {
Image(systemName: "person.circle.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundStyle(Color.gray.opacity(0.3))
defaultImage // URL 에러 시 기본 이미지
} else {
ProgressView()
}
}
.frame(width: profileImageSize, height: profileImageSize)
.clipShape(Circle())
} else {
Image(systemName: "person.circle.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundStyle(Color.gray.opacity(0.3))
.frame(width: profileImageSize, height: profileImageSize)
.clipShape(Circle())
defaultImage // 이미지 URL이 nil인 경우
}

Text(message)
.font(messageFont)
.foregroundStyle(messageColor)
Text(entity.notificationContent)
.font(Font.codive_body2_regular)
.foregroundStyle(Color.Codive.grayscale1)

Spacer()
}
}

private var defaultImage: some View {
Image(systemName: "person.circle.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundStyle(Color.gray.opacity(0.3))
.frame(width: profileImageSize, height: profileImageSize)
.clipShape(Circle())
}
}

// MARK: - Preview
#Preview {
VStack(spacing: 16) {
NotificationRow(
profileImageUrl: "https://picsum.photos/id/237/200/200",
message: "홍길동님이 회원님의 옷장을 팔로우하기 시작했습니다."
)
NotificationRow(
profileImageUrl: nil,
message: "김철수님이 새로운 게시물을 업로드했습니다."
)
NotificationRow(
profileImageUrl: "invalid_url",
message: "이영희님이 회원님의 게시물에 좋아요를 눌렀습니다."
)
NotificationRow(entity: NotificationEntity(
notificationId: 1,
notificationImageUrl: "https://picsum.photos/id/237/200/200",
notificationContent: "홍길동님이 회원님의 옷장을 팔로우하기 시작했습니다.",
redirectInfo: "user_123",
redirectType: .member,
readStatus: .unread,
createdAt: "2025-11-18T10:00:00"
))

NotificationRow(entity: NotificationEntity(
notificationId: 2,
notificationImageUrl: nil,
notificationContent: "김철수님이 새로운 게시물을 업로드했습니다.",
redirectInfo: "post_456",
redirectType: .history,
readStatus: .unread,
createdAt: "2025-11-18T11:00:00"
))

NotificationRow(entity: NotificationEntity(
notificationId: 3,
notificationImageUrl: "invalid_url",
notificationContent: "내일은 비 소식이 있습니다. 우산을 준비하세요!",
redirectInfo: "seoul",
redirectType: .weather,
readStatus: .read,
createdAt: "2025-11-18T12:00:00"
))
}
.padding()
}
Loading