Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3f99739
[Delete] #206 - 논의 완료된 TODO 사항 제거
OneTen19 Apr 12, 2026
7c8d9bd
[Feat] #206 - Data, Domain API 연결 세팅
OneTen19 Apr 12, 2026
64213e7
[Style] #204 - 프로필 사진 변경 기능 사용성 확보
Remaked-Swain Apr 8, 2026
b518312
[Fix] #204 - 프로필 편집 후 아카이빙 탭이 나타나는 문제 해소
Remaked-Swain Apr 8, 2026
e7dbc2a
[Chore] #204 - 더미 유저정보 추가
Remaked-Swain Apr 8, 2026
49ab5c9
[Refactor] #204 - 유저 로그인 세션 주입방식 변경
Remaked-Swain Apr 8, 2026
1672f0a
[Chore] #202 - 어색한 로딩메세지 수정
OneTen19 Apr 12, 2026
cd073ea
[Chore] #202 - DateFormatters 통합
OneTen19 Apr 12, 2026
570d871
[Chore] #202 - imageURL들을 꺼내오는 작업을 백그라운드 스레드 과정에 포함시켜 메인스레드의 부담 완화
OneTen19 Apr 12, 2026
b391c72
[Chore] #202 - 불필요한 NavigationStack 래핑 제거
OneTen19 Apr 12, 2026
449d759
[Feat] #202 - 현재 폴더 선택 불가 및 복제시 다중 폴더 선택 가능하게 수정
OneTen19 Apr 12, 2026
156b913
[Feat] #202 - 이동과 복제와 관련된 작업 하위 리듀서들로 캡슐화
OneTen19 Apr 12, 2026
25eecb3
[Chore] #202 - 모든 사진 보기에서는 ArchiveImageFooter에 사진 이동 숨기기
OneTen19 Apr 12, 2026
6fe3e0e
[Chore] #202 - PhotoSelectionPurpose 레이어 변경
OneTen19 Apr 13, 2026
fe461ae
[Chore] #206 - 변경된 dto 반영
OneTen19 Apr 13, 2026
d4db35e
[Network] #206 - API 연결
OneTen19 Apr 13, 2026
0c5db09
Merge remote-tracking branch 'refs/remotes/origin/develop'
OneTen19 Apr 13, 2026
2761cf3
[Chore] #206 - 팀 컨벤션 맞춰 변수명 수정
OneTen19 Apr 15, 2026
ecc6890
[Chore] #206 - ifelse문 switch문으로 수정
OneTen19 Apr 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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public enum ArchiveEndpoint {
case excludePhotosInAlbum(albumID: Int, request: DeletePhotoRequestDTO)
case editFolderName(albumID: Int, request: FolderDTO.Request)
case updateMemo(photoID: Int, request: UpdateMemoRequestDTO)
case duplicatePhoto(request: UpdateMappingPhotoDTO.DuplicatePhotos)
case movePhoto(request: UpdateMappingPhotoDTO.MovePhotos)
}

extension ArchiveEndpoint: Endpoint {
Expand Down Expand Up @@ -61,6 +63,10 @@ extension ArchiveEndpoint: Endpoint {
return "folders/\(albumID)"
case .updateMemo(let id, _):
return "photos/\(id)"
case .duplicatePhoto:
return "folders/photos/copy"
case .movePhoto:
return "folders/photos/move"
}
}

Expand Down Expand Up @@ -122,6 +128,10 @@ extension ArchiveEndpoint: Endpoint {
return .patch
case .updateMemo:
return .put
case .duplicatePhoto:
return .post
case .movePhoto:
return .patch
}
}

