[feat] #179 #180 #181 #184 Sprint 4: 사진 이동/복제 + 앨범 삭제#187
[feat] #179 #180 #181 #184 Sprint 4: 사진 이동/복제 + 앨범 삭제#187
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
Walkthrough사진 복사·이동 API와 저장소 메서드가 추가되고, 앨범 상세·전체 사진·선택 앨범 UI/네비게이션에 복사/이동 및 앨범 삭제·가져오기 플로우가 통합되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as AlbumDetail UI
participant VM as AlbumDetailViewModel
participant Repo as FolderRepository
participant API as FolderService
participant Server as Backend
User->>UI: 사진 선택 후 '복사' 또는 '이동' 클릭
UI->>VM: ClickCopyIcon / ClickMoveIcon (selected photoIds)
VM->>VM: selectedPhotos 검증
VM->>UI: NavigateToSelectAlbum(SelectAlbumAction.CopyPhotos/MovePhotos)
UI->>User: 앨범 선택 화면 표시
User->>UI: 대상 앨범 선택 & 확인
UI->>SelectAlbumVM: ClickConfirmButton (selected albums)
SelectAlbumVM->>Repo: copyPhotos(photoIds, targetFolderIds) or movePhotos(sourceFolderId, photoIds, targetFolderIds)
Repo->>API: copyPhotos / movePhotos 요청 (Copy/MovePhotosRequest)
API->>Server: HTTP POST/PATCH /api/folders/photos/...
Server-->>API: 응답 Result<Unit>
API-->>Repo: Result<Unit>
Repo-->>SelectAlbumVM: Result<Unit>
SelectAlbumVM->>UI: SendPhotoCopiedResult / SendPhotoMovedResult (resultEventBus)
UI->>User: 성공 토스트 및 네비게이션(뒤로)
sequenceDiagram
actor User
participant UI as AlbumDetail UI
participant VM as AlbumDetailViewModel
participant Repo as FolderRepository
participant API as FolderService
participant Server as Backend
User->>UI: 앨범 옵션에서 '앨범 삭제' 선택
UI->>VM: ClickDeleteAlbumOption
VM->>UI: isShowDeleteAlbumBottomSheet = true
User->>UI: 삭제 옵션 선택 (사진 포함/미포함) & 확인
UI->>VM: ClickDeleteAlbumConfirmButton (deletePhotos)
VM->>Repo: deleteFolder(albumId, deletePhotos)
Repo->>API: DELETE /api/folders/{id}
API->>Server: DELETE 요청
Server-->>API: Result<Unit>
API-->>Repo: Result<Unit>
Repo-->>VM: Result<Unit>
VM->>UI: NavigateBack / NotifyResult / 토스트
UI->>User: 앨범 목록으로 이동 및 성공 알림
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
2c0f094 to
d49d0a1
Compare
- CopyPhotos에 withShowToast 파라미터 추가 - 사진 상세에서 복제 시 SelectAlbum 토스트 생략 (PhotoCopied 이벤트로 별도 처리) - 앨범 이동 완료 시 토스트 표시 추가
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
feature/archive/impl/src/main/res/drawable/icon_copy_photo.xml (1)
1-33: 아이콘 색상 일관성 확인 필요이 아이콘은 스트로크 색상
#616575를 사용하지만, 같은 PR에서 추가된icon_move_photo.xml은#B7B9C3을 사용합니다. 의도된 디자인 차이인지 확인해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/archive/impl/src/main/res/drawable/icon_copy_photo.xml` around lines 1 - 33, 아이콘 색상 불일치 문제: 이 파일의 벡터 요소들(그룹 내부의 <path>와 외부의 세 <path> 요소들에서 사용하는 android:strokeColor="#616575")이 같은 PR의 다른 아이콘(icon_move_photo.xml)이 사용하는 "#B7B9C3"과 다릅니다; 디자인 의도에 따라 일관되게 만들려면 해당 요소들의 android:strokeColor 값을 프로젝트 표준 색("#B7B9C3" 또는 지정된 디자인 색상)으로 변경하거나(또는 반대로 icon_move_photo.xml을 "#616575"로 수정) 디자이너/디자인 토큰을 확인해 결정하고 모든 관련 path 요소(android:strokeColor가 설정된 요소들)에서 동일한 색상으로 통일하세요.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailActionBar.kt (1)
81-110:ActionBarItem컴포넌트가AllPhotoActionBar.kt와 중복됩니다.이 파일의
ActionBarItem과AllPhotoActionBar.kt의ActionBarItem이 동일한 구현을 가지고 있습니다. 공통 컴포넌트로 추출하여 재사용하는 것을 고려해 주세요.♻️ 공통 컴포넌트 추출 제안
ActionBarItem을core/ui또는feature/archive/impl/component등 공통 위치로 이동하여 두 파일에서 재사용할 수 있습니다.// 공통 위치에 ActionBarItem.kt 생성 `@Composable` fun ActionBarItem( iconRes: Int, label: String, isEnabled: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { // 기존 구현... }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailActionBar.kt` around lines 81 - 110, ActionBarItem is duplicated between AlbumDetailActionBar.kt and AllPhotoActionBar.kt; extract the `@Composable` ActionBarItem function into a shared file (e.g., core/ui or feature/archive/impl/component/ActionBarItem.kt), keep its signature (iconRes, label, isEnabled, onClick, modifier) and implementation identical, then replace the local definitions in AlbumDetailActionBar and AllPhotoActionBar with imports of the shared ActionBarItem and remove the duplicated function declarations; ensure package and import statements are updated so both AlbumDetailActionBar and AllPhotoActionBar reference the new shared component.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt (1)
180-194:density변수가 중복 선언되어 있습니다.Line 181의
density변수는 Line 118에서 이미 선언된 동일한 이름의 변수를 섀도잉합니다. 외부 스코프의density를 재사용하거나, 명확성을 위해 다른 이름을 사용하세요.♻️ 중복 선언 제거 제안
if (uiState.isShowOptionPopup) { - val density = LocalDensity.current - val popupOffsetX = with(density) { (-20).dp.toPx().toInt() } - val popupOffsetY = with(density) { 54.dp.toPx().toInt() } + val popupOffsetX = with(LocalDensity.current) { (-20).dp.toPx().toInt() } + val popupOffsetY = with(LocalDensity.current) { 54.dp.toPx().toInt() } DropdownPopup(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt` around lines 180 - 194, The LocalDensity.current variable is being shadowed: remove the inner declaration at the PhotoDetailScreen block and reuse the existing density from the outer scope (or rename consistently) so popupOffsetX and popupOffsetY compute using that same density; update the computations that set popupOffsetX and popupOffsetY (used by DropdownPopup) to reference the existing density variable, leaving the rest (uiState.isShowOptionPopup, DropdownPopup, onIntent handlers, and PhotoDetailIntent values) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt`:
- Around line 114-117: When handling AlbumDetailIntent.SelectImportAlbum in
AlbumDetailViewModel, you're only updating importPhotoState.selectedAlbumId but
not clearing previous importPhotoState.selectedPhotoIds (and related selection
flags), so previously selected photo IDs from another album are retained and
later sent by loadImportPhotos; fix by clearing
importPhotoState.selectedPhotoIds (and any isAllSelected/isSelectAll flags) in
the same reduce call that sets selectedAlbumId and isShowAlbumDropdown before
calling loadImportPhotos so selection state is reset whenever selectedAlbumId
changes.
- Around line 315-323: Before calling folderRepository.copyPhotos in the
viewModelScope.launch block, guard against an empty selection: check
state.importPhotoState.selectedPhotoIds (used in the current block) and if
empty, stop the flow (reset importPhotoState.isLoading = false if set, close the
bottom sheet or keep it open as desired) and optionally post a side effect like
a toast or no-op; only call folderRepository.copyPhotos when
selectedPhotoIds.isNotEmpty(). Update the logic around viewModelScope.launch /
reduce and the copyPhotos invocation to perform this early-return check so no
request is made with an empty list.
- Around line 294-296: The "전체사진" count currently uses loadedAlbums.sumOf {
it.photoCount } which double-counts photos that appear in multiple albums;
change the calculation used when constructing AlbumFilterOption (the
AlbumFilterOption(..., loadedAlbums.sumOf { it.photoCount }) call) to count
unique photos instead by flattening loadedAlbums' photo lists and taking
distinct by photo id (or using an existing combined/allPhotos collection) and
using its size so the total reflects unique photos across albums.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/ImportPhotoBottomSheet.kt`:
- Around line 71-73: The sheetHeight is stored with remember but not keyed to
changing inputs so it won't recompute on rotation or inset changes; update the
calculation of sheetHeight (the val sheetHeight using remember) to either
include configuration, statusBarHeight, and navigationBarHeight as remember keys
or remove remember and compute it directly each composition so that sheetHeight
recalculates when configuration or inset values change.
In
`@feature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumNavKey.kt`:
- Line 20: The default value for SelectAlbumNavKey.multiSelect was changed and
that affects callers that rely on the implicit default; update either the
default back to its original value or explicitly pass the desired boolean at
each call site: locate SelectAlbumNavKey (constructor/property multiSelect) and
the navigateToSelectAlbum usages (the functions invoking navigateToSelectAlbum
in MainActivity, MainScreen, AlbumDetailScreen, AllPhotoScreen,
PhotoDetailScreen) and ensure each call explicitly supplies
multiSelect=true/false to preserve intended behavior, or revert the multiSelect
default to its previous value to avoid changing behavior across those five
callers.
---
Nitpick comments:
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailActionBar.kt`:
- Around line 81-110: ActionBarItem is duplicated between
AlbumDetailActionBar.kt and AllPhotoActionBar.kt; extract the `@Composable`
ActionBarItem function into a shared file (e.g., core/ui or
feature/archive/impl/component/ActionBarItem.kt), keep its signature (iconRes,
label, isEnabled, onClick, modifier) and implementation identical, then replace
the local definitions in AlbumDetailActionBar and AllPhotoActionBar with imports
of the shared ActionBarItem and remove the duplicated function declarations;
ensure package and import statements are updated so both AlbumDetailActionBar
and AllPhotoActionBar reference the new shared component.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt`:
- Around line 180-194: The LocalDensity.current variable is being shadowed:
remove the inner declaration at the PhotoDetailScreen block and reuse the
existing density from the outer scope (or rename consistently) so popupOffsetX
and popupOffsetY compute using that same density; update the computations that
set popupOffsetX and popupOffsetY (used by DropdownPopup) to reference the
existing density variable, leaving the rest (uiState.isShowOptionPopup,
DropdownPopup, onIntent handlers, and PhotoDetailIntent values) unchanged.
In `@feature/archive/impl/src/main/res/drawable/icon_copy_photo.xml`:
- Around line 1-33: 아이콘 색상 불일치 문제: 이 파일의 벡터 요소들(그룹 내부의 <path>와 외부의 세 <path>
요소들에서 사용하는 android:strokeColor="#616575")이 같은 PR의 다른 아이콘(icon_move_photo.xml)이
사용하는 "#B7B9C3"과 다릅니다; 디자인 의도에 따라 일관되게 만들려면 해당 요소들의 android:strokeColor 값을 프로젝트
표준 색("#B7B9C3" 또는 지정된 디자인 색상)으로 변경하거나(또는 반대로 icon_move_photo.xml을 "#616575"로 수정)
디자이너/디자인 토큰을 확인해 결정하고 모든 관련 path 요소(android:strokeColor가 설정된 요소들)에서 동일한 색상으로
통일하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b858f24c-a94d-4bb9-8854-f5c3f9e97c07
📒 Files selected for processing (37)
app/src/main/java/com/neki/android/app/main/MainScreen.ktapp/src/main/java/com/neki/android/app/main/component/AddPhotoBottomSheet.ktcore/data-api/src/main/java/com/neki/android/core/dataapi/repository/FolderRepository.ktcore/data/src/main/java/com/neki/android/core/data/remote/api/FolderService.ktcore/data/src/main/java/com/neki/android/core/data/remote/model/request/CopyPhotosRequest.ktcore/data/src/main/java/com/neki/android/core/data/remote/model/request/MovePhotosRequest.ktcore/data/src/main/java/com/neki/android/core/data/repository/impl/FolderRepositoryImpl.ktcore/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.ktcore/designsystem/src/main/res/drawable/icon_copy_photo.xmlcore/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.ktfeature/archive/api/src/main/kotlin/com/neki/android/feature/archive/api/ArchiveResult.ktfeature/archive/impl/build.gradle.ktsfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailContract.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailActionBar.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/FavoriteAlbumActionBar.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/ImportPhotoBottomSheet.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/ImportPhotoGridItem.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/navigation/ArchiveEntryProvider.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoContract.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoViewModel.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoActionBar.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailContract.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.ktfeature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.ktfeature/archive/impl/src/main/res/drawable/icon_copy_photo.xmlfeature/archive/impl/src/main/res/drawable/icon_move_photo.xmlfeature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumAction.ktfeature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumNavKey.ktfeature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumContract.ktfeature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumScreen.ktfeature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt
| is AlbumDetailIntent.SelectImportAlbum -> { | ||
| reduce { copy(importPhotoState = importPhotoState.copy(selectedAlbumId = intent.albumId, isShowAlbumDropdown = false)) } | ||
| loadImportPhotos(intent.albumId, reduce) | ||
| } |
There was a problem hiding this comment.
가져올 앨범을 바꿔도 이전 선택이 유지됩니다.
Line 115에서는 selectedAlbumId만 바꾸고 있어서, 직전에 고른 selectedPhotoIds가 그대로 남습니다. 그 상태로 확인하면 Line 318이 이전 앨범의 사진 ID를 그대로 전송해서, 현재 보고 있는 앨범과 다른 사진이 가져와질 수 있습니다.
수정 예시
is AlbumDetailIntent.SelectImportAlbum -> {
- reduce { copy(importPhotoState = importPhotoState.copy(selectedAlbumId = intent.albumId, isShowAlbumDropdown = false)) }
+ reduce {
+ copy(
+ importPhotoState = importPhotoState.copy(
+ selectedAlbumId = intent.albumId,
+ isShowAlbumDropdown = false,
+ selectedPhotoIds = emptySet<Long>().toImmutableSet(),
+ ),
+ )
+ }
loadImportPhotos(intent.albumId, reduce)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| is AlbumDetailIntent.SelectImportAlbum -> { | |
| reduce { copy(importPhotoState = importPhotoState.copy(selectedAlbumId = intent.albumId, isShowAlbumDropdown = false)) } | |
| loadImportPhotos(intent.albumId, reduce) | |
| } | |
| is AlbumDetailIntent.SelectImportAlbum -> { | |
| reduce { | |
| copy( | |
| importPhotoState = importPhotoState.copy( | |
| selectedAlbumId = intent.albumId, | |
| isShowAlbumDropdown = false, | |
| selectedPhotoIds = emptySet<Long>().toImmutableSet(), | |
| ), | |
| ) | |
| } | |
| loadImportPhotos(intent.albumId, reduce) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt`
around lines 114 - 117, When handling AlbumDetailIntent.SelectImportAlbum in
AlbumDetailViewModel, you're only updating importPhotoState.selectedAlbumId but
not clearing previous importPhotoState.selectedPhotoIds (and related selection
flags), so previously selected photo IDs from another album are retained and
later sent by loadImportPhotos; fix by clearing
importPhotoState.selectedPhotoIds (and any isAllSelected/isSelectAll flags) in
the same reduce call that sets selectedAlbumId and isShowAlbumDropdown before
calling loadImportPhotos so selection state is reset whenever selectedAlbumId
changes.
| val options = buildList { | ||
| add(AlbumFilterOption(null, "전체사진", loadedAlbums.sumOf { it.photoCount })) | ||
| addAll(loadedAlbums.map { AlbumFilterOption(it.id, it.title, it.photoCount) }) |
There was a problem hiding this comment.
전체사진 개수가 중복 집계될 수 있습니다.
여기서는 이미 전체 사진 목록을 불러오고 있는데, 카운트를 loadedAlbums.sumOf { it.photoCount }로 만들면 같은 사진이 여러 앨범에 복제된 경우 실제 전체 사진 수보다 크게 표시됩니다.
수정 예시
private fun loadImportAlbumsAndPhotos(reduce: (AlbumDetailState.() -> AlbumDetailState) -> Unit) {
viewModelScope.launch {
var loadedAlbums: List<AlbumPreview> = emptyList()
+ var allPhotosCount = 0
awaitAll(
async { folderRepository.getFolders().onSuccess { albums -> loadedAlbums = albums } },
async {
photoRepository.getPhotos(folderId = null).onSuccess { photos ->
+ allPhotosCount = photos.size
reduce { copy(importPhotoState = importPhotoState.copy(photos = photos.toImmutableList())) }
}
},
)
val options = buildList {
- add(AlbumFilterOption(null, "전체사진", loadedAlbums.sumOf { it.photoCount }))
+ add(AlbumFilterOption(null, "전체사진", allPhotosCount))
addAll(loadedAlbums.map { AlbumFilterOption(it.id, it.title, it.photoCount) })
}.toImmutableList()
reduce { copy(importPhotoState = importPhotoState.copy(allAlbumOptions = options)) }
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val options = buildList { | |
| add(AlbumFilterOption(null, "전체사진", loadedAlbums.sumOf { it.photoCount })) | |
| addAll(loadedAlbums.map { AlbumFilterOption(it.id, it.title, it.photoCount) }) | |
| private fun loadImportAlbumsAndPhotos(reduce: (AlbumDetailState.() -> AlbumDetailState) -> Unit) { | |
| viewModelScope.launch { | |
| var loadedAlbums: List<AlbumPreview> = emptyList() | |
| var allPhotosCount = 0 | |
| awaitAll( | |
| async { folderRepository.getFolders().onSuccess { albums -> loadedAlbums = albums } }, | |
| async { | |
| photoRepository.getPhotos(folderId = null).onSuccess { photos -> | |
| allPhotosCount = photos.size | |
| reduce { copy(importPhotoState = importPhotoState.copy(photos = photos.toImmutableList())) } | |
| } | |
| }, | |
| ) | |
| val options = buildList { | |
| add(AlbumFilterOption(null, "전체사진", allPhotosCount)) | |
| addAll(loadedAlbums.map { AlbumFilterOption(it.id, it.title, it.photoCount) }) | |
| }.toImmutableList() | |
| reduce { copy(importPhotoState = importPhotoState.copy(allAlbumOptions = options)) } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt`
around lines 294 - 296, The "전체사진" count currently uses loadedAlbums.sumOf {
it.photoCount } which double-counts photos that appear in multiple albums;
change the calculation used when constructing AlbumFilterOption (the
AlbumFilterOption(..., loadedAlbums.sumOf { it.photoCount }) call) to count
unique photos instead by flattening loadedAlbums' photo lists and taking
distinct by photo id (or using an existing combined/allPhotos collection) and
using its size so the total reflects unique photos across albums.
| viewModelScope.launch { | ||
| reduce { copy(importPhotoState = importPhotoState.copy(isLoading = true)) } | ||
| folderRepository.copyPhotos( | ||
| photoIds = state.importPhotoState.selectedPhotoIds.toList(), | ||
| targetFolderIds = listOf(albumId), | ||
| ).onSuccess { | ||
| reduce { copy(isShowImportPhotoBottomSheet = false, importPhotoState = ImportPhotoState()) } | ||
| postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) | ||
| postSideEffect(AlbumDetailSideEffect.PhotoImported(albumId)) |
There was a problem hiding this comment.
선택된 사진이 없을 때는 가져오기 API를 막아주세요.
현재는 빈 selectedPhotoIds도 그대로 copyPhotos(...)로 전달됩니다. UI에서 버튼을 비활성화하더라도 ViewModel 쪽 방어가 없으면 불필요한 요청이나 애매한 실패 토스트가 발생할 수 있습니다.
수정 예시
private fun handleConfirmImport(
state: AlbumDetailState,
reduce: (AlbumDetailState.() -> AlbumDetailState) -> Unit,
postSideEffect: (AlbumDetailSideEffect) -> Unit,
) {
+ if (state.importPhotoState.selectedPhotoIds.isEmpty()) {
+ postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 선택해주세요."))
+ return
+ }
viewModelScope.launch {
reduce { copy(importPhotoState = importPhotoState.copy(isLoading = true)) }
folderRepository.copyPhotos(
photoIds = state.importPhotoState.selectedPhotoIds.toList(),
targetFolderIds = listOf(albumId),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| viewModelScope.launch { | |
| reduce { copy(importPhotoState = importPhotoState.copy(isLoading = true)) } | |
| folderRepository.copyPhotos( | |
| photoIds = state.importPhotoState.selectedPhotoIds.toList(), | |
| targetFolderIds = listOf(albumId), | |
| ).onSuccess { | |
| reduce { copy(isShowImportPhotoBottomSheet = false, importPhotoState = ImportPhotoState()) } | |
| postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) | |
| postSideEffect(AlbumDetailSideEffect.PhotoImported(albumId)) | |
| if (state.importPhotoState.selectedPhotoIds.isEmpty()) { | |
| postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 선택해주세요.")) | |
| return | |
| } | |
| viewModelScope.launch { | |
| reduce { copy(importPhotoState = importPhotoState.copy(isLoading = true)) } | |
| folderRepository.copyPhotos( | |
| photoIds = state.importPhotoState.selectedPhotoIds.toList(), | |
| targetFolderIds = listOf(albumId), | |
| ).onSuccess { | |
| reduce { copy(isShowImportPhotoBottomSheet = false, importPhotoState = ImportPhotoState()) } | |
| postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) | |
| postSideEffect(AlbumDetailSideEffect.PhotoImported(albumId)) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt`
around lines 315 - 323, Before calling folderRepository.copyPhotos in the
viewModelScope.launch block, guard against an empty selection: check
state.importPhotoState.selectedPhotoIds (used in the current block) and if
empty, stop the flow (reset importPhotoState.isLoading = false if set, close the
bottom sheet or keep it open as desired) and optionally post a side effect like
a toast or no-op; only call folderRepository.copyPhotos when
selectedPhotoIds.isNotEmpty(). Update the logic around viewModelScope.launch /
reduce and the copyPhotos invocation to perform this early-return check so no
request is made with an empty list.
| val sheetHeight = remember { | ||
| configuration.screenHeightDp.dp - statusBarHeight - navigationBarHeight | ||
| } |
There was a problem hiding this comment.
sheetHeight 계산이 구성 변경 시 업데이트되지 않을 수 있습니다.
remember로 감싸진 sheetHeight는 화면 회전이나 inset 변경 시 재계산되지 않습니다. configuration과 inset 값을 key로 사용하거나 매번 계산하도록 수정하세요.
🐛 수정 제안
- val sheetHeight = remember {
- configuration.screenHeightDp.dp - statusBarHeight - navigationBarHeight
- }
+ val sheetHeight = remember(configuration.screenHeightDp, statusBarHeight, navigationBarHeight) {
+ configuration.screenHeightDp.dp - statusBarHeight - navigationBarHeight
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val sheetHeight = remember { | |
| configuration.screenHeightDp.dp - statusBarHeight - navigationBarHeight | |
| } | |
| val sheetHeight = remember(configuration.screenHeightDp, statusBarHeight, navigationBarHeight) { | |
| configuration.screenHeightDp.dp - statusBarHeight - navigationBarHeight | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/ImportPhotoBottomSheet.kt`
around lines 71 - 73, The sheetHeight is stored with remember but not keyed to
changing inputs so it won't recompute on rotation or inset changes; update the
calculation of sheetHeight (the val sheetHeight using remember) to either
include configuration, statusBarHeight, and navigationBarHeight as remember keys
or remove remember and compute it directly each composition so that sheetHeight
recalculates when configuration or inset values change.
| action: SelectAlbumAction, | ||
| title: String = "모든 앨범", | ||
| multiSelect: Boolean = true, | ||
| multiSelect: Boolean = false, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 목적: navigateToSelectAlbum(...) 호출 중 multiSelect 인자를 생략한 케이스 탐지
python - <<'PY'
from pathlib import Path
def extract_call(text, start_idx):
i = start_idx
depth = 0
end = None
while i < len(text):
ch = text[i]
if ch == '(':
depth += 1
elif ch == ')':
depth -= 1
if depth == 0:
end = i
break
i += 1
return end
for p in Path(".").rglob("*.kt"):
try:
s = p.read_text(encoding="utf-8", errors="ignore")
except Exception:
continue
idx = 0
while True:
idx = s.find("navigateToSelectAlbum(", idx)
if idx == -1:
break
end = extract_call(s, idx + len("navigateToSelectAlbum"))
if end is None:
break
call = s[idx:end+1]
if "multiSelect" not in call:
line = s.count("\n", 0, idx) + 1
one_line = " ".join(call.split())
print(f"{p}:{line}")
print(f" {one_line[:220]}")
idx = end + 1
PYRepository: YAPP-Github/27th-App-Team-2-Android
Length of output: 733
🏁 Script executed:
# SelectAlbumNavKey.kt 파일 내용 확인
find . -name "SelectAlbumNavKey.kt" -type fRepository: YAPP-Github/27th-App-Team-2-Android
Length of output: 182
🏁 Script executed:
# 해당 파일의 내용 확인
cat -n $(find . -name "SelectAlbumNavKey.kt" -type f | head -1)Repository: YAPP-Github/27th-App-Team-2-Android
Length of output: 870
🏁 Script executed:
# 각 호출 위치에서의 context 확인
grep -B 2 -A 2 "navigateToSelectAlbum(action)" app/src/main/java/com/neki/android/app/MainActivity.kt | head -20Repository: YAPP-Github/27th-App-Team-2-Android
Length of output: 550
🏁 Script executed:
# 다른 호출 위치들도 확인
grep -B 2 -A 2 "navigateToSelectAlbum(sideEffect.action)" app/src/main/java/com/neki/android/app/main/MainScreen.ktRepository: YAPP-Github/27th-App-Team-2-Android
Length of output: 392
🏁 Script executed:
grep -B 2 -A 2 "navigateToSelectAlbum(sideEffect.photoIds)" feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.ktRepository: YAPP-Github/27th-App-Team-2-Android
Length of output: 267
🏁 Script executed:
grep -B 2 -A 2 "navigateToSelectAlbum(sideEffect.photoId)" feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.ktRepository: YAPP-Github/27th-App-Team-2-Android
Length of output: 341
기본값 변경으로 인한 5개 호출부 영향 확인 필요
multiSelect 기본값 변경이 5개 호출부에 영향을 미칩니다:
MainActivity.kt:126-navigateToSelectAlbum(action)MainScreen.kt:99-navigateToSelectAlbum(sideEffect.action)AlbumDetailScreen.kt:127-navigateToSelectAlbum(sideEffect.action)AllPhotoScreen.kt:126-navigateToSelectAlbum(sideEffect.photoIds)PhotoDetailScreen.kt:93-navigateToSelectAlbum(sideEffect.photoId)
이들 호출부는 명시적으로 multiSelect 인자를 지정하지 않으므로 기본값(false)을 사용합니다. 기본값이 변경되었다면 이 5개 화면의 동작이 의도치 않게 변경되었을 가능성이 있습니다. 이 변경이 의도된 것인지 확인 후, 필요시 호출부를 업데이트하거나 기본값을 원래대로 복원하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@feature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumNavKey.kt`
at line 20, The default value for SelectAlbumNavKey.multiSelect was changed and
that affects callers that rely on the implicit default; update either the
default back to its original value or explicitly pass the desired boolean at
each call site: locate SelectAlbumNavKey (constructor/property multiSelect) and
the navigateToSelectAlbum usages (the functions invoking navigateToSelectAlbum
in MainActivity, MainScreen, AlbumDetailScreen, AllPhotoScreen,
PhotoDetailScreen) and ensure each call explicitly supplies
multiSelect=true/false to preserve intended behavior, or revert the multiSelect
default to its previous value to avoid changing behavior across those five
callers.
🔗 관련 이슈
📙 작업 설명
🧪 테스트 내역 (선택)
📸 스크린샷 또는 시연 영상 (선택)
default.mp4
default.mp4
default.mp4
default.mp4
default.mp4
default.mp4
💬 추가 설명 or 리뷰 포인트 (선택)
SelectAlbumActionsealed interface로 upload/move/copy 액션을 통합 처리PhotoMovedResultResultBus 이벤트로 AlbumDetail 사진 목록 새로고침 트리거ModalBottomSheet+contentWindowInsets = { WindowInsets() }로 statusBar 아래 전체 높이 구현disabledAlbumId = sourceFolderId로 출처 앨범 UI/클릭 비활성화Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항