Skip to content

[feat] #210 포즈 상세 좌우 스와이프 지원#215

Merged
ikseong00 merged 6 commits into
developfrom
feat/#210-pose-detail-horizontal-swipe
Jun 22, 2026
Merged

[feat] #210 포즈 상세 좌우 스와이프 지원#215
ikseong00 merged 6 commits into
developfrom
feat/#210-pose-detail-horizontal-swipe

Conversation

@ikseong00

@ikseong00 ikseong00 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🔗 관련 이슈

📙 작업 설명

  • 포즈 상세에서 사진 상세과 같은 방식의 좌우 pager 스와이프를 지원합니다.
  • 포즈 목록 snapshot, 선택 index, 필터 조건, hasNext를 상세로 전달하고 상세에서 preload 및 추가 로드를 처리합니다.
  • 포즈 목록 API의 nullable width/height를 모델에 반영하고, 값이 있을 때 이미지 영역 비율을 먼저 잡아 레이아웃 시프트를 줄입니다.
  • 랜덤 포즈 화면의 포즈 상세 이동 버튼과 관련 navigation/effect를 제거합니다.

🧪 테스트 내역 (선택)

  • 주요 기능 정상 동작 확인
  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

검증 명령:

./gradlew :core:model:compileKotlin :core:data-api:compileDebugKotlin :core:data:compileDebugKotlin :feature:pose:api:compileDebugKotlin :feature:pose:impl:compileDebugKotlin :core:model:detekt :core:data-api:detekt :core:data:detekt :feature:pose:api:detekt :feature:pose:impl:detekt

📸 스크린샷 또는 시연 영상 (선택)

기능 미리보기 기능 미리보기
- - - -

💬 추가 설명 or 리뷰 포인트 (선택)

  • 사진 상세의 list snapshot 기반 pager/preload 구조를 포즈 상세에도 맞춰 적용했습니다.
  • 포즈 width/height는 nullable로 처리하며, 값이 없으면 기존처럼 이미지 로드 성공 후 intrinsic size를 사용합니다.
  • 상세 추가 로드 시 기존 snapshot과 추가 응답 간 동일 항목 중복 가능성은 사진/포즈 공통 follow-up 이슈로 분리했습니다: [fix] 사진/포즈 상세 스와이프 추가 로드 시 기존 항목 중복 노출 가능성 #214
  • 요청에 따라 테스트 코드는 PR 커밋에 포함하지 않았습니다.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 포즈 목록에 페이지네이션 기반 로딩 추가
    • 상세 화면에서 여러 포즈를 슬라이드로 탐색(페이징 연동)
  • 개선사항

    • 다음 페이지 여부를 hasNext 기준으로 더 안정적으로 처리
    • 상세 화면에서 포즈 단위 북마크 상태 반영 및 프리로드 개선
    • 상세 진입 시 선택 위치/정렬 조건 유지
  • UI 변경

    • 랜덤 포즈의 “상세 보기” 버튼 제거
    • 포즈 이미지 가로/세로 크기 정보 반영하여 비율 표시 개선

@ikseong00 ikseong00 added the feat label Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6102fe51-1195-452b-8e39-3a2375a0a701

📥 Commits

Reviewing files that changed from the base of the PR and between c5601d7 and 77e3e62.

📒 Files selected for processing (1)
  • feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt

📝 Walkthrough

Walkthrough

PosePage 도메인 모델과 Repository 페이징 API를 추가하고, PoseNavKey.PoseDetail을 포즈 목록·인덱스·정렬·북마크 여부를 포함하는 구조로 확장했습니다. 포즈 상세 화면은 단일 이미지에서 HorizontalPager 기반 좌우 스와이프로 전환되고, 뷰모델에 프리로드 로직이 추가되었습니다. RandomPose 화면의 상세 이동 버튼은 제거되었으며, PhotoDetailViewModel의 onCleared 동기화도 다중 항목 처리로 확장됩니다.

Changes

포즈 상세 좌우 스와이프 및 페이징 지원