Expand Down Expand Up @@ -151,6 +161,10 @@ extension ArchiveEndpoint: Endpoint {
return request
case .updateMemo(_, let request):
return request
case .duplicatePhoto(let request):
return request
case .movePhoto(let request):
return request
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// UpdateMappingPhotoDTO.swift
// Neki-iOS
//
// Created by OneTen on 4/12/26.
//

import Foundation

public enum UpdateMappingPhotoDTO {
public struct MovePhotos: Encodable {
let sourceFolderID: Int
let photoIDs, targetFolderIDs: [Int]

enum CodingKeys: String, CodingKey {
case sourceFolderID = "sourceFolderId"
case photoIDs = "photoIds"
case targetFolderIDs = "targetFolderIds"
}
}

public struct DuplicatePhotos: Encodable {
let photoIDs, targetFolderIDs: [Int]

enum CodingKeys: String, CodingKey {
case photoIDs = "photoIds"
case targetFolderIDs = "targetFolderIds"
}
}
}
Comment on lines +10 to +30
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2)

팀 내 컨벤션에 따르면 식별자의 복수형에 대하여 ID는 대문자로, 뒤의 s는 소문자로 작성되어야 합니다.

Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,44 @@ extension DefaultArchiveRepository {
}

}

func duplicatePhoto(photoIDs: [Int], targetFolderIDs: [Int]) async throws {
let request = UpdateMappingPhotoDTO.DuplicatePhotos(
photoIDs: photoIDs,
targetFolderIDs: targetFolderIDs
)
let endpoint = ArchiveEndpoint.duplicatePhoto(request: request)
let _ = try await networkProvider.request(endpoint: endpoint)

// 앨범 정보 캐시 갱신
self.isAlbumCacheDirty = true

// Target 폴더들의 캐시를 다음 진입 시 새로 받아오도록 유도
for targetID in targetFolderIDs {
self.isPhotoCacheDirty[targetID] = true
}
}

