Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f4eee65
[#53] 피드 목록 조회 API 구현
Hrepay Jan 25, 2026
4c60dfd
[#53] UI 버그 수정 및 자동로그인 임시주석 해제
Hrepay Jan 25, 2026
366d3a6
[#53] 옷추가 관련 UI 수정
Hrepay Jan 25, 2026
42a9fc9
[#53] 피드 관련 API 연결
Hrepay Jan 20, 2026
6a48a2b
[#53] ClokeyId -> NickName으로 변경
Hrepay Jan 20, 2026
2f9628a
[#53] 이미지 슬라이더 추가 및 태그 API 연결
Hrepay Jan 21, 2026
3cc112a
[#53] feedDetailView에 내용, 해시태그 추가
Hrepay Jan 21, 2026
e6ab747
[#53] feedDetailView에 태그 이미지 연결
Hrepay Jan 21, 2026
a954e49
[#53] Feed 탭 좋아요, 좋아요 리스트 API 연결
Hrepay Jan 22, 2026
f03bcba
[#53] Feed 좋아요 리스트 UI 수정
Hrepay Jan 22, 2026
5ebe26c
[#53] 아키텍처 구조에 맞게 코드 수정
Hrepay Jan 22, 2026
694f642
[#53] Comment API 연결 - CodiveAPI 인증 미들웨어 적용
Hrepay Jan 22, 2026
16a40b9
[#53] Comment UseCase, ViewModel 확장 - 대댓글 기능
Hrepay Jan 22, 2026
415dd9c
[#53] Comment UI, Sheet 지원 완성 - 대댓글 입력 및 사용자 표시
Hrepay Jan 22, 2026
ed624df
[#53] Feed 필터 API 연결 - styleIds와 situationIds ID 변환 추가
Hrepay Jan 23, 2026
6f173b5
[#53] Feed 필터 - 바텀시트 선택 스타일이 상단 카테고리에 반영
Hrepay Jan 23, 2026
5653ff2
[#53] Feed 필터 정렬 - 선택된 카테고리를 앞으로 이동
Hrepay Jan 23, 2026
a0d96f8
[#53] CodiveAPI 업데이트
Hrepay Jan 23, 2026
4651dcf
[#53] 검색 계정/해시태그 API 연결 완료
Hrepay Jan 23, 2026
17bc11c
[#53] 해시태그 기록 필터 조건 추가
Hrepay Jan 23, 2026
dc382a8
[#53] 검색-> 계정/기록 UI 구현
Hrepay Jan 23, 2026
be1653b
[#53] 검색 결과 postCard UI 수정
Hrepay Jan 24, 2026
c30d997
[#53] 마이페이지 상단바 가리기
Hrepay Jan 24, 2026
a9f77cd
[#53] 내 프로필 조회/ 팔로잉,팔로워 API 연결
Hrepay Jan 25, 2026
1939e10
[#53] 프로필 편집, 설정 네비게이션 연결
Hrepay Jan 25, 2026
267f81c
[#53] 기본 프로필 이미지 통일
Hrepay Jan 25, 2026
7a472b2
[#53] Profile 모듈 아키텍처 정리 - Clean Architecture 준수
Hrepay Jan 25, 2026
cfe3db9
[#53] 프로필 DI 오류 수정
Hrepay Jan 25, 2026
62fb30a
[#53] 프로필 수정 API 연결
Hrepay Jan 25, 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
2 changes: 1 addition & 1 deletion Codive/Core/Resources/TextLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ enum TextLiteral {
static let situationTravel = "여행"
static let situationExercise = "운동"
static let situationFestival = "축제"
static let situationWork = "출근복"
static let situationWork = "출근룩"
static let situationParty = "파티"

// Photo Tag
Expand Down
4 changes: 4 additions & 0 deletions Codive/DIContainer/AppDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ final class AppDIContainer {
func makeCommentDIContainer() -> CommentDIContainer {
return CommentDIContainer(navigationRouter: navigationRouter)
}

func makeProfileDIContainer() -> ProfileDIContainer {
return ProfileDIContainer(navigationRouter: navigationRouter)
}
}
18 changes: 14 additions & 4 deletions Codive/DIContainer/CommentDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
//

import Foundation
import CodiveAPI

@MainActor
final class CommentDIContainer {

// MARK: - Properties
let navigationRouter: NavigationRouter
lazy var commentViewFactory = CommentViewFactory(commentDIContainer: self)
Expand All @@ -22,8 +23,7 @@ final class CommentDIContainer {
// MARK: - DataSources

private lazy var commentDataSource: CommentDataSource = {
// 나중에 실제 API가 구현되면 이 부분만 DefaultCommentDataSource()로 교체
return MockCommentDataSource()
return DefaultCommentDataSource()
}()

// MARK: - Repositories
Expand All @@ -42,13 +42,23 @@ final class CommentDIContainer {
return DefaultPostCommentUseCase(commentRepository: commentRepository)
}

func makeFetchRepliesUseCase() -> FetchRepliesUseCase {
return DefaultFetchRepliesUseCase(commentRepository: commentRepository)
}

func makePostReplyUseCase() -> PostReplyUseCase {
return DefaultPostReplyUseCase(commentRepository: commentRepository)
}

// MARK: - ViewModels

func makeCommentViewModel(feedId: Int) -> CommentViewModel {
return CommentViewModel(
feedId: feedId,
fetchCommentsUseCase: makeFetchCommentsUseCase(),
postCommentUseCase: makePostCommentUseCase()
postCommentUseCase: makePostCommentUseCase(),
fetchRepliesUseCase: makeFetchRepliesUseCase(),
postReplyUseCase: makePostReplyUseCase()
)
}

Expand Down
38 changes: 28 additions & 10 deletions Codive/DIContainer/FeedDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ final class FeedDIContainer {

// MARK: - Properties
let navigationRouter: NavigationRouter
lazy var feedViewFactory = FeedViewFactory(feedDIContainer: self)
lazy var feedViewFactory = FeedViewFactory(feedDIContainer: self, navigationRouter: navigationRouter)
lazy var commentDIContainer = CommentDIContainer(navigationRouter: navigationRouter)
lazy var profileDIContainer = ProfileDIContainer(navigationRouter: navigationRouter)

// MARK: - Initializer
init(navigationRouter: NavigationRouter) {
Expand All @@ -26,7 +28,7 @@ final class FeedDIContainer {
}()

private lazy var feedDataSource: FeedDataSource = {
return MockFeedDataSource()
return DefaultFeedDataSource()
}()

// MARK: - Repositories
Expand All @@ -36,7 +38,10 @@ final class FeedDIContainer {
}()

private lazy var feedRepository: FeedRepository = {
return FeedRepositoryImpl(dataSource: feedDataSource)
return FeedRepositoryImpl(
dataSource: feedDataSource,
historyAPIService: HistoryAPIService()
)
}()

// MARK: - UseCases
Expand All @@ -57,13 +62,21 @@ final class FeedDIContainer {
return DefaultFetchFeedLikersUseCase(feedRepository: feedRepository)
}

func makeToggleLikeUseCase() -> ToggleLikeUseCase {
return DefaultToggleLikeUseCase(feedRepository: feedRepository)
}

func makeFetchClothTagsUseCase() -> FetchClothTagsUseCase {
return DefaultFetchClothTagsUseCase(feedRepository: feedRepository)
}

// MARK: - ViewModels

func makeFeedViewModel() -> FeedViewModel {
return FeedViewModel(
navigationRouter: navigationRouter,
fetchFeedsUseCase: makeFetchFeedsUseCase(),
feedRepository: feedRepository
toggleLikeUseCase: makeToggleLikeUseCase()
)
}

Expand All @@ -72,7 +85,8 @@ final class FeedDIContainer {
feedId: feedId,
fetchFeedDetailUseCase: makeFetchFeedDetailUseCase(),
fetchLikersUseCase: makeFetchFeedLikersUseCase(),
feedRepository: feedRepository,
toggleLikeUseCase: makeToggleLikeUseCase(),
fetchClothTagsUseCase: makeFetchClothTagsUseCase(),
navigationRouter: navigationRouter
)
}
Expand All @@ -82,7 +96,8 @@ final class FeedDIContainer {
func makeFeedDetailView(feedId: Int) -> FeedDetailView {
return FeedDetailView(
viewModel: makeFeedDetailViewModel(feedId: feedId),
navigationRouter: navigationRouter
navigationRouter: navigationRouter,
commentDIContainer: commentDIContainer
)
}
}
Expand All @@ -95,13 +110,16 @@ extension FeedDIContainer {
repository: FeedRepository,
navigationRouter: NavigationRouter
) -> FeedDetailViewModel {
let useCase = DefaultFetchFeedDetailUseCase(repository: repository)
let likersUseCase = DefaultFetchFeedLikersUseCase(feedRepository: repository)
let detailUseCase = DefaultFetchFeedDetailUseCase(repository: repository)
let likersUseCase = DefaultFetchFeedLikersUseCase(feedRepository: repository)
let toggleLikeUseCase = DefaultToggleLikeUseCase(feedRepository: repository)
let clothTagsUseCase = DefaultFetchClothTagsUseCase(feedRepository: repository)
return FeedDetailViewModel(
feedId: feedId,
fetchFeedDetailUseCase: useCase,
fetchFeedDetailUseCase: detailUseCase,
fetchLikersUseCase: likersUseCase,
feedRepository: repository,
toggleLikeUseCase: toggleLikeUseCase,
fetchClothTagsUseCase: clothTagsUseCase,
navigationRouter: navigationRouter
)
}
Expand Down
95 changes: 95 additions & 0 deletions Codive/DIContainer/ProfileDIContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// ProfileDIContainer.swift
// Codive
//
// Created by Claude on 1/25/26.
//

import Foundation

@MainActor
final class ProfileDIContainer {

// MARK: - Properties
let navigationRouter: NavigationRouter

// MARK: - Initializer
init(navigationRouter: NavigationRouter) {
self.navigationRouter = navigationRouter
}

// MARK: - API Service
private lazy var profileAPIService: ProfileAPIServiceProtocol = {
return ProfileAPIService()
}()

// MARK: - Data Sources
private lazy var profileDataSource: ProfileDataSourceProtocol = {
return ProfileDataSource(apiService: profileAPIService)
}()

// MARK: - Repositories
private lazy var profileRepository: ProfileRepository = {
return ProfileRepositoryImpl(dataSource: profileDataSource)
}()

// MARK: - UseCases
func makeFetchMyProfileUseCase() -> FetchMyProfileUseCase {
return DefaultFetchMyProfileUseCase(repository: profileRepository)
}

func makeFetchFollowsUseCase() -> FetchFollowsUseCase {
return DefaultFetchFollowsUseCase(repository: profileRepository)
}

func makeUpdateProfileUseCase() -> UpdateProfileUseCase {
return DefaultUpdateProfileUseCase(repository: profileRepository)
}

// MARK: - ViewModels
func makeProfileViewModel() -> ProfileViewModel {
return ProfileViewModel(
navigationRouter: navigationRouter,
fetchMyProfileUseCase: makeFetchMyProfileUseCase()
)
}

func makeFollowListViewModel(mode: FollowListMode, memberId: Int) -> FollowListViewModel {
return FollowListViewModel(
mode: mode,
memberId: memberId,
fetchFollowsUseCase: makeFetchFollowsUseCase()
)
}

func makeProfileSettingViewModel() -> ProfileSettingViewModel {
return ProfileSettingViewModel(
navigationRouter: navigationRouter,
updateProfileUseCase: makeUpdateProfileUseCase(),
profileRepository: profileRepository
)
}

func makeOtherProfileViewModel() -> OtherProfileViewModel {
return OtherProfileViewModel(navigationRouter: navigationRouter)
}

// MARK: - Views
func makeProfileView() -> ProfileView {
return ProfileView(viewModel: makeProfileViewModel(), navigationRouter: navigationRouter)
}

func makeFollowListView(mode: FollowListMode, memberId: Int) -> FollowListView {
return FollowListView(
viewModel: makeFollowListViewModel(mode: mode, memberId: memberId),
navigationRouter: navigationRouter
)
}

func makeProfileSettingView() -> ProfileSettingView {
return ProfileSettingView(
viewModel: makeProfileSettingViewModel(),
navigationRouter: navigationRouter
)
}
}
6 changes: 4 additions & 2 deletions Codive/DIContainer/SearchDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ final class SearchDIContainer {
let navigationRouter: NavigationRouter
lazy var searchViewFactory = SearchViewFactory(searchDIContainer: self)

lazy var searchDataSource = SearchDataSource()

lazy var searchAPIService: SearchAPIServiceProtocol = SearchAPIService()

lazy var searchDataSource = SearchDataSource(apiService: searchAPIService)

lazy var searchRepository: SearchRepository = SearchRepositoryImpl(datasource: searchDataSource)

lazy var searchUseCase = SearchUseCase(repository: searchRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ final class SplashViewModel: ObservableObject {

private func checkAutoLogin() async {
// 임시 자동 로그인 해제 (토큰이 있어도 로그인 화면으로 이동)
appRouter.finishSplash()
return
// appRouter.finishSplash()
// return

/*
// 1. 키체인에 토큰이 있는지 확인
guard tokenService.hasValidTokens() else {
appRouter.finishSplash()
Expand All @@ -91,7 +90,6 @@ final class SplashViewModel: ObservableObject {

// 4. 토큰 재발급
await reissueTokens()
*/
}

private func reissueTokens() async {
Expand Down
8 changes: 7 additions & 1 deletion Codive/Features/Closet/Data/ClothAPIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct ClothDetailResult {
let name: String?
let brand: String?
let clothUrl: String?
let seasons: [Season]
}

struct ClothUpdateAPIRequest {
Expand Down Expand Up @@ -220,13 +221,18 @@ extension ClothAPIService {
throw ClothAPIError.serverError(statusCode: 0, message: "result가 nil입니다")
}

let seasons = result.seasons?.compactMap { seasonString -> Season? in
Season(rawValue: seasonString.rawValue)
} ?? []

return ClothDetailResult(
clothImageUrl: result.clothImageUrl ?? "",
parentCategory: result.parentCategory,
category: result.category,
name: result.name,
brand: result.brand,
clothUrl: result.clothUrl
clothUrl: result.clothUrl,
seasons: seasons
)

case .undocumented(statusCode: let code, _):
Expand Down
36 changes: 15 additions & 21 deletions Codive/Features/Closet/Data/DataSources/ClothDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,25 @@ final class DefaultClothDataSource: ClothDataSource {
self.apiService = apiService
}

// MARK: - Mock Data (TODO: API 연결 후 제거)

private let mockClothItems: [ProductItem] = [
ProductItem(id: 1, imageName: "sample1", isTodayCloth: true, brand: "Nike", name: "에어포스 1"),
ProductItem(id: 2, imageName: "sample2", isTodayCloth: true, brand: "Adidas", name: "후디"),
ProductItem(id: 3, imageName: "sample3", isTodayCloth: true, brand: nil, name: "검은 모자"),
ProductItem(id: 4, imageName: "sample4", isTodayCloth: false, brand: "Uniqlo", name: "오버핏 티셔츠"),
ProductItem(id: 5, imageName: "sample5", isTodayCloth: false, brand: "Zara", name: "슬랙스"),
ProductItem(id: 6, imageName: "sample6", isTodayCloth: false, brand: nil, name: nil)
]

// MARK: - Methods

func fetchClothItems(category: String?) async throws -> [ProductItem] {
// TODO: 실제 API 호출로 대체
// 전체 옷 목록 조회 (페이지네이션 없이 전체)
let result = try await apiService.fetchClothes(
lastClothId: nil,
size: 100,
categoryId: nil,
seasons: []
)

// 카테고리 필터링 (전체면 전부 반환)
if let category = category, category != "전체" {
return mockClothItems.filter { _ in
// TODO: ProductItem에 category 필드 추가 후 필터링
return true
}
return result.clothes.map { item in
ProductItem(
id: Int(item.clothId),
imageUrl: item.imageUrl,
brand: item.brand,
name: item.name
)
}

return mockClothItems
}

/// 옷 저장 (전체 흐름: Presigned URL → S3 업로드 → 옷 생성)
Expand Down
19 changes: 18 additions & 1 deletion Codive/Features/Closet/Domain/Entities/ProductItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,25 @@ import Foundation
// MARK: - Product Item Model
struct ProductItem: Identifiable {
let id: Int
let imageName: String
let imageName: String?
let imageUrl: String?
let isTodayCloth: Bool
let brand: String?
let name: String?

init(
id: Int,
imageName: String? = nil,
imageUrl: String? = nil,
isTodayCloth: Bool = false,
brand: String? = nil,
name: String? = nil
) {
self.id = id
self.imageName = imageName
self.imageUrl = imageUrl
self.isTodayCloth = isTodayCloth
self.brand = brand
self.name = name
}
}
Loading