Layer / File(s) Summary
PosePage 모델 및 Pose 크기 필드 nullable 전환
core/model/.../PosePage.kt, core/model/.../Pose.kt, core/data/.../PoseResponse.kt
PosePage(poses, hasNext) 데이터 클래스 신규 추가. Pose.width/heightInt?로 변경. PoseResponse.Itemwidth/height 직렬화 필드 추가 및 toModel() 확장.
PosePage Repository 인터페이스 및 구현
core/data-api/.../PoseRepository.kt, core/data/.../PoseRepositoryImpl.kt, core/data/.../PosePagingSource.kt, core/data/.../BookmarkPosePagingSource.kt
getPosesPage/getBookmarkedPosesPage suspend 메서드 추가 및 구현. 기존 PagingSource의 nextKey 계산을 poses.isEmpty() 대신 response.data.hasNext 기반으로 변경.
PoseNavKey.PoseDetail 확장 및 네비게이터 시그니처 변경
feature/pose/api/.../PoseNavKey.kt
PoseNavKey.PoseDetailposes, initialIndex, hasNext, headCount, sortOrder, isBookmarkOnly를 포함하는 다중 필드 구조로 확장. MainNavigator.navigateToPoseDetail 파라미터를 PoseNavKey.PoseDetail로 변경.
PoseDetailState/Intent 다중 포즈 구조로 재설계
feature/pose/impl/.../detail/PoseDetailContract.kt
PoseDetailStateposes 리스트·currentPage·committedBookmarks 맵 기반으로 재구성. currentIndex/pose를 getter로 파생. PageChanged, poseId 포함 BookmarkCommitted/RevertBookmark 인텐트 추가/변경.
PoseDetailViewModel 다중 포즈 처리 및 프리로드 로직
feature/pose/impl/.../detail/PoseDetailViewModel.kt
생성자를 PoseNavKey.PoseDetail(key) 기반으로 전환. 북마크 토글을 (poseId, newBookmark) 쌍으로 처리. preloadIfNeeded/loadMorePoses 추가로 스와이프 시 다음 페이지 프리로드 구현. onCleared에서 전체 poses 순회 후 불일치 항목만 저장소 업데이트.
PoseDetailScreen HorizontalPager 전환 및 PoseDetailImageItem 추가
feature/pose/impl/.../detail/PoseDetailScreen.kt, feature/pose/impl/.../detail/component/PoseDetailImageItem.kt
PoseDetailImageItem 컴포저블 신규 추가(AsyncImage + zoomable). PoseDetailScreenHorizontalPager 도입, LaunchedEffectsettledPagePageChanged 인텐트 동기화.
포즈 목록 화면 인덱스 전달 및 네비게이션 키 구성
feature/pose/impl/.../main/PoseContract.kt, feature/pose/impl/.../main/PoseScreen.kt, feature/pose/impl/.../main/PoseViewModel.kt, feature/pose/impl/.../main/component/PoseListContent.kt, feature/pose/impl/.../navigation/PoseEntryProvider.kt
ClickPoseItemindex 추가, NavigateToPoseDetail payload를 PoseNavKey.PoseDetail로 변경. PoseListContentonClickItem(Pose, Int) → Unit으로 확장, append 로딩 인디케이터 추가. PoseItem aspectRatio를 서버 크기 정보 우선 적용으로 변경. PoseScreen에서 posePagingItems 스냅샷과 hasNextPoseDetail에 주입.
RandomPose 상세 이동 기능 제거
feature/pose/impl/.../random/RandomPoseContract.kt, feature/pose/impl/.../random/RandomPoseScreen.kt, feature/pose/impl/.../random/RandomPoseViewModel.kt, feature/pose/impl/.../random/component/RandomPoseFloatingBar.kt
ClickGoToDetailIcon 인텐트, NavigateToDetail 이펙트, 대응 뷰모델 분기, FloatingBar의 화살표 버튼 UI를 일괄 제거.

사진 상세 즐겨찾기 동기화 다중 항목 처리 확장

Layer / File(s) Summary
PhotoDetailViewModel onCleared 다중 사진 순회 처리
feature/archive/impl/.../photo_detail/PhotoDetailViewModel.kt
onCleared()에서 단일 state.photo 기준 비교 대신 state.photos 리스트 전체 순회, committedFavoritesisFavorite 불일치 항목만 비동기 updateFavorite 호출.

Sequence Diagram(s)

sequenceDiagram
  participant 사용자
  participant PoseScreen
  participant PoseViewModel
  participant PoseDetailScreen
  participant PoseDetailViewModel
  participant PoseRepository

  사용자->>PoseScreen: 포즈 아이템 클릭 (pose, index)
  PoseScreen->>PoseViewModel: ClickPoseItem(pose, index)
  PoseViewModel->>PoseScreen: NavigateToPoseDetail(PoseNavKey.PoseDetail)
  Note over PoseScreen: poses = 페이징 스냅샷, hasNext = append 상태
  PoseScreen->>PoseDetailScreen: navigate(PoseNavKey.PoseDetail)
  PoseDetailScreen->>PoseDetailViewModel: create(key) → 초기 poses/index/북마크 맵 구성
  PoseDetailViewModel-->>PoseDetailScreen: uiState(poses, currentPage)

  사용자->>PoseDetailScreen: 좌우 스와이프
  PoseDetailScreen->>PoseDetailViewModel: PageChanged(page)
  PoseDetailViewModel->>PoseDetailViewModel: preloadIfNeeded(currentIndex)
  PoseDetailViewModel->>PoseRepository: getPosesPage(nextPage) 또는 getBookmarkedPosesPage(nextPage)
  PoseRepository-->>PoseDetailViewModel: Result~PosePage~(poses, hasNext)
  PoseDetailViewModel-->>PoseDetailScreen: uiState 업데이트(poses 누적)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 토끼가 화면을 왼쪽으로 쓱~