func movePhoto(sourceFolderId: Int, photoIDs: [Int], targetFolderIDs: [Int]) async throws {
let request = UpdateMappingPhotoDTO.MovePhotos(
sourceFolderID: sourceFolderId,
photoIDs: photoIDs,
targetFolderIDs: targetFolderIDs
)
let endpoint = ArchiveEndpoint.movePhoto(request: request)
let _ = try await networkProvider.request(endpoint: endpoint)

// 앨범 정보 캐시 갱신
self.isAlbumCacheDirty = true

self.isPhotoCacheDirty[sourceFolderId] = true

// Target 폴더(혹은 전체 사진) 캐시 갱신
for targetID in targetFolderIDs {
self.isPhotoCacheDirty[targetID] = true
}
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ struct ArchiveClient {
public var editAlbumName: (_ albumID: Int, _ name: String) async throws -> Void
public var updatePhotoMemo: (_ photoID: Int, _ memo: String) async throws -> Void
public var clearCache: () async -> Void
public var duplicatePhoto: (_ photoIDs: [Int], _ targetFolderIDs: [Int]) async throws -> Void
public var movePhoto: (_ sourceFolderId: Int, _ photoIDs: [Int], _ targetFolderIDs: [Int]) async throws -> Void
}

extension ArchiveClient: DependencyKey {
Expand Down Expand Up @@ -69,6 +71,12 @@ extension ArchiveClient: DependencyKey {
},
clearCache: {
await archiveRepository.clearCache()
},
duplicatePhoto: { photoIDs, targetFolderIDs in
try await archiveRepository.duplicatePhoto(photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
},
movePhoto: { sourceFolderId, photoIDs, targetFolderIDs in
try await archiveRepository.movePhoto(sourceFolderId: sourceFolderId, photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ protocol ArchiveRepository: Sendable {
func excludePhotosInAlbum(albumID: Int, photoIDs: [Int]) async throws
func editAlbumName(albumID: Int, name: String) async throws
func updatePhotoMemo(photoID: Int, memo: String) async throws
func duplicatePhoto(photoIDs: [Int], targetFolderIDs: [Int]) async throws
func movePhoto(sourceFolderId: Int, photoIDs: [Int], targetFolderIDs: [Int]) async throws

// Delete
func deletePhotoList(photoIDs: [Int]) async throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,33 @@ struct AlbumSelectionFeature {
case .tapConfirm:
guard !state.selectedAlbumIDs.isEmpty else { return .none }
state.isLoading = true

let purpose = state.selectionPurpose
let photoIDs = state.photoIDs
let targetFolderIDs = Array(state.selectedAlbumIDs)
let currentAlbumId = state.currentAlbumId

return .run { send in
// TODO: 실제 API 연동 (archiveClient.duplicatePhoto / movePhoto)
try? await Task.sleep(for: .seconds(1))

let msg = purpose == .duplicate ? "사진을 앨범에 추가했어요" : "사진을 앨범으로 이동했어요"
await send(.taskCompleted(message: msg))
do {
switch purpose {
case .duplicate:
// 복제 - sourceFolderId 없음
try await archiveClient.duplicatePhoto(photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
await send(.taskCompleted(message: "사진을 앨범에 추가했어요"))

case .move:
// 이동 - sourceFolderId 필수
if let sourceFolderId = currentAlbumId {
try await archiveClient.movePhoto(sourceFolderId: sourceFolderId, photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
await send(.taskCompleted(message: "사진을 앨범으로 이동했어요"))
} else {
await send(.taskFailed(message: "사진 이동에 실패했어요"))
}
}
} catch {
let failMsg = purpose == .duplicate ? "사진 추가에 실패했어요" : "사진 이동에 실패했어요"
await send(.taskFailed(message: failMsg))
}
}

case let .taskCompleted(message):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct PhotoImportFeature {
var isFetchingAlbums: Bool = false
var isLoading: Bool = false

var targetAlbumId: Int
var targetAlbumId: Int

var uploadCount: Int { selectedIDs.count }
var isUploadEnabled: Bool { uploadCount > 0 }
Expand Down Expand Up @@ -121,7 +121,7 @@ struct PhotoImportFeature {

return .run { [id = state.selectedAlbum?.id] send in
if id == -1 {
await send(.fetchPhotosResponse(Result { try await archiveClient.fetchFavoritePhotoList(20, sortOrder) }))
await send(.fetchPhotosResponse(Result { try await archiveClient.fetchFavoritePhotoList(size: 20, sortOrder: sortOrder) }))
} else {
await send(.fetchPhotosResponse(Result { try await archiveClient.fetchPhotoList(folderId: targetFolderId, size: 20, sortOrder: sortOrder) }))
}
Expand Down Expand Up @@ -172,14 +172,17 @@ struct PhotoImportFeature {
case .tapUpload:
guard state.isUploadEnabled else { return .none }
state.isLoading = true
let ids = Array(state.selectedIDs)
let targetId = state.targetAlbumId

let photoIDs = Array(state.selectedIDs)
let targetFolderIDs = [state.targetAlbumId]

return .run { send in
// TODO: 실제 API 연동 (archiveClient.duplicatePhoto(sourceFolderId: nil, photoIDs: ids, targetFolderIDs: [targetId]))
try? await Task.sleep(for: .seconds(1)) // 임시 딜레이

await send(.taskCompleted(message: "사진을 앨범에 가져왔어요"))
do {
try await archiveClient.duplicatePhoto(photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
await send(.taskCompleted(message: "사진을 앨범에 가져왔어요"))
} catch {
await send(.taskFailed(message: "사진을 가져오지 못했어요"))
}
}

case let .taskCompleted(message):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ private extension ArchiveAlbumDetailView {
} label: {
Text("앨범 삭제")
.nekiFont(.body16Medium)
.foregroundStyle(.primary500) // TODO: - 위험한 액션이니 빨간색 어떠냐고 피그마 문의 남김. 답변에 따라 수정가능성 있음
.foregroundStyle(.primary500)
}
.frame(width: 120, height: 34, alignment: .leading)
.padding(.leading, 12)
Expand Down