Conversation
- 작품 연결이 되어있지 않는 글을 뜨지 않게 하기 위해 null 허용으로 전환
- 지금 뜨는 글 피드 슬롯 구성을 3개에서 2개로 변경 - 피드 상세 정보(제목, 썸네일, 장르) 표시를 위한 UI 레이아웃 대폭 수정 - `HomeViewModel`에서 피드 상세 정보를 함께 조회하도록 데이터 가공 로직 추가 - `PopularFeedsViewHolder`에서 썸네일(Coil 사용) 및 제목 생략 처리 등 바인딩 로직 고도화 - `FeedRepository`에 단일 피드 조회(`fetchFeed`) 메서드 추가 - `PopularFeedsAdapter`의 리스트 비교(`DiffUtil`) 로직 수정
- `HomeViewModel`에 있던 인기 피드 가공 로직(`toHomePopularFeeds`)을 `FeedRepository`의 `fetchHomePopularFeeds` 메서드로 이관 - `FeedRepository`에서 인기 피드 목록 조회 시 각 피드의 상세 정보를 병렬로 호출하여 데이터(내용, 스포일러 여부, 작품 정보 등)를 보충하도록 수정 - 공개 상태이며 작품 제목과 이미지가 유효한 피드만 필터링하고 페이지 사이즈(2개)에 맞춰 청킹(Chunk) 처리하는 로직 추가 - `HomeViewModel`의 데이터 로딩 로직을 `runCatching`과 `async/await`을 활용해 예외 처리 및 가독성 개선 - 공통 에러 핸들링을 위한 `handleFailureState` 메서드 추출 및 적용
워크스루API/엔티티 확장, DTO→Entity 매핑 기본값 적용, 저장소의 인기 피드 상세 병렬 조회 추가, 뷰모델 동시성 리팩터링, 2-슬롯 UI 및 레이아웃·이미지 로딩 변경을 포함한 홈 인기 피드 기능 전체 경로 변경. 변경 사항홈 화면 지금 뜨는 글 기능 구현
예상 코드 리뷰 시간🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
제안 레이블
제안 검토자
🚥 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
app/src/main/res/layout/item_popular_feed_slot.xml (2)
64-64: 💤 Low value썸네일
marginStart가 의도한 간격인지 확인하세요.Line 64의
android:layout_marginStart="20dp"는FrameLayout에 적용되어 있지만, 제목/본문 텍스트와 썸네일 사이의 간격을 제어합니다. 만약 제목을 제약 기반 레이아웃(0dp)으로 변경한다면, 제목의marginEnd로 이 간격을 관리하는 것이 더 명확합니다.🤖 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 `@app/src/main/res/layout/item_popular_feed_slot.xml` at line 64, 썸네일의 간격 제어가 현재 FrameLayout의 android:layout_marginStart="20dp"에 의해 이뤄지고 있는데, 제목(TextView 등)이 제약 기반으로 변경되어 width=0dp로 동작할 경우 간격 관리는 제목의 marginEnd로 하는 것이 명확합니다; item_popular_feed_slot.xml에서 썸네일을 감싼 FrameLayout(썸네일 뷰)에서 layout_marginStart를 제거하고 대신 제목을 정의한 TextView(혹은 제목 관련 뷰)의 android:layout_marginEnd="20dp"로 동일한 간격을 적용하도록 변경하세요.
18-18: ⚡ Quick win고정 너비(195dp) 대신 제약 기반 레이아웃 사용을 고려하세요.
제목, 본문, 스포일러 텍스트에 하드코딩된
195dp너비는 다양한 화면 크기에서 반응형으로 동작하지 않습니다.0dp(match_constraint)와layout_constraintEnd_toStartOf를 사용하면 썸네일과의 간격을 유지하면서 화면 크기에 맞게 조정됩니다.♻️ 제약 기반 레이아웃 제안
<TextView android:id="@+id/tv_popular_feed_title" - android:layout_width="195dp" + android:layout_width="0dp" + android:layout_marginEnd="20dp" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" android:textAppearance="`@style/title3`" android:textColor="`@color/black`" app:layout_constraintEnd_toStartOf="`@id/fl_popular_feed_thumbnail`" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintHorizontal_bias="0" tools:text="피폐 역하렘 남주들의 막내 처제가..." /> <TextView android:id="@+id/tv_popular_feed_content" - android:layout_width="195dp" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:ellipsize="end" android:maxLines="3" android:textAppearance="`@style/body5`" android:textColor="`@color/black`" app:layout_constraintEnd_toEndOf="`@id/tv_popular_feed_title`" app:layout_constraintStart_toStartOf="`@id/tv_popular_feed_title`" app:layout_constraintTop_toBottomOf="`@id/tv_popular_feed_title`" tools:text="이름이 나여주입니다ㅋㅋㅋ읽던 소설이 세계멸망엔딩나서 댓글달았다가 그 세계의 본인에게 빙의하게 되었는데 S급 힐..." /> <TextView android:id="@+id/tv_popular_feed_content_spoiler" - android:layout_width="195dp" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:ellipsize="end" android:maxLines="1" android:text="`@string/home_popular_feed_spoiler`" android:textAppearance="`@style/body5`" android:textColor="`@color/secondary_100_FF675D`" android:visibility="gone" app:layout_constraintEnd_toEndOf="`@id/tv_popular_feed_title`" app:layout_constraintStart_toStartOf="`@id/tv_popular_feed_title`" app:layout_constraintTop_toBottomOf="`@id/tv_popular_feed_title`" tools:visibility="visible" />참고: 제목에서
layout_constraintHorizontal_bias="0"도 함께 제거했습니다(start 제약만 있을 때 불필요).Also applies to: 32-32, 46-46
🤖 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 `@app/src/main/res/layout/item_popular_feed_slot.xml` at line 18, Replace the fixed width (android:layout_width="195dp") on the feed slot text views with a constraint-based width so the items are responsive: set android:layout_width="0dp" (match_constraint) for the title/body/spoiler TextViews and add a layout_constraintEnd_toStartOf pointing to the thumbnail ImageView (and keep their start constraint to the parent or other anchor), and remove any unnecessary layout_constraintHorizontal_bias="0" on the title; apply the same change for the other occurrences (the identical fixed-width attributes at the other two locations noted in the comment).
🤖 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 `@app/src/main/java/com/into/websoso/data/mapper/FeedMapper.kt`:
- Line 118: The mapping in FeedMapper (the line setting isPublic = feed.isPublic
?: true) uses a default of true which may expose private content; change the
default to false so that when feed.isPublic is null it is treated as non-public.
Locate the mapping method in FeedMapper (the function that constructs the mapped
Feed/DTO and assigns isPublic) and replace the null-coalescing default from true
to false, ensuring all callers still expect a non-null Boolean afterward.
In `@app/src/main/java/com/into/websoso/ui/main/home/HomeViewModel.kt`:
- Around line 147-151: The success-state updates using _uiState.value =
uiState.value?.copy(... popularNovels = popularNovels.popularNovels,
popularFeeds = popularFeeds) (and the similar update at the second success
branch around the other copy at lines noted) do not reset the error flag; modify
both uiState.value?.copy calls to include error = false so a prior error state
is cleared when successful data arrives (update both occurrences where
popularNovels/popularFeeds are assigned).
In `@app/src/main/res/layout/item_popular_feed.xml`:
- Line 36: slot2의 top 제약(layout_constraintTop_toBottomOf)이 현재
`@id/item_popular_fees_slot1로` 되어 있어 divider_1(`@id/divider_1`)이 slot1 바로 아래로 배치된
설정과 충돌합니다; slot2의 top 제약을 divider_1의 아래로 변경하여 중첩을 제거하세요 — 즉
item_popular_feed.xml에서 slot2의 layout_constraintTop_toBottomOf 속성값을
`@id/item_popular_divider_1`(또는 실제 divider_1 ID)로 바꿔 divider_1과 slot1 사이의 순서를
보장하도록 수정하세요.
---
Nitpick comments:
In `@app/src/main/res/layout/item_popular_feed_slot.xml`:
- Line 64: 썸네일의 간격 제어가 현재 FrameLayout의 android:layout_marginStart="20dp"에 의해
이뤄지고 있는데, 제목(TextView 등)이 제약 기반으로 변경되어 width=0dp로 동작할 경우 간격 관리는 제목의 marginEnd로
하는 것이 명확합니다; item_popular_feed_slot.xml에서 썸네일을 감싼 FrameLayout(썸네일 뷰)에서
layout_marginStart를 제거하고 대신 제목을 정의한 TextView(혹은 제목 관련 뷰)의
android:layout_marginEnd="20dp"로 동일한 간격을 적용하도록 변경하세요.
- Line 18: Replace the fixed width (android:layout_width="195dp") on the feed
slot text views with a constraint-based width so the items are responsive: set
android:layout_width="0dp" (match_constraint) for the title/body/spoiler
TextViews and add a layout_constraintEnd_toStartOf pointing to the thumbnail
ImageView (and keep their start constraint to the parent or other anchor), and
remove any unnecessary layout_constraintHorizontal_bias="0" on the title; apply
the same change for the other occurrences (the identical fixed-width attributes
at the other two locations noted in the comment).
🪄 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: CHILL
Plan: Pro
Run ID: 992b4eed-8a16-4f50-861f-b60934dfcc8d
📒 Files selected for processing (10)
app/src/main/java/com/into/websoso/data/mapper/FeedMapper.ktapp/src/main/java/com/into/websoso/data/model/PopularFeedsEntity.ktapp/src/main/java/com/into/websoso/data/remote/response/PopularFeedsResponseDto.ktapp/src/main/java/com/into/websoso/data/repository/FeedRepository.ktapp/src/main/java/com/into/websoso/ui/main/home/HomeViewModel.ktapp/src/main/java/com/into/websoso/ui/main/home/adpater/PopularFeedsAdapter.ktapp/src/main/java/com/into/websoso/ui/main/home/adpater/PopularFeedsViewHolder.ktapp/src/main/res/layout/fragment_home.xmlapp/src/main/res/layout/item_popular_feed.xmlapp/src/main/res/layout/item_popular_feed_slot.xml
- `FeedRepository`의 `fetchHomePopularFeeds` 메서드명을 `fetchPopularFeedsWithDetails`로 변경 - `HomeViewModel`에서 변경된 메서드명을 참조하도록 수정 및 반영
- `FeedRepository`에서 수행하던 인기 피드 리스트 chunk(페이징) 처리를 `HomeViewModel`로 이동 - `FeedRepository.fetchPopularFeedsWithDetails`의 반환 타입을 `List<List<...>>`에서 `List<...>`로 변경 - `HomeViewModel`에 `HOME_POPULAR_FEED_PAGE_SIZE` 상수 추가 및 UI 상태 업데이트 시 데이터 가공 로직 반영
- `FeedMapper`에서 `isPublic` 필드의 기본값을 `true`에서 `false`로 변경
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (1)
46-61:⚠️ Potential issue | 🟠 Major | ⚡ Quick win상세 조회 전체 실패를 성공으로 삼키지 마세요.
여기서 상세 API 예외를 전부
null로 바꾸면, 원본 인기 피드가 있어도fetchPopularFeedsWithDetails()가 빈 리스트로 정상 반환될 수 있습니다. 그러면HomeViewModel의 실패 처리 경로가 타지 않아 홈 화면에서 섹션이 조용히 사라집니다. 최소한 원본 목록이 비어 있지 않은데 상세 조회가 전부 실패한 경우에는 예외를 다시 올리거나, 실패 신호를 함께 반환하도록 바꿔주세요.🤖 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 `@app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt` around lines 46 - 61, The current mapping in fetchPopularFeedsWithDetails() uses async + runCatching { feedApi.getFeed(feed.feedId)... }.getOrNull(), which swallows all exceptions and can silently return an empty list; instead return success/failure info per item (e.g., Result<Feed> or a sealed wrapper) from each async so you can detect global failure; after awaitAll examine the collected results and if the input popular feeds list was non-empty but all detail lookups failed, rethrow an appropriate exception (or return a failure result) rather than returning an empty list; reference feedApi.getFeed(feed.feedId) and fetchPopularFeedsWithDetails() when making the change.app/src/main/java/com/into/websoso/ui/main/home/HomeViewModel.kt (1)
73-90:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCancellationException을 runCatching으로 잡아 error UI로 전환될 수 있습니다.
fetchUserHomeData()/fetchGuestData()의async { runCatching { ... } }가CancellationException까지Result로 감싸고,exceptionOrNull() != null이면handleFailureState()로 가서error = true를 설정합니다(취소는 취소로 전파되어야 합니다).updateFeed()/updateNovel()도 동일 패턴이라 함께 개선 필요합니다.수정 방향
+import kotlinx.coroutines.CancellationException + +private suspend inline fun <T> suspendResultOf( + crossinline block: suspend () -> T, +): Result<T> = + try { + Result.success(block()) + } catch (e: CancellationException) { + throw e + } catch (t: Throwable) { + Result.failure(t) + } + - val popularFeedsDeferred = - async { runCatching { feedRepository.fetchPopularFeedsWithDetails() } } + val popularFeedsDeferred = + async { suspendResultOf { feedRepository.fetchPopularFeedsWithDetails() } }🤖 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 `@app/src/main/java/com/into/websoso/ui/main/home/HomeViewModel.kt` around lines 73 - 90, The current async { runCatching { ... } } pattern in fetchUserHomeData()/fetchGuestData() (and similarly in updateFeed()/updateNovel()) is wrapping CancellationException into a Result and causing cancelled coroutines to be treated as failures; change each async block to explicitly rethrow CancellationException before wrapping other exceptions (e.g. replace async { runCatching { ... } } with async { try { Result.success(...) } catch (e: CancellationException) { throw e } catch (t: Throwable) { Result.failure(t) } } ) so cancellations propagate, and keep the subsequent failure check (exceptionOrNull) unchanged.
🤖 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.
Outside diff comments:
In `@app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt`:
- Around line 46-61: The current mapping in fetchPopularFeedsWithDetails() uses
async + runCatching { feedApi.getFeed(feed.feedId)... }.getOrNull(), which
swallows all exceptions and can silently return an empty list; instead return
success/failure info per item (e.g., Result<Feed> or a sealed wrapper) from each
async so you can detect global failure; after awaitAll examine the collected
results and if the input popular feeds list was non-empty but all detail lookups
failed, rethrow an appropriate exception (or return a failure result) rather
than returning an empty list; reference feedApi.getFeed(feed.feedId) and
fetchPopularFeedsWithDetails() when making the change.
In `@app/src/main/java/com/into/websoso/ui/main/home/HomeViewModel.kt`:
- Around line 73-90: The current async { runCatching { ... } } pattern in
fetchUserHomeData()/fetchGuestData() (and similarly in
updateFeed()/updateNovel()) is wrapping CancellationException into a Result and
causing cancelled coroutines to be treated as failures; change each async block
to explicitly rethrow CancellationException before wrapping other exceptions
(e.g. replace async { runCatching { ... } } with async { try {
Result.success(...) } catch (e: CancellationException) { throw e } catch (t:
Throwable) { Result.failure(t) } } ) so cancellations propagate, and keep the
subsequent failure check (exceptionOrNull) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8110d88b-2f7d-4aa6-9c6c-d1259f68f5d9
📒 Files selected for processing (2)
app/src/main/java/com/into/websoso/data/repository/FeedRepository.ktapp/src/main/java/com/into/websoso/ui/main/home/HomeViewModel.kt
- 인기 피드 슬롯의 높이를 고정값(122dp)에서 `0dp`로 변경하여 영역을 가변적으로 차지하도록 수정 - 구분선(divider)을 중앙에 배치하고, 이를 기준으로 슬롯 1과 2가 상하로 균등하게 배치되도록 제약 조건 조정
- `HomeViewModel`에서 인기 작품 및 인기 피드 데이터 로드 성공 시 `error` 상태를 `false`로 변경하도록 수정
📌𝘐𝘴𝘴𝘶𝘦𝘴
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
작업 내용
홈 화면의
지금 뜨는 글컴포넌트를 변경된 피그마 디자인과 요구사항에 맞게 수정했습니다.지금 뜨는 글을 한 페이지당 최대 2개씩 노출하도록 변경/feeds/popular응답만으로 부족한 작품 제목/공개 여부/작품 연결 정보를 피드 상세 조회로 보강스포일러가 포함된 글 보기문구로 표시고려한 점
/feeds/popular응답에 작품 제목이 없어, 임시로 feedContent를 제목처럼 사용하는 방식은 피하고 피드 상세 API를 통해 작품 제목이 보이도록 했습니다.또한 작품 연결 여부에 따른 지금 뜨는 글 노출 여부와 스포일러 공개 여부는 앱에서 필터링하도록 처리했습니다.
Repository는 상세 정보가 보강된 인기 피드 목록만 반환하도록 하고, 홈에서 한 페이지에 2개씩 묶는 UI 정책은 HomeViewModel에서 처리하도록 책임을 분리했습니다.
📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴
Summary by CodeRabbit
New Features
UI/UX 개선
Bug Fixes