포즈가 줄줄이 나타나네
hasNext가 참이면 더 불러오고
committedBookmarks 맵에 꾹꾹 저장
RandomPose 화살표는 이제 안녕~
스와이프 한 번에 세상이 넓어졌다 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 포즈 상세에서 좌우 스와이프 지원이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed 작업 설명, 테스트 내역, 추가 설명이 완전하게 제공되어 PR의 의도와 범위가 명확합니다.
Linked Issues check ✅ Passed 포즈 상세 진입 시 목록과 선택 index 전달, 좌우 스와이프 구현, 북마크 동작 유지가 모두 충족되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 포즈 상세 스와이프 구현과 관련 리팩토링 범위 내에 있으며, 범위를 벗어난 변경은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#210-pose-detail-horizontal-swipe

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/pose/api/src/main/java/com/neki/android/feature/pose/api/PoseNavKey.kt`:
- Around line 19-27: The PoseDetail data class in PoseNavKey contains a full
poses list that gets serialized during navigation, causing
TransactionTooLargeException risks when handling large datasets. Remove the
poses property from the PoseDetail class and instead implement a shared cache
mechanism (such as a repository or in-memory cache) to store pose snapshots
keyed by poseId. Update PoseDetailViewModel initialization to retrieve poses
from the cache rather than from the navigation arguments, keeping only the
essential context parameters in PoseDetail (poseId, initialIndex, hasNext,
headCount, sortOrder, isBookmarkOnly) for the navigation payload.

In
`@feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt`:
- Around line 34-35: The nextPage calculation in PoseDetailViewModel using floor
division (key.poses.size / PAGE_SIZE) can cause re-requesting already-loaded
pages if the initial snapshot size is not a multiple of PAGE_SIZE, and the
append logic around line 181 lacks duplicate removal, causing duplicates to
accumulate in the list. Fix the nextPage calculation to use ceiling division to
correctly calculate the starting page for pagination, and add duplicate removal
logic when appending new poses to ensure only new items are added to the list.

In
`@feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt`:
- Around line 102-107: The aspectRatio calculation in the PoseListContent
component only validates that width and height are not null, but does not check
if they are valid positive values before performing the division. Add an
additional validation to ensure that both pose.width and pose.height are greater
than 0 in the hasSize condition, so that when creating the mutableFloatStateOf
for aspectRatio, you only perform the division if height is a valid positive
number. Otherwise, default to 0f. Apply this same fix to both occurrences of
aspectRatio calculation (the first one around lines 102-107 and the second one
around lines 125-129).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a07240f4-7492-48bc-aa50-eb074f9895f9

📥 Commits

Reviewing files that changed from the base of the PR and between 12a74e6 and 86a47c9.

📒 Files selected for processing (21)
  • core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt
  • core/data/src/main/java/com/neki/android/core/data/paging/BookmarkPosePagingSource.kt
  • core/data/src/main/java/com/neki/android/core/data/paging/PosePagingSource.kt
  • core/data/src/main/java/com/neki/android/core/data/remote/model/response/PoseResponse.kt
  • core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt
  • core/model/src/main/java/com/neki/android/core/model/Pose.kt
  • core/model/src/main/java/com/neki/android/core/model/PosePage.kt
  • feature/pose/api/src/main/java/com/neki/android/feature/pose/api/PoseNavKey.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailContract.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailScreen.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/component/PoseDetailImageItem.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseContract.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseScreen.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/navigation/PoseEntryProvider.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseScreen.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseFloatingBar.kt
💤 Files with no reviewable changes (4)
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseFloatingBar.kt
  • feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseScreen.kt

Comment on lines +19 to +27
data class PoseDetail(
val poseId: Long,
val poses: List<Pose> = emptyList(),
val initialIndex: Int = 0,
val hasNext: Boolean = false,
val headCount: PeopleCount? = null,
val sortOrder: SortOrder = SortOrder.DESC,
val isBookmarkOnly: Boolean = false,
) : PoseNavKey

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# PoseDetail 키 생성 지점 확인
rg -n --type=kotlin -C3 'PoseNavKey\.PoseDetail\('

# 목록 스냅샷/리스트가 payload로 주입되는 경로 확인
rg -n --type=kotlin -C5 'poses\s*=|itemSnapshotList|snapshot' \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseScreen.kt \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt

# 전체 키 객체를 navigate로 전달하는 경로 확인
rg -n --type=kotlin -C3 \
  'fun MainNavigator\.navigateToPoseDetail\(poseDetail: PoseNavKey\.PoseDetail\)|navigate\(poseDetail\)'

Repository: Team-Neki/Team-Neki-Android

Length of output: 6460


🏁 Script executed:

# Get full PoseDetail class definition
cat -n feature/pose/api/src/main/java/com/neki/android/feature/pose/api/PoseNavKey.kt | head -50

Repository: Team-Neki/Team-Neki-Android

Length of output: 1633


🏁 Script executed:

# Check how poses field is used in the destination screen (PoseDetailScreen)
rg -n --type=kotlin 'poseDetail\.poses|navKey\.poses' \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/

Repository: Team-Neki/Team-Neki-Android

Length of output: 53


🏁 Script executed:

# Look for any size checks or serialization guards
rg -n --type=kotlin 'TransactionTooLarge|Parcelable|Bundle' feature/pose/

Repository: Team-Neki/Team-Neki-Android

Length of output: 53


🏁 Script executed:

# Verify if poses is actually needed during navigation or if it's just passed through
rg -n --type=kotlin 'poses\.size|poses\.isEmpty|poses\[' \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/ \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/

Repository: Team-Neki/Team-Neki-Android

Length of output: 1323


🏁 Script executed:

# Check PoseDetailViewModel initialization to see where it gets the poses from the nav key
rg -n --type=kotlin -B5 -A10 'class PoseDetailViewModel|constructor.*key.*PoseNavKey' \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt

Repository: Team-Neki/Team-Neki-Android

Length of output: 833


🏁 Script executed:

# Look for the initialState or reduce logic to see if poses from key are used in state
rg -n --type=kotlin -B3 -A10 'reduce|initialState|copy.*poses' \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt | head -80

Repository: Team-Neki/Team-Neki-Android

Length of output: 3599


🏁 Script executed:

# Check if there's any data loading from DB/API in PoseDetailViewModel
rg -n --type=kotlin 'getPoseDetail|fetchPose|observePose' \
  feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/

Repository: Team-Neki/Team-Neki-Android

Length of output: 403


PoseNavKey에 전체 poses 리스트를 싣는 방식은 네비게이션 실패를 유발할 수 있습니다.

Line 21의 poses: List<Pose>를 직렬화하여 Line 74에서 posePagingItems.itemSnapshotList.items로 채운 후 Line 43에서 전달하면, 로드된 아이템이 많은 상태에서 payload가 커져 TransactionTooLargeException 또는 상태 복원 실패 리스크가 큽니다.

PoseDetailViewModel에서 초기화 시(Line 45) 이 poses 리스트를 직접 사용하며, Line 47의 bookmark 매핑과 Line 34의 페이지네이션 계산도 이에 의존하므로, 구조적 개선이 필요합니다. 네비게이션 키에는 최소 컨텍스트(poseId, initialIndex, 필터/정렬, hasNext)만 두고, 목록 스냅샷은 공유 메모리 캐시(키 기반)로 분리하는 것이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/pose/api/src/main/java/com/neki/android/feature/pose/api/PoseNavKey.kt`
around lines 19 - 27, The PoseDetail data class in PoseNavKey contains a full
poses list that gets serialized during navigation, causing
TransactionTooLargeException risks when handling large datasets. Remove the
poses property from the PoseDetail class and instead implement a shared cache
mechanism (such as a repository or in-memory cache) to store pose snapshots
keyed by poseId. Update PoseDetailViewModel initialization to retrieve poses
from the cache rather than from the navigation arguments, keeping only the
essential context parameters in PoseDetail (poseId, initialIndex, hasNext,
headCount, sortOrder, isBookmarkOnly) for the navigation payload.

