Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d8cc215
[#43] UseCase 기능별 분리
Funital Dec 25, 2025
ef65e08
[#43] CodiClothView 수정
Funital Dec 25, 2025
5a054c6
[#43] HomeNoCodiView 수정
Funital Dec 25, 2025
abd16e8
[#43] CategoryView TextLiteral 수정
Funital Dec 25, 2025
21095bf
[#43] 경고문 제거
Funital Dec 25, 2025
e653c38
[#43] CodiBoardView DTO 추가
Funital Dec 25, 2025
09d756c
[#43] 코디 완성 팝업창 제작
Funital Dec 25, 2025
28f4686
[#43] 코디 완성 후 팝업 띄우기 로직 추가
Funital Dec 25, 2025
b257dac
[#43] 홈화면 카테고리별 계절 옷 추천 api 연결 준비
Funital Dec 25, 2025
528b266
[#43] 오늘의 코디 api 연결 준비
Funital Dec 25, 2025
368eb32
[#43] 룩북에 추가 바텀시트 제작
Funital Dec 26, 2025
e1f8414
[#43] HomeViewModel 수정
Funital Dec 26, 2025
3496492
[#43] 바텀시트 연결하기
Funital Dec 26, 2025
b3bb867
[#43] HomeView 스크롤 오류 수정 및 태그 기능 추가
Funital Dec 26, 2025
409a251
[#43] 태그 위치 및 mock data 적용
Funital Dec 26, 2025
1054b09
[#43] 태그 출력 수정 및 선택 초기화 기능 추가
Funital Dec 26, 2025
46cb29e
[#43] 옷 태그 위치 기준 수정
Funital Dec 29, 2025
b771d7c
[#43] 옷이 없는 경우, 버튼 숨김처리
Funital Dec 29, 2025
0651f76
[#43] 옷이 없는 경우, 상의 하의 신발 로 초기 세팅 되도록 수정
Funital Dec 29, 2025
c723d59
[#43] 카테고리 별 mock data가 뜨도록 수정
Funital Dec 29, 2025
0a876b7
[#43] 옷이 없는 경우의 CodiClothView 인덱스 정렬 수정
Funital Dec 29, 2025
814f10d
[#43] 총 카테고리 7개 수정 및 중복 불가로 수정
Funital Jan 1, 2026
6b6db95
[#43] 카테고리 상품 개수 별 위치 자동 설정
Funital Jan 2, 2026
c073362
Merge branch 'develop' into feat/#43
Funital Jan 3, 2026
fc69dcc
[#43] 카테고리별 선택된 인덱스의 이미지로 띄우기 수정
Funital Jan 3, 2026
8307c9b
[#43] 고정 카테고리 상품 해제
Funital Jan 3, 2026
307c8ce
[#43] 팝업 카테고리 이미지 겹침 레이아웃 수정
Funital Jan 3, 2026
3e8e184
[#43] 팝업 카테고리 이미지 영역 수정
Funital Jan 3, 2026
115479c
[#43] 오늘의 코디 생성 api 연결 준비
Funital Jan 15, 2026
0e47d6e
[#43] 코드 정리
Funital Jan 15, 2026
f9703a7
[#43] 오류 수정
Funital Jan 15, 2026
d8229ce
[#43] 팝업창 띄우기 수정
Funital Jan 15, 2026
83fdb03
[#43] swiftlint 경고 제거
Funital Jan 15, 2026
15f7f24
Merge branch 'develop' into feat/#43
Funital Jan 15, 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
10 changes: 8 additions & 2 deletions Codive/Core/Resources/TextLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,15 @@ enum TextLiteral {
static let failWeather = "날씨 정보를 가져오는 데 실패했습니다."
static let todayCodiTitle = "오늘의 코디"
static let currentCategoryCount = "현재 카테고리"
static let changeAlertTitle = "변경사항이 있습니다"
static let changeAlertMessage = "변경사항을 저장하지 않고 나가시겠습니까?"
static let changeAlertTitle = "정말 나가시겠습니까?"
static let changeAlertMessage = "작성중인 내용은 복구할 수 없습니다"
static let leave = "나가기"
static let noClothTitle = "아직 옷이 없어요!"
static let noClothDescription = "옷을 추가해 날씨에\n맞게 코디 해봐요."
static let popUpTitle = "오늘의 코디 완성!"
static let popUpSubtitle = "오늘의 코디는 홈 화면에서 하루 동안만 유지\n됩니다. 추가로 기록하려면 피드에 남겨보세요!"
static let close = "닫기"
static let record = "기록하기"
}

enum Search {
Expand Down
72 changes: 62 additions & 10 deletions Codive/DIContainer/HomeDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,90 @@ final class HomeDIContainer {

let locationService: LocationService = SystemLocationService()

lazy var homeDatasource = HomeDatasource(locationService: locationService)
// HomeViewModel 싱글톤 인스턴스 저장
private var homeViewModel: HomeViewModel?

lazy var homeRepository: HomeRepository = HomeRepositoryImpl(
dataSource: homeDatasource
)
// MARK: - DataSources
private lazy var homeDatasource: HomeDatasource = {
HomeDatasource(locationService: locationService)
}()

// MARK: - Repositories
private lazy var homeRepository: HomeRepository = {
HomeRepositoryImpl(dataSource: homeDatasource)
}()

// MARK: - UseCases (각 기능별)

func makeFetchWeatherUseCase() -> FetchWeatherUseCase {
FetchWeatherUseCase(repository: homeRepository)
}

lazy var homeUseCase = HomeUseCase(repository: homeRepository)
func makeCategoryUseCase() -> CategoryUseCase {
CategoryUseCase(repository: homeRepository)
}

func makeCodiBoardUseCase() -> CodiBoardUseCase {
CodiBoardUseCase(repository: homeRepository)
}

func makeTodayCodiUseCase() -> TodayCodiUseCase {
TodayCodiUseCase(repository: homeRepository)
}

func makeDateUseCase() -> DateUseCase {
DateUseCase(repository: homeRepository)
}

func makeAddToLookBookUseCase() -> AddToLookBookUseCase {
AddToLookBookUseCase(repository: homeRepository)
}

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

// MARK: - ViewModels

func makeHomeViewModel() -> HomeViewModel {
return HomeViewModel(
if let existingViewModel = homeViewModel {
return existingViewModel
}

let viewModel = HomeViewModel(
navigationRouter: navigationRouter,
useCase: homeUseCase
fetchWeatherUseCase: makeFetchWeatherUseCase(),
todayCodiUseCase: makeTodayCodiUseCase(),
dateUseCase: makeDateUseCase(),
categoryUseCase: makeCategoryUseCase(),
addToLookBookUseCase: makeAddToLookBookUseCase()
)

homeViewModel = viewModel
return viewModel
}

func makeEditCategoryViewModel() -> EditCategoryViewModel {
return EditCategoryViewModel(navigationRouter: navigationRouter)
return EditCategoryViewModel(
navigationRouter: navigationRouter
)
}

func makeCodiBoardViewModel() -> CodiBoardViewModel {
return CodiBoardViewModel(
navigationRouter: navigationRouter, useCase: homeUseCase
navigationRouter: navigationRouter,
codiBoardUseCase: makeCodiBoardUseCase(),
homeViewModel: homeViewModel
)
}

// MARK: - Views

func makeEditCategoryView() -> EditCategoryView {
return EditCategoryView(viewModel: makeEditCategoryViewModel())
return EditCategoryView(
viewModel: makeEditCategoryViewModel()
)
}

func makeCodiBoardView() -> CodiBoardView {
Expand Down
171 changes: 146 additions & 25 deletions Codive/Features/Home/Data/DataSources/HomeDatasource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ final class HomeDatasource {
self.locationService = locationService
}

// MARK: - Location & Geocoding
// MARK: - 날씨 및 위치

// 위치
private func geocodeLocation(_ location: CLLocation) async -> String {
let geocoder = CLGeocoder()
do {
Expand Down Expand Up @@ -64,8 +66,8 @@ final class HomeDatasource {
return "위치 정보 오류"
}
}
// MARK: - Weather Data Fetching

// 날씨
func fetchWeatherData(for location: CLLocation?) async throws -> WeatherData {

let targetLocation: CLLocation
Expand Down Expand Up @@ -95,22 +97,93 @@ final class HomeDatasource {
currentTemp: currentTemp,
symbolName: symbolName,
dailyForecasts: Array(dailyForecasts),
// MARK: - 수정: 위치 이름을 추가
locationName: locationName
)

return weatherData
}

// MARK: - Categories
// MARK: - 코디가 없는 경우의 Home 관련

/// 홈화면 - 카테고리 별 옷 더미 list
func fetchClothItems(request: ClothListRequestDTO) async throws -> [ClothListResponseDTO] {

let categoryId = request.categoryId ?? 1
let mockResponse: [ClothListResponseDTO]

switch categoryId {
case 1:
mockResponse = [
ClothListResponseDTO(clothId: 101, clothImageUrl: "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=800"),
ClothListResponseDTO(clothId: 102, clothImageUrl: "https://images.unsplash.com/photo-1596755389378-c31d21fd1273?w=800")
]
case 2:
mockResponse = [
ClothListResponseDTO(clothId: 201, clothImageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800"),
ClothListResponseDTO(clothId: 202, clothImageUrl: "https://images.unsplash.com/photo-1594633312681-425c7b97ccd1?w=800")
]
case 3:
mockResponse = [
ClothListResponseDTO(clothId: 201, clothImageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800"),
ClothListResponseDTO(clothId: 202, clothImageUrl: "https://images.unsplash.com/photo-1594633312681-425c7b97ccd1?w=800")
]
case 4:
mockResponse = [
ClothListResponseDTO(clothId: 201, clothImageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800"),
ClothListResponseDTO(clothId: 202, clothImageUrl: "https://images.unsplash.com/photo-1594633312681-425c7b97ccd1?w=800")
]
case 5:
mockResponse = [
ClothListResponseDTO(clothId: 501, clothImageUrl: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=800"),
ClothListResponseDTO(clothId: 502, clothImageUrl: "https://images.unsplash.com/photo-1549298916-b41d501d3772?w=800")
]
case 6:
mockResponse = [
ClothListResponseDTO(clothId: 201, clothImageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800"),
ClothListResponseDTO(clothId: 202, clothImageUrl: "https://images.unsplash.com/photo-1594633312681-425c7b97ccd1?w=800")
]
case 7:
mockResponse = [
ClothListResponseDTO(clothId: 201, clothImageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800"),
ClothListResponseDTO(clothId: 202, clothImageUrl: "https://images.unsplash.com/photo-1594633312681-425c7b97ccd1?w=800")
]
default:
mockResponse = []
}
Comment on lines +114 to +152

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

switch 문에서 case 2, 3, 4, 6, 7의 코드가 중복됩니다. 여러 case를 쉼표로 구분하여 함께 처리하면 코드를 더 간결하게 만들고 유지보수성을 높일 수 있습니다.

        switch categoryId {
        case 1: 
            mockResponse = [
                ClothListResponseDTO(clothId: 101, clothImageUrl: "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=800"),
                ClothListResponseDTO(clothId: 102, clothImageUrl: "https://images.unsplash.com/photo-1596755389378-c31d21fd1273?w=800")
            ]
        case 2, 3, 4, 6, 7:
            mockResponse = [
                ClothListResponseDTO(clothId: 201, clothImageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800"),
                ClothListResponseDTO(clothId: 202, clothImageUrl: "https://images.unsplash.com/photo-1594633312681-425c7b97ccd1?w=800")
            ]
        case 5:
            mockResponse = [
                ClothListResponseDTO(clothId: 501, clothImageUrl: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=800"),
                ClothListResponseDTO(clothId: 502, clothImageUrl: "https://images.unsplash.com/photo-1549298916-b41d501d3772?w=800")
            ]
        default:
            mockResponse = []
        }


return mockResponse
}

// 오늘의 코디 추가하기
func createTodayDailyCodi(_ entity: TodayDailyCodi) async throws {

let requestDTO = CodiCoordinateRequestDTO(
coordinateImageUrl: entity.coordinateImageUrl,
Payload: entity.payloads.map {
CodiCoordinatePayloadDTO(
clothId: Int64($0.clothId),
locationX: $0.locationX,
locationY: $0.locationY,
ratio: $0.ratio,
degree: Double($0.degree),
order: $0.order
)
}
)

try await saveCodiCoordinate(requestDTO)
}

// MARK: - Categorory 수정 뷰 관련
/// 카테고리 별 개수
func loadCategories() -> [CategoryEntity] {
if let savedCategories = UserDefaults.standard.data(forKey: "SavedCategories"),
let decoded = try? JSONDecoder().decode([CategoryEntity].self, from: savedCategories) {
return decoded
}

return [
CategoryEntity(id: 1, title: "상의", itemCount: 1),
CategoryEntity(id: 1, title: "상의", itemCount: 2),
CategoryEntity(id: 2, title: "바지", itemCount: 1),
CategoryEntity(id: 3, title: "스커트", itemCount: 0),
CategoryEntity(id: 4, title: "아우터", itemCount: 0),
Expand All @@ -120,15 +193,33 @@ final class HomeDatasource {
]
}

/// 카테고리 적용하기
func saveCategories(_ categories: [CategoryEntity]) {
if let encoded = try? JSONEncoder().encode(categories) {
UserDefaults.standard.set(encoded, forKey: "SavedCategories")
}
print("저장 완료:")
categories.forEach { print("\($0.id): \($0.title): \($0.itemCount)") }
}

// MARK: - 코디보드
// 코디 추가하기
func saveCodiCoordinate(_ request: CodiCoordinateRequestDTO) async throws {
try await Task.sleep(nanoseconds: 500_000_000)

for (index, item) in request.Payload.enumerated() {
print("""
[Item \(index)]
- clothId: \(item.clothId)
- position: (\(item.locationX), \(item.locationY))
- ratio(scale): \(item.ratio)
- degree: \(item.degree)
- order: \(item.order)
""")
}
}

// MARK: - Codi Items
// 코디보드 옷 불러오기
func loadInitialImages() -> [DraggableImageEntity] {
return [
DraggableImageEntity(id: 1, name: "image1", position: CGPoint(x: 80, y: 80), scale: 1.0, rotationAngle: 0.0),
Expand All @@ -140,33 +231,63 @@ final class HomeDatasource {
]
}

func saveCodiItems(_ images: [DraggableImageEntity]) {
print("코디 저장 완료 (\(images.count)개)")
for image in images {
let pos = "pos: (\(Int(image.position.x)), \(Int(image.position.y)))"
let scaleStr = "scale: \(String(format: "%.2f", image.scale))"
let rotStr = "rotation: \(String(format: "%.2f", image.rotationAngle))°"
print("• \(image.name) →", pos + ",", scaleStr + ",", rotStr)
}
}

// MARK: - 코디가 있는 경우의 Home 관련
// 코디 불러오기
func loadDummyCodiItems() -> [CodiItemEntity] {
return [
CodiItemEntity(id: 1, imageName: "image1", x: 80, y: 80, width: 80, height: 80),
CodiItemEntity(id: 2, imageName: "image2", x: 160, y: 120, width: 90, height: 90),
CodiItemEntity(id: 3, imageName: "image3", x: 240, y: 160, width: 100, height: 100),
CodiItemEntity(id: 4, imageName: "image4", x: 120, y: 240, width: 70, height: 70),
CodiItemEntity(id: 5, imageName: "image5", x: 200, y: 280, width: 120, height: 120),
CodiItemEntity(id: 6, imageName: "image6", x: 250, y: 240, width: 110, height: 110)
CodiItemEntity(
id: 1,
imageName: "image1",
clothName: "시계",
brandName: "apple",
description: "사계절 착용 가능한 시계",
x: 300,
y: 100,
width: 70,
height: 70
),
CodiItemEntity(
id: 2,
imageName: "image4",
clothName: "체크 셔츠",
brandName: "Polo",
description: "사계절 착용 가능한 셔츠",
x: 100,
y: 100,
width: 70,
height: 70
),
CodiItemEntity(
id: 3,
imageName: "image3",
clothName: "와이드 치노 팬츠",
brandName: "Basic Concept",
description: "사계절 착용 가능한 면 바지",
x: 300,
y: 200,
width: 100,
height: 100
)
]
}
// MARK: - Date Handling

// 오늘의 날짜
func fetchToday() -> DateEntity {
let formatter = DateFormatter()
formatter.dateFormat = "MM.dd"

let todayString = formatter.string(from: Date())
return DateEntity(formattedDate: todayString)
}

// 룩북에 추가 바텀시트 더미데이터
func fetchLookBookList() async throws -> [LookBookBottomSheetEntity] {
try await Task.sleep(nanoseconds: 300_000_000)

return [
LookBookBottomSheetEntity(lookbookId: 1, codiId: 101, imageUrl: "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=800", title: "운동룩", count: 6),
LookBookBottomSheetEntity(lookbookId: 2, codiId: 102, imageUrl: "https://images.unsplash.com/photo-1541099649105-f69ad21f3246?w=800", title: "출근룩", count: 12),
LookBookBottomSheetEntity(lookbookId: 3, codiId: 103, imageUrl: "https://images.unsplash.com/photo-1596755389378-c31d21fd1273?w=800", title: "데이트룩", count: 16)
]
}
}
Loading