Comment on lines +34 to +35
private var nextPage: Int = key.poses.size / PAGE_SIZE
private var isLoadingMore: Boolean = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

다음 페이지 계산/append 로직 때문에 중복 포즈가 누적될 수 있습니다.

Line 34의 key.poses.size / PAGE_SIZE는 내림 계산이라 초기 스냅샷이 페이지 크기의 배수가 아니면 이미 포함된 페이지를 재요청할 수 있고, Line 181에서 중복 제거 없이 append되어 목록이 중복됩니다.

수정 예시
-    private var nextPage: Int = key.poses.size / PAGE_SIZE
+    private var nextPage: Int = if (key.poses.isEmpty()) 0 else (key.poses.size + PAGE_SIZE - 1) / PAGE_SIZE
...
-                    .onSuccess { page ->
-                        reduce {
-                            copy(
-                                poses = poses + page.poses,
-                                committedBookmarks = committedBookmarks + page.poses.associate { it.id to it.isBookmarked },
-                            )
-                        }
+                    .onSuccess { page ->
+                        reduce {
+                            val existingIds = poses.asSequence().map { it.id }.toSet()
+                            val appended = page.poses.filterNot { it.id in existingIds }
+                            copy(
+                                poses = poses + appended,
+                                committedBookmarks = committedBookmarks + appended.associate { it.id to it.isBookmarked },
+                            )
+                        }
                         hasNext = page.hasNext
                         nextPage++
                     }

Also applies to: 179-187

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt`
around lines 34 - 35, The nextPage calculation in PoseDetailViewModel using
floor division (key.poses.size / PAGE_SIZE) can cause re-requesting
already-loaded pages if the initial snapshot size is not a multiple of
PAGE_SIZE, and the append logic around line 181 lacks duplicate removal, causing
duplicates to accumulate in the list. Fix the nextPage calculation to use
ceiling division to correctly calculate the starting page for pagination, and
add duplicate removal logic when appending new poses to ensure only new items
are added to the list.

Comment on lines +102 to +107
val hasSize = pose.width != null && pose.height != null
var aspectRatio by if (hasSize) {
remember { mutableFloatStateOf(pose.width!! / pose.height!!.toFloat()) }
} else {
rememberSaveable { mutableFloatStateOf(0f) }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

서버 width/height가 0 이하일 때 aspectRatio 계산이 깨질 수 있습니다.

Line 104는 null만 확인하고 나눗셈을 수행합니다. height <= 0 데이터가 오면 비정상 ratio가 만들어져 Modifier.aspectRatio(...)에서 런타임 오류를 유발할 수 있습니다.

수정 예시
-    val hasSize = pose.width != null && pose.height != null
-    var aspectRatio by if (hasSize) {
-        remember { mutableFloatStateOf(pose.width!! / pose.height!!.toFloat()) }
+    val hasValidSize = (pose.width ?: 0) > 0 && (pose.height ?: 0) > 0
+    var aspectRatio by if (hasValidSize) {
+        remember(pose.width, pose.height) { mutableFloatStateOf(pose.width!!.toFloat() / pose.height!!.toFloat()) }
     } else {
         rememberSaveable { mutableFloatStateOf(0f) }
     }
...
-                if (!hasSize) {
+                if (!hasValidSize) {
                     val size = state.painter.intrinsicSize
                     if (size.width > 0f && size.height > 0f) {
                         aspectRatio = size.width / size.height
                     }
                 }

Also applies to: 125-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt`
around lines 102 - 107, The aspectRatio calculation in the PoseListContent
component only validates that width and height are not null, but does not check
if they are valid positive values before performing the division. Add an
additional validation to ensure that both pose.width and pose.height are greater
than 0 in the hasSize condition, so that when creating the mutableFloatStateOf
for aspectRatio, you only perform the division if height is a valid positive
number. Otherwise, default to 0f. Apply this same fix to both occurrences of
aspectRatio calculation (the first one around lines 102-107 and the second one
around lines 125-129).

@Ojongseok Ojongseok left a comment

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.

고생하셨습니다! 전체적으로 좋아보입니닷

한 가지 제안하고 싶은 부분은 포즈 - 포즈 상세의 스와이프 로직을 아카이빙 - 사진 상세의 스와이프 로직과 동일하게 가져가는게 어떨까요?

예를 들어, 좋아요/북마크 상태가 PhotoDetailViewModel에서는 committedFavorites: MutableMap ViewModel에서 관리되고 있고, PoseDetailViewModel에서는 committedBookmarks: MapPoseDetailState에서 관리되고 있습니다!

각 뷰모델의 onCleared() 함수 내에서 현재 사진의 commited 상태만 보는지, 전체 리스트의 상태를 순회하는지의 차이도 있구요!

@ikseong00
ikseong00 merged commit a698d69 into develop Jun 22, 2026
2 checks passed
@Ojongseok
Ojongseok deleted the feat/#210-pose-detail-horizontal-swipe branch June 26, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 포즈 상세 좌우 스와이프 기능 추가

2 participants