Skip to content

Conversation

@yeonjeen
Copy link
Member

@yeonjeen yeonjeen commented Jul 21, 2025

📌𝘐𝘴𝘴𝘶𝘦𝘴

📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯

  • 서재 탭 스크롤 상태 유지: 서재 탭 전환 시 기존 스크롤 위치를 유지하도록 상태 관리 (LazyListState, LazyGridState)
  • 서재 탭 재클릭 시 스크롤 최상단 이동: 유저가 서재 탭을 다시 클릭하면 리스트가 최상단으로 스크롤되도록 기능 추가

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵

KakaoTalk_Video_2025-07-21-18-06-24.mp4

💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴

Summary by CodeRabbit

  • New Features

    • 라이브러리(서재) 기능이 Jetpack Compose 기반의 새로운 UI 및 화면 구조로 전면 개편되었습니다.
    • 필터 및 정렬, 관심 작품/읽기 상태/별점/매력포인트 등 다양한 조건으로 소설 목록을 탐색할 수 있습니다.
    • 그리드/리스트 보기 전환, 페이징(무한 스크롤) 지원, 비어있는 상태 안내 및 탐색 버튼 제공.
    • 라이브러리 필터를 위한 바텀시트 및 필터 토글, 초기화, 적용 기능 추가.
    • 새로운 네비게이션 경로 추가 및 기존 네비게이션 방식 개선.
    • Room 기반 로컬 DB 및 Paging 3 연동 추가로 데이터 계층 확장.
    • 내부 데이터 저장소에 라이브러리 필터 상태 관리 기능 추가.
    • 신규 도메인 계층 및 UseCase 도입으로 페이징 및 필터링 로직 분리.
    • 라이브러리 관련 신규 아이콘 및 벡터 리소스 다수 추가.
  • 버그 수정

    • 네비게이션 및 화면 전환 방식 개선으로 중복 생성/불필요한 새로고침 방지.
  • 리팩터/구조 개선

    • 기존 뷰 기반 라이브러리 및 유저 서재 관련 화면, ViewModel, 어댑터, 데이터 모델 등 대거 삭제 및 Compose 기반으로 일원화.
    • 데이터 계층 및 도메인 계층에 페이징, 필터, 정렬 등 확장성 높은 구조 도입.
    • 내부 데이터 저장소 및 토큰 관리 함수 네이밍 명확화.
    • 의존성 및 모듈 구조 확장(코어/도메인/데이터/피처).
    • 레이아웃 XML 단순화 및 ComposeView로 대체.
    • Fragment 및 Activity 내 네비게이션 로직 개선 및 재구성.
  • 리소스/디자인

    • 라이브러리 관련 신규 아이콘 및 벡터 리소스 다수 추가.
    • 레이아웃 XML 단순화 및 ComposeView로 대체.
  • 기타

    • 내부 데이터 저장소 및 토큰 관리 함수 네이밍 명확화.
    • 의존성 및 모듈 구조 확장(코어/도메인/데이터/피처).

@yeonjeen yeonjeen requested a review from a team July 21, 2025 09:43
@yeonjeen yeonjeen self-assigned this Jul 21, 2025
@yeonjeen yeonjeen added 🍯 [FEAT] 새로운 기능을 개발합니다. 👛 황후 연진 labels Jul 21, 2025
@github-actions github-actions bot requested review from junseo511, m6z1 and s9hn July 21, 2025 09:43
@coderabbitai
Copy link

coderabbitai bot commented Jul 21, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

이 PR은 서재(라이브러리) 기능의 대규모 리팩터링과 확장, Jetpack Compose 기반 UI 도입, 페이징 및 필터링 기능 추가, 데이터/도메인/네트워크 계층의 구조 개선, 네비게이션 시스템 확장, Room 기반 로컬 DB 구축, 관련 레이아웃 및 어댑터 제거, 그리고 빌드 스크립트 및 의존성 추가를 포함합니다.

Changes

파일(그룹) 변경 요약
app/build.gradle.kts, data/library/build.gradle.kts, feature/library/build.gradle.kts, domain/library/build.gradle.kts, core/database/build.gradle.kts, core/datastore/build.gradle.kts Paging3, Room 등 라이브러리 및 모듈 의존성 추가/정리
app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt, core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt 네비게이터 인터페이스 및 구현 확장: 새로운 액티비티 네비게이션 메서드 추가 및 기존 시그니처 수정
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt, app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt, app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt, app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt, app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt 메인/서재/마이페이지/타인서재 화면 리팩터링 및 네비게이션/로직 수정, Compose 전환, 파라미터 처리 단순화
app/src/main/java/com/into/websoso/data/mapper/UserMapper.kt, app/src/main/java/com/into/websoso/data/remote/api/UserApi.kt, app/src/main/java/com/into/websoso/data/remote/response/UserStorageResponseDto.kt, app/src/main/java/com/into/websoso/data/repository/UserRepository.kt, app/src/main/java/com/into/websoso/ui/mapper/NovelMapper.kt, app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt, app/src/main/java/com/into/websoso/ui/userStorage/UserStorageViewModel.kt, app/src/main/java/com/into/websoso/ui/userStorage/SortMenuHandler.kt, app/src/main/java/com/into/websoso/ui/userStorage/SortPopupHandler.kt, app/src/main/java/com/into/websoso/ui/userStorage/adapter/, app/src/main/java/com/into/websoso/ui/userStorage/model/ UserStorage 관련 DTO, 엔티티, 매퍼, 뷰모델, 어댑터, 정렬/탭/상태 모델 등 완전 제거 및 관련 코드 삭제
app/src/main/res/layout/activity_storage.xml, app/src/main/res/layout/fragment_library.xml, app/src/main/res/layout/item_storage.xml, app/src/main/res/layout/item_storage_novel.xml, app/src/main/res/layout/menu_storage_popup.xml 기존 View 기반 레이아웃 삭제 및 Compose용 최소 레이아웃으로 교체
core/database/* Room 기반 DB 모듈 신설: 엔티티, Dao, DB, TypeConverter, DI 모듈 등 추가
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/, core/datastore/src/main/java/com/into/websoso/core/datastore/di/ 라이브러리 필터 DataStore 및 DI 모듈, 매퍼, 모델 등 추가
core/network/src/main/java/com/into/websoso/core/network/datasource/library/* UserLibrary → UserNovels API/DTO/엔티티/매퍼 확장 및 필터/정렬/페이징 파라미터 추가, 관련 코드 정비
data/library/src/main/java/com/into/websoso/data/library/* Paging, 필터링, RemoteMediator, 로컬/리모트 데이터소스, 모델, 엔티티, 필터데이터 관리 등 대폭 추가 및 기존 fetchLibrary 제거
domain/library/* 도메인 계층 신설: UseCase, 모델(enum, 엔티티), 페이징 유즈케이스 등 추가
feature/library/src/main/java/com/into/websoso/feature/library/* Compose 기반 서재 화면/컴포넌트/필터 바텀시트/그리드/리스트/탑바 등 신규 구현, ViewModel/상태관리/필터/정렬/관심토글/스크롤 유지 등 추가
core/resource/src/main/res/drawable/ic_library_*.xml 서재/필터/정렬/관심 등 관련 신규 아이콘 벡터 리소스 다수 추가
기타(core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt, core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt) 디스패처 상수 추가, 날짜 포맷 확장 함수 추가 등 유틸리티 보강

Changes Table

파일/경로(그룹) 변경 요약
app/build.gradle.kts, data/library/build.gradle.kts, feature/library/build.gradle.kts, domain/library/build.gradle.kts, core/database/build.gradle.kts, core/datastore/build.gradle.kts Paging3, Room, Compose 등 의존성 추가/정비
app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt, core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt 네비게이터 인터페이스/구현 확장, 새로운 네비게이션 메서드 추가 및 시그니처 변경
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt, app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt, app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt, app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt 메인/서재/마이페이지/타인서재 화면 리팩터링, Compose 전환, 네비게이션 로직 단순화
app/src/main/java/com/into/websoso/data/mapper/UserMapper.kt, app/src/main/java/com/into/websoso/data/remote/api/UserApi.kt, app/src/main/java/com/into/websoso/data/remote/response/UserStorageResponseDto.kt, app/src/main/java/com/into/websoso/data/repository/UserRepository.kt, app/src/main/java/com/into/websoso/ui/mapper/NovelMapper.kt, app/src/main/java/com/into/websoso/ui/userStorage/* UserStorage 관련 DTO, 엔티티, 매퍼, 뷰모델, 어댑터, 정렬/탭/상태 모델 등 완전 삭제
app/src/main/res/layout/activity_storage.xml, app/src/main/res/layout/fragment_library.xml, app/src/main/res/layout/item_storage.xml, app/src/main/res/layout/item_storage_novel.xml, app/src/main/res/layout/menu_storage_popup.xml View 기반 레이아웃 삭제 및 Compose용 최소 레이아웃으로 교체
core/database/* Room DB 모듈 신설: 엔티티, Dao, DB, TypeConverter, DI 모듈 등 추가
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/, core/datastore/src/main/java/com/into/websoso/core/datastore/di/ 라이브러리 필터 DataStore, DI 모듈, 매퍼, 모델 추가
core/network/src/main/java/com/into/websoso/core/network/datasource/library/* UserLibrary → UserNovels API/DTO/엔티티/매퍼 확장 및 필터/정렬/페이징 파라미터 추가
data/library/src/main/java/com/into/websoso/data/library/* Paging, 필터링, RemoteMediator, 로컬/리모트 데이터소스, 모델, 엔티티, 필터데이터 등 신규 구현
domain/library/* 도메인 계층 신설: UseCase, 모델(enum, 엔티티), 페이징 유즈케이스 등 추가
feature/library/src/main/java/com/into/websoso/feature/library/* Compose 기반 서재 화면/컴포넌트/필터 바텀시트/그리드/리스트/탑바 등 신규 구현, ViewModel/상태관리 등 추가
core/resource/src/main/res/drawable/ic_library_*.xml 서재 관련 신규 아이콘 리소스 추가
기타(core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt, core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt) 디스패처 상수 추가, 날짜 포맷 확장 함수 등 유틸리티 보강

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LibraryScreen
    participant LibraryViewModel
    participant LibraryRepository
    participant LibraryRemoteDataSource
    participant LibraryLocalDataSource
    participant RoomDB

    User->>LibraryScreen: 화면 진입/필터/정렬/스크롤 등 UI 인터랙션
    LibraryScreen->>LibraryViewModel: 상태 업데이트/필터/정렬/뷰타입 변경
    LibraryViewModel->>LibraryRepository: getUserLibrary(필터/정렬/페이징)
    LibraryRepository->>NovelRemoteMediator: 페이징 요청
    NovelRemoteMediator->>LibraryRemoteDataSource: getUserNovels(필터/정렬/페이징)
    LibraryRemoteDataSource->>LibraryRemoteDataSource: API 호출, 결과 파싱
    LibraryRemoteDataSource-->>NovelRemoteMediator: UserNovelsEntity 반환
    NovelRemoteMediator->>LibraryLocalDataSource: insertNovels()
    LibraryLocalDataSource->>RoomDB: DB 저장
    LibraryLocalDataSource-->>NovelRemoteMediator: 저장 완료
    NovelRemoteMediator-->>LibraryRepository: MediatorResult 반환
    LibraryRepository-->>LibraryViewModel: PagingData<NovelEntity> Flow 전달
    LibraryViewModel-->>LibraryScreen: UI 상태 및 PagingData 전달
    LibraryScreen-->>User: UI 렌더링 및 피드백
Loading

Estimated code review effort

4 (60–120분)

  • 대규모 파일 변경, 모듈 신설, 기존 코드 대거 삭제, Compose UI 도입, Paging/필터/정렬/네비게이션 등 다수의 신규 기능 및 구조 변경 포함

Possibly related PRs

  • #692: NavigatorProvider 및 WebsosoNavigator의 최초 도입과 관련, 본 PR의 네비게이터 확장과 직접적으로 연결됨.
  • #691: 네비게이터의 (Intent) -> Unit 파라미터 방식 도입, 본 PR의 네비게이터 시그니처 확장과 연관.
  • #723: 본 PR과 동일한 서재/라이브러리 기능, 네비게이터, Compose 전환, Paging, 필터, 데이터 계층 리팩터링 등 직접 코드 레벨로 강하게 연결됨.

Suggested labels

⚖️ 재상 세훈

Suggested reviewers

  • junseo511
  • m6z1

Poem

🐰
서재는 새 옷을 입었네,
Compose 바람에 UI가 춤추고
페이징과 필터가 토끼처럼 깡충!
데이터는 방금 구운 빵처럼 신선,
네비게이터는 길을 넓혔지.
새 아이콘 반짝,
개발자 마음도 두근두근!

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/725

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

kapt(libs.room.compiler)

// 페이징3
val paging_version = "3.3.6"

Choose a reason for hiding this comment

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

🚫 [ktlint] standard:property-naming reported by reviewdog 🐶
Property name should start with a lowercase letter and use camel case


// 페이징3
val paging_version = "3.3.6"
implementation("androidx.room:room-paging:2.5.1") // Room 최신 버전에 맞게 버전 확인

Choose a reason for hiding this comment

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

🚫 [ktlint] standard:no-multi-spaces reported by reviewdog 🐶
Unnecessary long whitespace

val paging_version = "3.3.6"
implementation("androidx.room:room-paging:2.5.1") // Room 최신 버전에 맞게 버전 확인


Choose a reason for hiding this comment

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

🚫 [ktlint] standard:no-consecutive-blank-lines reported by reviewdog 🐶
Needless blank line(s)

dependencies {
implementation(projects.core.database)
// 페이징3
val paging_version = "3.3.6"

Choose a reason for hiding this comment

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

🚫 [ktlint] standard:property-naming reported by reviewdog 🐶
Property name should start with a lowercase letter and use camel case

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

🧹 Nitpick comments (38)
domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt (1)

3-6: 도메인 레이어로의 SortType 이동이 적절합니다.

UI 레이어에서 도메인 레이어로 SortType을 이동시킨 것은 좋은 아키텍처 개선입니다. RECENT와 OLD라는 명명도 직관적입니다.

가독성 향상을 위해 각 enum 값에 대한 문서 주석 추가를 고려해보세요:

 enum class SortType {
+    /** 최신순 정렬 */
     RECENT,
+    /** 과거순 정렬 */
     OLD,
 }
core/resource/src/main/res/drawable/ic_library_vibe.xml (1)

6-8: 하드코드된 색상 리소스 사용 자제 권장

android:fillColor="#6A5DFD"처럼 직접 색상 값을 하드코딩하면 다크모드·테마 전환 시 유연성이 떨어집니다. 가능하면 @color/library_vibe 같은 색상 리소스를 정의해서 참조하도록 변경해 주세요.

core/resource/src/main/res/drawable/ic_library_finished.xml (1)

7-9: 색상 값 하드코딩 대신 색상 리소스 사용 권장

android:fillColor="#C7C7D0"가 하드코딩되어 있습니다. 테마 대응을 위해 @color/library_finished 등으로 분리하는 것이 좋습니다.

core/resource/src/main/res/drawable/ic_cancel_modal.xml (1)

6-17: Stroke 색상 하드코딩 개선 제안

두 path 모두 android:strokeColor="#52515F"가 고정되어 있습니다. 다크모드 및 테마 변경 대응을 위해 색상 리소스로 분리하는 것을 권장합니다.

core/resource/src/main/res/drawable/ic_library_sort.xml (1)

10-11: 색상 값 하드코딩 → 테마 속성으로 치환 제안

fillColorstrokeColor#C7C7D0를 직접 넣으면 다크모드·테마별 색상 변경이 불가능합니다.
?attr/colorOnSurfaceVariant 같은 테마 속성을 사용해 재사용성과 접근성을 높여주세요.

-      android:fillColor="#00000000"
-      android:strokeColor="#C7C7D0"
+      android:fillColor="@android:color/transparent"
+      android:strokeColor="?attr/colorOnSurfaceVariant"
data/library/build.gradle.kts (1)

12-20: 페이징 버전 상수 중복 선언 제거

이 모듈에서도 paging_version을 별도로 선언하고 있습니다. 중앙화된 버전 관리(버전 카탈로그 또는 Gradle extra)로 일원화하면 버전 변경 시 실수를 줄일 수 있습니다. 빌드 스크립트 단순화 차원에서 고려해주세요.

core/resource/src/main/res/drawable/ic_library_null_star.xml (1)

7-8: 스타 아이콘 색상 테마 적용 미지원

fillColor="#C7C7D0"가 고정되어 있어 다크모드에서 시안성이 떨어질 수 있습니다. ?attr/colorOnSurfaceVariant와 같이 테마 응용 색상을 사용해 주세요.

core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml (1)

8-10: 하드-코드된 색상 → 테마 속성/리소스로 추출 제안

#DDDDE3 색상이 fillColor/strokeColor에 직접 하드-코딩되어 있습니다. 다크모드나 브랜드 컬러 변경 시 유지보수가 어려우므로 color resource 또는 theme attribute로 교체하는 것이 좋습니다.

예시 diff:

-      android:fillColor="#DDDDE3"
-      android:strokeColor="#DDDDE3"/>
+      android:fillColor="?attr/colorOnSurfaceVariant"
+      android:strokeColor="?attr/colorOnSurfaceVariant"/>
core/resource/src/main/res/drawable/ic_library_character.xml (1)

8-11: 색상 하드-코딩 제거 권장

#6A5DFD 보라색이 여러 path에 반복됩니다. 색상 리소스(@color/library_primary)로 추출하면 재사용·일관성 유지가 수월합니다.

Also applies to: 13-14

core/resource/src/main/res/drawable/ic_library_grid.xml (1)

8-8: 다수 path의 동일 색상 → 공통 리소스 사용

#C7C7D0가 네 곳 모두 동일하게 쓰입니다. 향후 디자인 시스템 컬러 변경을 고려해 색상 리소스로 분리해 주세요.

Also applies to: 11-11, 14-14, 17-17

core/resource/src/main/res/drawable/ic_library_reset.xml (1)

7-9: reset 아이콘 색상도 테마 기반으로

#52515F 역시 다크모드 대응을 위해 ?attr/colorOnSurface 등으로 교체하는 것이 바람직합니다.

core/resource/src/main/res/drawable/ic_library_half_star.xml (1)

8-8: 별 아이콘 색상 분리 및 상태별 컬러 토큰 사용

회색 #C7C7D0과 포인트 컬러 #FF675D가 하드-코딩되어 있습니다. 평점 테마 변경 시를 위해 @color/rating_inactive, @color/rating_active 같은 토큰으로 분리해 주세요.

Also applies to: 11-11

core/resource/src/main/res/drawable/ic_library_quote_started.xml (1)

6-8: 하드코딩된 색상 값 대신 색상 리소스를 사용해 다크모드·테마 대응 강화
android:fillColor="#949399" 값이 직접 입력되어 있어 다크모드나 테마 변경 시 아이콘 색상이 어둡게 보일 수 있습니다. @color/… 혹은 ?attr/colorOnSurfaceVariant 등으로 추출해두면 유지보수성이 좋아집니다.

core/resource/src/main/res/drawable/ic_library_reading.xml (1)

6-9: 아이콘 색상 하드코딩 제거 권장
#C7C7D0 색상이 고정돼 있습니다. 공통 팔레트 리소스로 분리하면 다크모드 대응이 수월하고 디자인 팀이 색상을 일괄 교체하기도 편리합니다.

core/resource/src/main/res/drawable/ic_library_stopped.xml (1)

6-8: 색상값 리소스화 필요
다른 신규 아이콘과 동일하게 #C7C7D0 이 하드코딩되어 있습니다. @color/neutral60 등 공용 색상 리소스로 이동해 주세요.

core/resource/src/main/res/drawable/ic_library_quote_finished.xml (1)

6-8: 테마 적용을 위한 색상 추상화
android:fillColor="#949399"@color/library_icon_quote_finished 와 같이 분리하면, 차후 색상 시스템 개편 시 아이콘을 재빌드할 필요가 없습니다.

core/resource/src/main/res/drawable/ic_library_material.xml (1)

6-8: 메인 브랜드 색상도 리소스로 관리하세요
브랜드 퍼플 #6A5DFD 역시 하드코딩돼 있습니다. 다른 화면에서도 동일 색상을 사용하므로 @color/brand_primary 등으로 분리해 일관성을 유지하는 것이 좋습니다.

core/resource/src/main/res/drawable/ic_library_world_view.xml (1)

1-9: 테마 컬러를 사용하도록 변경하면 재사용성이 높아집니다
android:fillColor="#6A5DFD"와 같이 고정 HEX 값을 쓰면 다크모드·테마 변경 시 일관성이 깨질 수 있습니다. ?attr/colorOnSurface 등 테마 속성으로 교체하거나 @color/... 리소스로 분리해 두면 유지보수가 용이합니다.

-      android:fillColor="#6A5DFD"/>
+      android:fillColor="?attr/colorOnSurface"/>
app/src/main/res/layout/fragment_library.xml (1)

2-11: 계층을 한 단계 줄여 불필요한 LinearLayout 제거 제안
ComposeView 하나만 포함하고 orientation 속성도 의미가 없어 레이아웃 트리 깊이만 늘립니다. ComposeView를 루트로 사용하면 렌더링·측정 비용을 줄일 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.compose.ui.platform.ComposeView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cv_library"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
app/src/main/res/layout/activity_storage.xml (1)

2-8: 빈 ConstraintLayout 대신 불필요한 XML 제거 고려
Activity 내부에서 바로 setContent { ... }로 Compose UI를 띄운다면, XML 레이아웃 자체가 필요 없습니다. 파일을 삭제하고 Activity에서 setContent를 호출하도록 변경하면 정의·로드 비용을 없앨 수 있습니다.

core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)

17-25: 데이터베이스 마이그레이션 전략 고려 필요

현재 exportSchema = false로 설정되어 있어 스키마 히스토리가 관리되지 않습니다. 향후 데이터베이스 스키마 변경 시 마이그레이션 전략을 고려해보세요.

@Database(
    entities = [InDatabaseNovelEntity::class],
    version = 1,
-   exportSchema = false,
+   exportSchema = true,
)

또한 데이터베이스 빌드 시 fallback 전략도 고려해볼 수 있습니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt (1)

37-37: 하드코딩된 값을 테마로 추출 고려

UI 간격값들이 하드코딩되어 있습니다. 일관성을 위해 디자인 시스템으로 추출을 고려해보세요.

.padding(top = 80.dp),

현재는 문제없지만, 향후 디자인 시스템 확장 시 WebsosoTheme.spacing 등을 활용할 수 있습니다.

Also applies to: 46-46, 52-52

domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt (1)

14-19: valueOf 메서드명 충돌 가능성

커스텀 valueOf 메서드가 enum의 기본 valueOf 메서드를 숨길 수 있습니다. 혼란을 방지하기 위해 다른 이름 사용을 고려해보세요.

companion object {
-   fun valueOf(name: String): AttractivePoints =
+   fun fromName(name: String): AttractivePoints =
        AttractivePoints.entries.find { attractivePoints -> attractivePoints.name == name }
-           ?: throw IllegalArgumentException()
+           ?: throw IllegalArgumentException("Unknown AttractivePoints: $name")
}

또한 예외 메시지에 구체적인 정보를 포함하면 디버깅에 도움이 됩니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1)

27-34: 로딩 인디케이터 추가를 고려해보세요.

LibraryGridList와 달리 페이징 로딩 상태에 대한 인디케이터가 없습니다. 일관된 사용자 경험을 위해 추가를 고려해보세요.

        items(novels.itemCount) { index ->
            novels[index]?.let { novel ->
                LibraryListItem(
                    item = novel,
                    onClick = { onItemClick(novel) },
                )
            }
        }
+
+        if (novels.loadState.append is LoadState.Loading) {
+            item {
+                CircularProgressIndicator(modifier = Modifier.padding(16.dp))
+            }
+        }
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1)

25-29: 리소스 타입 어노테이션 사용을 검토해주세요.

@SuppressLint("ResourceType")@IntegerRes 어노테이션이 함께 사용되고 있습니다. @IntegerRes는 정수 리소스 ID를 나타내지만, 실제로는 drawable 리소스 ID를 받고 있는 것으로 보입니다.

올바른 어노테이션 사용을 위해 다음과 같이 수정하는 것을 권장합니다:

-    @IntegerRes icon: Int,
+    @DrawableRes icon: Int,

그리고 @SuppressLint("ResourceType") 어노테이션을 제거하세요.

data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryRemoteDataSource.kt (1)

6-17: 매개변수 객체 패턴 사용을 고려해보세요.

메서드가 10개의 매개변수를 받고 있어 가독성과 유지보수성이 떨어질 수 있습니다. 필터링과 페이징 관련 매개변수들을 하나의 데이터 클래스로 그룹화하는 것을 권장합니다.

예를 들어 다음과 같이 리팩토링할 수 있습니다:

data class UserNovelsRequest(
    val userId: Long,
    val lastUserNovelId: Long,
    val size: Int,
    val sortCriteria: String,
    val isInterest: Boolean? = null,
    val readStatuses: List<String>? = null,
    val attractivePoints: List<String>? = null,
    val novelRating: Float? = null,
    val query: String? = null,
    val updatedSince: String? = null,
)

suspend fun getUserNovels(request: UserNovelsRequest): UserNovelsEntity
core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt (1)

47-48: 날짜 필드의 null 처리 방식을 검토해주세요.

startDate.orEmpty()endDate.orEmpty()로 null을 빈 문자열로 변환하고 있습니다. 도메인 모델에서 날짜가 없는 상태를 빈 문자열로 표현하는 것이 적절한지 확인해보세요.

다음 중 하나를 고려해보세요:

  1. 도메인 모델도 nullable String으로 유지하기
  2. 특별한 기본값 (예: "미정", "TBD") 사용하기
  3. 별도의 sealed class나 enum으로 날짜 상태 표현하기
data/library/src/main/java/com/into/websoso/data/library/NovelRemoteMediator.kt (1)

45-49: 디버그 로그 태그를 의미있는 이름으로 변경 필요

"123123"과 "1231234" 같은 임시 태그 대신 적절한 로그 태그를 사용해야 합니다.

-            Log.d("123123", response.toString())
+            Log.d("NovelRemoteMediator", "Loaded novels: $response")
-            Log.d("1231234", e.toString())
+            Log.d("NovelRemoteMediator", "Error loading novels", e)
domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt (1)

15-36: 매개변수가 많아 가독성이 떨어집니다.

9개의 매개변수를 가진 함수는 유지보수가 어려울 수 있습니다. 필터링 관련 매개변수들을 하나의 데이터 클래스로 묶는 것을 고려해보세요.

예시 리팩터링:

+data class LibraryFilter(
+    val isInterest: Boolean? = null,
+    val readStatuses: List<String>? = null,
+    val attractivePoints: List<String>? = null,
+    val novelRating: Float? = null,
+    val query: String? = null
+)

operator fun invoke(
    userId: Long,
    lastUserNovelId: Long = INITIAL_LAST_USER_NOVEL_ID,
    size: Int = 60,
    sortCriteria: SortType = SortType.RECENT,
-    isInterest: Boolean? = null,
-    readStatuses: List<String>? = null,
-    attractivePoints: List<String>? = null,
-    novelRating: Float? = null,
-    query: String? = null,
+    filter: LibraryFilter = LibraryFilter()
): Flow<PagingData<NovelEntity>>
feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (2)

25-31: 메서드명이 실제 동작과 일치하지 않습니다.

updateMyLibraryFilter는 필터를 업데이트한다기보다는 전체 상태를 교체합니다. replaceLibraryFilter 또는 setLibraryFilterState 같은 이름이 더 적절합니다.

-fun updateMyLibraryFilter(libraryFilterUiState: LibraryFilterUiState) {
+fun replaceLibraryFilterState(libraryFilterUiState: LibraryFilterUiState) {

67-79: 코루틴 에러 핸들링 추가를 고려해보세요.

viewModelScope.launch 블록에서 repository 호출 시 발생할 수 있는 예외에 대한 처리가 없습니다. 사용자 경험을 위해 에러 핸들링을 추가하는 것이 좋습니다.

fun searchFilteredNovels() {
    viewModelScope.launch {
-        libraryRepository.updateMyLibraryFilter(
-            readStatuses = libraryFilterUiState.value.readStatuses
-                .filterValues { it }
-                .map { it.key.name },
-            attractivePoints = libraryFilterUiState.value.attractivePoints
-                .filterValues { it }
-                .map { it.key.name },
-            novelRating = libraryFilterUiState.value.novelRating,
-        )
+        try {
+            libraryRepository.updateMyLibraryFilter(
+                readStatuses = libraryFilterUiState.value.readStatuses
+                    .filterValues { it }
+                    .map { it.key.name },
+                attractivePoints = libraryFilterUiState.value.attractivePoints
+                    .filterValues { it }
+                    .map { it.key.name },
+                novelRating = libraryFilterUiState.value.novelRating,
+            )
+        } catch (e: Exception) {
+            // 에러 로깅 또는 사용자에게 알림
+        }
    }
}
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1)

36-43: JSON 파싱 오류 처리 추가 고려

JSON 디코딩 시 오류가 발생할 경우를 대비한 예외 처리를 추가하는 것이 좋을 것 같습니다.

 override val myLibraryFilterFlow: Flow<LibraryFilterParams?>
     get() = myLibraryFilterDataStore.data
         .map { prefs ->
             prefs[LIBRARY_FILTER_PARAMS_KEY]?.let { jsonString ->
                 withContext(dispatcher) {
-                    Json.decodeFromString<LibraryFilterPreferences>(jsonString).toData()
+                    try {
+                        Json.decodeFromString<LibraryFilterPreferences>(jsonString).toData()
+                    } catch (e: Exception) {
+                        // 로그 기록 후 null 반환 또는 기본값 반환
+                        null
+                    }
                 }
             }
         }.distinctUntilChanged()
feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt (1)

1-1: 파일명 오타 수정 필요

파일명에 오타가 있습니다: LibrayFilterTopBar.ktLibraryFilterTopBar.kt

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)

364-374: LazyRow items에 key 추가로 성능 개선

MyFeedCardGroup처럼 NovelKeywordChipGroup의 items에도 key를 추가하여 리컴포지션 성능을 개선하세요.

@Composable
private fun NovelKeywordChipGroup(novelKeyword: List<String>) {
    LazyRow(
        contentPadding = PaddingValues(end = 20.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
-        items(novelKeyword) {
+        items(novelKeyword, key = { it }) {
             NovelKeywordChip(it)
        }
    }
}
data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt (1)

76-83: TODO 주석 정리 필요

구현 계획을 설명하는 한글 주석이 남아있습니다. 이미 구현된 내용이라면 제거하거나, 아직 구현이 필요하다면 영문으로 작성하거나 이슈로 관리하세요.

-// 1. 클릭 리스너로 뷰모델 상태 업데이트
-// 2. 확인 누르면 datastore 업데이트
-// 3. 객체 직렬화 및 저장
-// 4. datastore를 읽고, 캐싱(가능하면), 널이 아니라면, 해당 쿼리문으로 룸 업데이트
-// 5. 룸에서 데이터 읽고 UI
-// 6. 이미지 캐싱
-// 7. 다른 뷰에서 룸 동기화
-// 8. 증분 API
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (3)

161-197: 읽기 상태 필터 섹션 개선 제안

현재 하드코딩된 패딩 값들을 디자인 시스템의 표준 spacing 값으로 통일하는 것을 고려해보세요.

            LibraryFilterBottomSheetClickableItem(
                icon = ic_library_reading,
                iconTitle = "보는 중",
                iconSize = 24.dp,
-               horizontalPadding = 36.dp,
+               horizontalPadding = WebsosoTheme.spacing.large, // 또는 표준화된 값
                onClick = { onReadStatusClick(WATCHING) },
                isSelected = readStatues[WATCHING] ?: false,
            )

199-251: 매력포인트 필터 섹션의 일관성 확인

5개의 매력포인트 아이템이 한 줄에 배치되어 있어 작은 화면에서 UI가 압축될 수 있습니다. 반응형 레이아웃이나 그리드 구조를 고려해보세요.

또한 읽기 상태 섹션과 동일하게 패딩 값 표준화를 권장합니다.

// 반응형 레이아웃 고려 예시
LazyHorizontalGrid(
    rows = GridCells.Fixed(2),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp)
) {
    // 아이템들...
}

366-398: NovelRatingItem의 조건부 스타일링 개선 제안

then() modifier를 사용한 조건부 스타일링보다는 더 가독성 좋은 패턴을 고려해보세요.

Box(
    modifier = modifier
        .background(
            color = backgroundColor,
            shape = RoundedCornerShape(size = 8.dp),
        )
-       .then(
-           if (isSelected) {
-               Modifier.border(
-                   width = 1.dp,
-                   color = Primary100,
-                   shape = RoundedCornerShape(size = 8.dp),
-               )
-           } else {
-               Modifier
-           },
-       )
+       .let { modifier ->
+           if (isSelected) {
+               modifier.border(
+                   width = 1.dp,
+                   color = Primary100,
+                   shape = RoundedCornerShape(size = 8.dp),
+               )
+           } else modifier
+       }
        .padding(vertical = 14.dp, horizontal = 24.dp),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe7fd74 and f2c1b80.

📒 Files selected for processing (107)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt (3 hunks)
  • app/src/main/java/com/into/websoso/data/mapper/UserMapper.kt (0 hunks)
  • app/src/main/java/com/into/websoso/data/remote/api/UserApi.kt (0 hunks)
  • app/src/main/java/com/into/websoso/data/remote/response/UserStorageResponseDto.kt (0 hunks)
  • app/src/main/java/com/into/websoso/data/repository/UserRepository.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/MainActivity.kt (5 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/LibraryViewModel.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemAdapter.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemViewHolder.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerAdapter.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerViewHolder.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/model/LibraryUiState.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/mapper/NovelMapper.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt (3 hunks)
  • app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/SortMenuHandler.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/SortPopupHandler.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/UserStorageViewModel.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemAdapter.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemViewHolder.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerAdapter.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerViewHolder.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/model/SortType.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/model/StatusTabs.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageModel.kt (0 hunks)
  • app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageUiState.kt (0 hunks)
  • app/src/main/res/layout/activity_storage.xml (1 hunks)
  • app/src/main/res/layout/fragment_library.xml (1 hunks)
  • app/src/main/res/layout/item_storage.xml (0 hunks)
  • app/src/main/res/layout/item_storage_novel.xml (0 hunks)
  • app/src/main/res/layout/menu_storage_popup.xml (0 hunks)
  • core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt (2 hunks)
  • core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt (1 hunks)
  • core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt (1 hunks)
  • core/database/.gitignore (1 hunks)
  • core/database/build.gradle.kts (1 hunks)
  • core/database/proguard-rules.pro (1 hunks)
  • core/database/src/main/AndroidManifest.xml (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/Converters.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt (1 hunks)
  • core/datastore/build.gradle.kts (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt (1 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (1 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/DefaultLibraryDataSource.kt (2 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/LibraryApi.kt (1 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt (2 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserLibraryResponseDto.kt (0 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserNovelsResponseDto.kt (1 hunks)
  • core/resource/src/main/res/drawable/ic_cancel_modal.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_character.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_finished.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_grid.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_half_star.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_interesting.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_list.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_material.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_null_star.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_quote_finished.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_quote_started.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_reading.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_relationship.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_reset.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_sort.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_stopped.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_vibe.xml (1 hunks)
  • core/resource/src/main/res/drawable/ic_library_world_view.xml (1 hunks)
  • data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (2 hunks)
  • data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1 hunks)
  • data/library/build.gradle.kts (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/NovelRemoteMediator.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryRemoteDataSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt (1 hunks)
  • domain/library/.gitignore (1 hunks)
  • domain/library/build.gradle.kts (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt (1 hunks)
  • feature/library/build.gradle.kts (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryTopBar.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt (1 hunks)
⛔ Files not processed due to max files limit (13)
  • feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt
  • feature/library/src/main/res/values/string.xml
  • feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt
  • feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt
  • gradle/libs.versions.toml
  • settings.gradle.kts
💤 Files with no reviewable changes (26)
  • app/src/main/java/com/into/websoso/ui/mapper/NovelMapper.kt
  • app/src/main/res/layout/item_storage.xml
  • app/src/main/java/com/into/websoso/ui/userStorage/model/SortType.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/model/StatusTabs.kt
  • app/src/main/java/com/into/websoso/data/repository/UserRepository.kt
  • app/src/main/java/com/into/websoso/data/remote/api/UserApi.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemViewHolder.kt
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemViewHolder.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerViewHolder.kt
  • app/src/main/java/com/into/websoso/data/mapper/UserMapper.kt
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemAdapter.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/SortPopupHandler.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageUiState.kt
  • app/src/main/res/layout/menu_storage_popup.xml
  • app/src/main/java/com/into/websoso/ui/userStorage/SortMenuHandler.kt
  • app/src/main/java/com/into/websoso/ui/main/library/model/LibraryUiState.kt
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerAdapter.kt
  • app/src/main/res/layout/item_storage_novel.xml
  • app/src/main/java/com/into/websoso/data/remote/response/UserStorageResponseDto.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageModel.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerAdapter.kt
  • app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerViewHolder.kt
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserLibraryResponseDto.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemAdapter.kt
  • app/src/main/java/com/into/websoso/ui/userStorage/UserStorageViewModel.kt
  • app/src/main/java/com/into/websoso/ui/main/library/LibraryViewModel.kt
🧰 Additional context used
🧠 Learnings (2)
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)

Learnt from: s9hn
PR: #676
File: core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt:27-34
Timestamp: 2025-05-14T07:41:05.706Z
Learning: Accessing DataStore via runBlocking in OkHttp interceptors can block network threads and cause performance issues. Consider using in-memory caching with background updates instead to achieve thread safety without blocking OkHttp threads.

core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1)

Learnt from: s9hn
PR: #676
File: core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt:27-34
Timestamp: 2025-05-14T07:41:05.706Z
Learning: Accessing DataStore via runBlocking in OkHttp interceptors can block network threads and cause performance issues. Consider using in-memory caching with background updates instead to achieve thread safety without blocking OkHttp threads.

🧬 Code Graph Analysis (13)
data/library/build.gradle.kts (1)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
  • implementation (70-76)
domain/library/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
  • setNamespace (9-13)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
  • implementation (70-76)
core/datastore/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
  • setNamespace (9-13)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
  • implementation (70-76)
feature/library/build.gradle.kts (1)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
  • implementation (70-76)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1)
  • NovelGridListItem (49-89)
app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (2)
  • LibraryScreen (42-105)
  • LibraryScreen (107-187)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
  • LibraryListItem (70-113)
app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (2)
  • LibraryScreen (42-105)
  • LibraryScreen (107-187)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1)
  • ReadStatusBadge (130-145)
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt (3)
app/src/main/java/com/into/websoso/ui/detailExploreResult/DetailExploreResultKeywordFragment.kt (1)
  • setupBackButtonListener (280-293)
app/src/main/java/com/into/websoso/ui/detailExplore/keyword/DetailExploreKeywordFragment.kt (1)
  • setupBackButtonListener (303-316)
app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingKeywordBottomSheetDialog.kt (1)
  • setupBackButtonListener (278-298)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (2)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (1)
  • updateMyLibraryFilter (25-31)
data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt (1)
  • updateMyLibraryFilter (54-74)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
  • ReadStatusBadge (157-180)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1)
  • LibraryFilterBottomSheetClickableItem (25-54)
🪛 detekt (1.23.8)
domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt

[warning] 12-12: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)

app/src/main/java/com/into/websoso/ui/main/MainActivity.kt

[warning] 231-231: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)


[warning] 235-235: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)

🔇 Additional comments (76)
app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1)

60-60: 메서드 이름 변경 승인

renewTokens()에서 createTokens()로의 메서드 이름 변경이 의미적으로 더 명확합니다. 기능적 동작은 변경되지 않으며 전체 토큰 관리 리팩터링의 일부로 보입니다.

core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (1)

71-71: 메서드 이름 변경 승인 및 성능 고려사항

renewTokens()에서 createTokens()로의 변경은 일관성을 위해 적절합니다.

다만, 이전 리뷰에서 OkHttp 인터셉터/인증자에서 DataStore 접근 시 네트워크 스레드 블로킹 이슈가 언급된 바 있습니다. accountRepository.get().createTokens() 호출이 내부적으로 DataStore를 사용한다면 성능 영향을 검토해볼 필요가 있습니다.

core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)

23-52: 메서드 명명 규칙 개선 승인

모든 메서드 이름 변경이 CRUD 명명 규칙을 따라 의미가 더욱 명확해졌습니다:

  • accessToken()selectAccessToken()
  • refreshToken()selectRefreshToken()
  • saveAccessToken()updateAccessToken()
  • saveRefreshToken()updateRefreshToken()
  • clearTokens()deleteTokens()

기능적 변경 없이 코드 가독성과 의도 전달이 향상되었습니다.

data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)

4-12: 인터페이스 메서드 명명 규칙 일관성 승인

인터페이스 메서드들이 구현체와 일치하도록 CRUD 명명 규칙으로 업데이트되었습니다. 계약(contract)과 구현 간의 일관성이 유지되어 좋습니다.

data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (5)

21-23: 토큰 조회 메서드 업데이트 승인

selectAccessToken()selectRefreshToken() 호출로 변경되어 데이터 소스 레이어와 일관성을 유지합니다.


25-38: 계정 생성 메서드 리팩터링 승인

saveTokens에서 createAccount로 이름 변경과 updateAccessToken(), updateRefreshToken() 호출 사용이 의미상 더 명확합니다. 계정 생성의 의도가 잘 드러납니다.


40-44: 계정 삭제 메서드 업데이트 승인

deleteTokens() 호출로 변경되어 명명 규칙 일관성이 유지됩니다.


46-52: 토큰 생성 메서드 리팩터링 승인

renewTokens에서 createTokens로의 이름 변경과 update* 메서드 사용이 적절합니다. 토큰 갱신보다는 생성의 의미가 더 명확하게 전달됩니다.


54-63: 토큰 삭제 메서드 위치 이동 및 업데이트 승인

메서드가 클래스 끝으로 이동되었고 deleteTokens() 호출로 업데이트되었습니다. 기능적으로는 동일하며 명명 규칙 일관성이 유지됩니다.

core/database/.gitignore (1)

1-1: 표준적인 .gitignore 구성이 적절합니다.

Android 모듈의 빌드 산출물을 올바르게 제외하고 있습니다.

core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt (2)

18-20: DEFAULT 디스패처 추가가 적절합니다.

CPU 집약적인 작업을 위한 DEFAULT 디스패처를 추가하는 것은 좋은 아키텍처 결정입니다. 기존 IO 디스패처와 일관된 패턴으로 구현되었습니다.


31-31: enum 확장이 적절합니다.

WebsosoDispatchers enum에 DEFAULT 추가가 깔끔하게 처리되었습니다.

domain/library/.gitignore (1)

1-1: 표준적인 .gitignore 구성이 적절합니다.

새로운 domain/library 모듈에 대한 적절한 빌드 산출물 제외 설정입니다.

core/database/src/main/AndroidManifest.xml (1)

1-4: 라이브러리 모듈에 적합한 최소 manifest 구성입니다.

core/database 모듈에 필요한 기본적인 AndroidManifest.xml 구성이 올바릅니다.

core/datastore/build.gradle.kts (1)

13-19: 레이어드 아키텍처 의존성 방향 확인 필요

core.datastore 모듈이 projects.data.library에 의존하면서 core → data 방향 결합이 생겼습니다. 일반적으로 core 계층은 하위(공통) 레이어여야 하므로, 데이터 계층을 참조하면 순환 의존의 위험이 있습니다. 의존성을 domain 또는 별도 contract 모듈로 분리하는지 확인해 주세요.

core/database/build.gradle.kts (1)

18-25: Paging 3 버전 관리 중복 및 Room-Paging 버전 불일치 위험

  1. val paging_version = "3.3.6" 선언이 여러 모듈에 중복됩니다. libs.versions.toml 또는 ext에 중앙 관리해 유지보수를 단일화하는 편이 좋습니다.
  2. androidx.room:room-paging:2.5.1 은 상단 libs.room.*가 사용 중인 Room 버전과 맞지 않을 가능성이 있습니다. 버전 불일치 시 컴파일 경고·런타임 오류가 발생할 수 있으니 동일 버전으로 맞춰주세요.
-    val paging_version = "3.3.6"
-    implementation("androidx.room:room-paging:2.5.1")
+    val paging_version = libs.versions.androidxPaging.get()
+    implementation("androidx.room:room-paging:${libs.versions.room.get()}")
core/database/proguard-rules.pro (1)

1-21: 파일이 build.gradle에 포함돼 있는지 확인 필요
새 규칙 파일이 추가됐지만, proguardFiles("core/database/proguard-rules.pro")가 해당 모듈의 build script 에 선언되지 않으면 적용되지 않습니다. 누락 여부를 점검해 주세요.

feature/library/build.gradle.kts (1)

11-18: 의존성 버전 충돌 가능성 확인 요청
libs.paging.compose 추가로 paging-runtime 과 compose-bom 간 버전 불일치가 발생할 수 있습니다. Gradle > Dependency Graph 로 충돌 여부를 한번 확인해 주세요. 문제 없으면 그대로 진행하셔도 됩니다.

core/resource/src/main/res/drawable/ic_library_relationship.xml (1)

1-13: 벡터 드로어블 구현이 올바르게 되어 있습니다.

XML 구조, 네임스페이스, 색상 사용(#6A5DFD), 그리고 경로 데이터가 모두 적절하게 구성되어 있습니다. evenOdd 채우기 타입 사용도 복잡한 경로 형상에 적합합니다.

core/resource/src/main/res/drawable/ic_library_interesting.xml (1)

1-12: 하트 모양 아이콘이 잘 구현되었습니다.

레이어드 접근 방식(채우기 + 윤곽선)으로 시각적 깊이를 만들고, 일관된 색상 체계를 사용하여 디자인 시스템과 잘 어울립니다.

app/build.gradle.kts (3)

84-84: 데이터베이스 모듈 의존성 추가가 적절합니다.

Room 데이터베이스 통합을 위한 core.database 모듈 추가가 새로운 라이브러리 아키텍처와 잘 맞습니다.


87-90: 새로운 라이브러리 피처 모듈 구조가 적절합니다.

data.libraryfeature.library 모듈의 추가와 의존성 순서 조정이 새로운 아키텍처를 잘 반영하고 있습니다.


137-137: 페이징 라이브러리 버전이 최신입니다.

Paging Runtime 3.3.2 버전 사용으로 새로운 페이징 기능을 적절히 지원합니다.

core/resource/src/main/res/drawable/ic_library_list.xml (1)

1-24: 리스트 뷰 아이콘이 잘 설계되었습니다.

2x3 그리드 레이아웃으로 리스트 뷰를 효과적으로 표현하고, 두 가지 회색 톤(#C7C7D0, #949399)을 사용하여 시각적 계층구조를 만들었습니다. 모서리가 둥근 사각형들의 일관된 반지름(1.009) 사용도 적절합니다.

core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt (2)

16-26: 잘 구현된 DAO 인터페이스입니다.

Paging 지원, 적절한 충돌 전략 사용, 캐시 무효화를 위한 clear 기능이 모두 잘 구현되어 있습니다.


28-34: Hilt 모듈이 올바르게 구성되었습니다.

SingletonComponent 스코프에서 DAO를 적절히 제공하고 있으며, 의존성 주입 설정이 정확합니다.

app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt (2)

198-210: 네비게이션 로직이 잘 단순화되었습니다.

두 클릭 리스너 모두 매개변수 없는 navigateToLibraryFragment() 메서드를 호출하도록 변경되어 코드가 더 간결해졌습니다. SingleEventHandler를 통한 중복 클릭 방지도 적절히 유지되고 있습니다.


212-220: 매개변수 제거로 네비게이션이 더 단순해졌습니다.

읽기 상태 매개변수를 제거한 것은 타겟 화면에서 상태를 내부적으로 관리하는 새로운 아키텍처와 일치하는 좋은 변경사항입니다.

core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt (2)

20-24: 새로운 DataStore 인스턴스가 일관된 패턴으로 추가되었습니다.

기존 account DataStore와 동일한 패턴을 따르고 있으며, 명명 규칙도 일관성 있게 적용되었습니다.


32-37: 의존성 주입 설정이 올바르게 구성되었습니다.

@Provides, @Singleton, @MyLibraryFilterDataStore 어노테이션이 적절히 적용되어 있으며, 메서드 시그니처가 기존 패턴과 일치합니다.

core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt (1)

5-12: 직렬화 가능한 필터 설정 모델이 잘 설계되었습니다.

@Serializable 어노테이션을 통한 JSON 직렬화 지원, 적절한 내부 가시성, 그리고 옵셔널 필터링을 위한 nullable 필드 설계가 모두 잘 구현되어 있습니다. DataStore 지속성 모델의 역할에 적합합니다.

core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)

32-41: 데이터베이스 구성이 적절합니다

Room 데이터베이스 설정과 Hilt 모듈 구성이 올바르게 구현되었습니다. 싱글톤 스코프와 애플리케이션 컨텍스트 사용이 적절합니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt (2)

32-58: 컴포넌트 구성이 우수합니다

빈 상태 UI 컴포넌트가 잘 구성되었습니다. Material3와 커스텀 테마를 적절히 활용하고 있으며, 리소스 참조도 올바르게 처리되었습니다.


60-85: 버튼 컴포넌트 구현이 적절합니다

private 컴포넌트로 분리되어 재사용성과 가독성이 좋습니다. 버튼 스타일링과 패딩 설정도 적절합니다.

domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt (1)

3-12: enum 클래스 구조가 적절합니다

한글 라벨과 영문 키를 가진 enum 구조가 잘 설계되었습니다. entries 프로퍼티 사용도 현대적인 Kotlin 접근법입니다.

core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt (1)

6-23: 매핑 함수 구현이 적절합니다

양방향 매핑이 일관되게 구현되었으며, 모든 프로퍼티가 올바르게 복사되고 있습니다. 단순한 프로퍼티 매핑에 적합한 구현입니다.

core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserNovelsResponseDto.kt (1)

9-25: DTO 구조와 매핑 로직이 잘 구현되었습니다.

네트워크 응답을 도메인 엔티티로 변환하는 표준적인 패턴을 잘 따르고 있으며, 직렬화 어노테이션과 프로퍼티 매핑이 적절합니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt (1)

19-49: 스크롤 상태 관리가 잘 구현된 그리드 리스트입니다.

LazyGridState를 매개변수로 받아 탭 전환 시 스크롤 위치를 유지할 수 있도록 구현되었으며, 페이징과 로딩 상태 처리도 적절합니다. 3열 고정 그리드 레이아웃과 간격 설정도 UI/UX에 적합합니다.

data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt (1)

3-13: 필터링 매개변수 모델이 잘 설계되었습니다.

페이징과 다양한 필터링 옵션을 지원하는 데이터 클래스가 적절히 구조화되어 있으며, nullable 필드를 통해 선택적 필터링을 잘 지원합니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1)

14-36: 스크롤 상태 관리가 잘 구현된 리스트입니다.

LazyListState를 매개변수로 받아 탭 전환 시 스크롤 위치 유지 기능을 지원하며, 페이징 아이템과의 통합도 적절합니다.

core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt (1)

7-30: 날짜 범위 포맷팅 유틸리티가 잘 구현되었습니다.

null 안전성과 예외 처리가 적절하며, Java 8 시간 API를 올바르게 사용하고 있습니다. 단일 날짜와 날짜 범위 모두를 지원하는 로직도 명확합니다.

data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt (1)

6-12: 인터페이스 설계가 우수합니다.

반응형 프로그래밍을 위한 Flow 사용, 비동기 작업을 위한 suspend 함수, 그리고 명확한 책임 분리가 잘 되어 있습니다. 깔끔하고 집중된 인터페이스 설계입니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1)

35-54: 컴포저블 구현이 잘 되어 있습니다.

UI 구조가 명확하고, 선택 상태에 따른 색상 변화, 적절한 간격과 정렬이 잘 구현되어 있습니다. 재사용 가능한 컴포넌트로 잘 설계되었습니다.

core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt (1)

36-51: 데이터 매핑 함수가 잘 구현되어 있습니다.

DTO에서 도메인 모델로의 변환 로직이 명확하고 모든 필드가 적절히 매핑되어 있습니다. 확장된 데이터 구조도 새로운 필터링 기능을 잘 지원합니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (3)

43-48: 상수 정의가 적절합니다.

그리드 레이아웃을 위한 상수들이 명확하게 정의되어 있고, 이미지 비율 상수도 적절합니다. 매직 넘버 사용을 피한 좋은 구현입니다.


148-162: 그리드 아이템 크기 계산 로직이 잘 구현되어 있습니다.

화면 크기에 따라 동적으로 아이템 크기를 계산하는 로직이 정확하고, remember를 사용해 불필요한 재계산을 방지하고 있습니다. 밀도 변환도 올바르게 처리되었습니다.


165-181: 별점 표시 컴포넌트가 잘 구현되어 있습니다.

별점 타입에 따른 아이콘 선택과 적절한 간격 설정이 잘 되어 있습니다. 재사용 가능한 컴포넌트로 잘 설계되었습니다.

core/network/src/main/java/com/into/websoso/core/network/datasource/library/LibraryApi.kt (1)

16-27: API 인터페이스 설계가 잘 되어 있습니다

페이징과 필터링을 위한 포괄적인 매개변수들이 잘 정의되어 있고, Retrofit 어노테이션도 적절히 사용되었습니다.

core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt (2)

6-21: Room 엔티티 구조가 잘 설계되었습니다

소설 데이터를 저장하기 위한 포괄적인 필드들이 잘 정의되어 있고, @Entity@PrimaryKey 어노테이션도 적절히 사용되었습니다.


16-20: TypeConverter 설정 확인 완료

Converters.ktList<String>String 변환 로직이 구현되어 있으며, WebsosoDatabase@TypeConverters(Converters::class)로 등록되어 있어 attractivePoints, keywords, myFeeds 필드는 정상 저장됩니다. 문제 없습니다.

app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt (1)

49-65: 새로운 네비게이션 메서드들이 잘 구현되었습니다

일관된 패턴을 따르며 필요한 매개변수들을 적절히 처리하고 있습니다. 특히 navigateToNovelDetailActivity에서 novelId를 받아 처리하는 부분이 잘 설계되었습니다.

data/library/src/main/java/com/into/websoso/data/library/NovelRemoteMediator.kt (1)

16-52: 페이징 로직이 올바르게 구현되었습니다

RemoteMediator의 다양한 LoadType을 적절히 처리하고 있으며, 로컬 데이터베이스와의 동기화 로직도 잘 구현되어 있습니다. 예외 처리도 적절합니다.

data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (3)

15-21: 인터페이스 설계가 깔끔하고 책임이 명확합니다.

로컬 데이터 소스의 핵심 기능인 삽입, 조회, 삭제가 적절히 정의되었고, PagingSource 반환으로 페이징 통합도 잘 구현되었습니다.


23-37: 구현체가 올바르게 작성되었습니다.

의존성 주입과 DAO 위임 패턴이 적절히 사용되었고, 엔티티 매핑도 정확합니다. NovelEntity::toDatabase 확장 함수 사용으로 코드가 간결합니다.


39-45: Dagger Hilt 모듈 설정이 적절합니다.

싱글톤 스코프로 바인딩되어 앱 전체에서 일관된 데이터 소스 인스턴스를 사용할 수 있습니다.

domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt (1)

16-16: 하드코딩된 사용자 ID를 검토해주세요.

기본값으로 userId: Long = 184가 하드코딩되어 있습니다. 프로덕션 코드에서는 현재 로그인된 사용자 ID를 동적으로 가져오거나, 필수 매개변수로 만드는 것이 적절합니다.

다음 스크립트로 이 use case의 사용 패턴을 확인해보겠습니다:

core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt (2)

11-14: 기존 메서드 확장이 적절합니다.

fragmentType 매개변수를 선택적으로 추가하여 하위 호환성을 유지하면서 기능을 확장했습니다.


18-26: 새로운 네비게이션 메서드들이 일관된 패턴을 따릅니다.

모든 메서드가 동일한 startActivity: (Intent) -> Unit 패턴을 사용하여 일관성을 유지하고, navigateToNovelDetailActivity는 필요한 novelId 매개변수를 적절히 포함했습니다.

feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (1)

53-59: 별점 토글 로직이 명확합니다.

동일한 별점을 다시 선택하면 0f로 리셋되는 토글 동작이 직관적이고 잘 구현되었습니다.

app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt (2)

19-19: NavigatorProvider 의존성 주입이 적절합니다.

중앙화된 네비게이션 관리를 위한 깔끔한 의존성 주입 구현입니다.

Also applies to: 35-36


210-226: 네비게이션 로직이 단순화되어 유지보수성이 향상되었습니다.

이전의 복잡한 매개변수 전달 방식에서 NavigatorProvider로 위임하는 방식으로 변경되어 결합도가 낮아지고 코드가 더 깔끔해졌습니다.

data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt (1)

1-59: 데이터 모델 구현이 깔끔합니다!

데이터 클래스와 변환 함수가 잘 구성되어 있습니다. 모든 필드가 올바르게 매핑되어 있고, 데이터베이스 레이어와 데이터 레이어 간의 변환이 명확하게 구현되어 있습니다.

app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt (1)

21-37: Compose 마이그레이션이 잘 구현되었습니다!

Activity가 Compose 기반으로 깔끔하게 마이그레이션되었습니다. Edge-to-edge 설정과 테마 적용, 네비게이션 콜백 설정이 모두 올바르게 구현되어 있습니다.

core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1)

35-43: Flow 구현이 효율적입니다!

JSON 디코딩을 위해 dispatcher를 사용하고 distinctUntilChanged()로 불필요한 emission을 방지하는 것은 좋은 구현입니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt (1)

49-84: 필터 UI 컴포넌트가 잘 구성되었습니다!

컴포저블 함수들이 적절하게 분리되어 있고, 재사용 가능한 구조로 잘 설계되었습니다. 스크롤 상태 관리와 테마 적용도 올바르게 구현되어 있습니다.

feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)

352-362: when 표현식을 exhaustive하게 만들어 컴파일 시점에 오류 감지

새로운 AttractivePoints 타입이 추가될 때 런타임 오류를 방지하기 위해 when 표현식을 exhaustive하게 만드세요.

@Composable
private fun attractivePointIcon(points: AttractivePointUiModel): ImageVector {
-    val resId = when (points.type) {
+    return when (points.type) {
         AttractivePoints.CHARACTER -> ic_library_character
         AttractivePoints.MATERIAL -> ic_library_material
         AttractivePoints.WORLDVIEW -> ic_library_world_view
         AttractivePoints.RELATIONSHIP -> ic_library_relationship
         AttractivePoints.VIBE -> ic_library_vibe
-    }
-    return ImageVector.vectorResource(id = resId)
+    }.let { ImageVector.vectorResource(id = it) }
}

Likely an incorrect or invalid review comment.

feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (1)

43-47: 스크롤 상태 관리 구현 확인

리스트와 그리드 뷰의 스크롤 상태를 별도로 관리하는 구현이 적절합니다. 탭 전환 시에도 각 뷰의 스크롤 위치가 유지됩니다.

app/src/main/java/com/into/websoso/ui/main/MainActivity.kt (1)

110-123: 서재 탭 재클릭 시 스크롤 초기화 구현 확인

PR 목표에 맞게 서재 탭을 다시 클릭했을 때 스크롤이 맨 위로 이동하는 기능이 올바르게 구현되었습니다.

feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (2)

57-58: 스크롤 상태 관리가 올바르게 구현됨

ViewModel에서 관리되는 listStategridState를 사용하여 탭 전환 시에도 스크롤 위치가 유지되도록 구현되었습니다.

Also applies to: 156-160, 164-168


59-62: BottomSheet 상태 변경 제한 확인 필요

confirmValueChange = { false }로 설정되어 있어 사용자가 스와이프로 BottomSheet를 닫을 수 없습니다. 의도적인 설정인지 확인이 필요합니다.

의도적으로 스와이프 닫기를 막은 것이라면 문제없지만, 그렇지 않다면 다음과 같이 수정하세요:

val bottomSheetState = rememberModalBottomSheetState(
    skipPartiallyExpanded = true,
-    confirmValueChange = { false },
+    confirmValueChange = { true },
)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (6)

1-61: 패키지 구조와 import 문이 잘 구성되어 있습니다.

필요한 Compose UI, Material3, 도메인 모델 및 리소스 imports가 체계적으로 정리되어 있고, 패키지 명명 규칙도 적절합니다.


62-131: 메인 LibraryFilterBottomSheet 컴포저블 구현이 우수합니다.

ModalBottomSheet을 사용하여 필터링 UI를 잘 구성했으며, 각 필터 섹션을 별도의 컴포저블로 분리하여 가독성과 유지보수성을 향상시켰습니다. 콜백 기반의 상태 관리 패턴도 적절합니다.


133-159: 헤더 컴포저블 구현이 적절합니다.

제목과 닫기 버튼을 포함한 헤더 레이아웃이 Material Design 패턴을 잘 따르고 있습니다.


253-303: 버튼 섹션 구현이 우수합니다.

초기화와 검색 버튼의 레이아웃과 스타일링이 Material Design 가이드라인을 잘 따르고 있으며, 시각적 계층구조도 명확합니다.


305-364: 별점 필터 그리드 구현이 적절합니다.

2x2 그리드 레이아웃으로 별점 옵션을 효과적으로 배치했습니다.


400-421: 프리뷰 구현이 적절합니다.

개발 시 UI 확인을 위한 프리뷰가 잘 설정되어 있습니다.

Comment on lines 34 to 38
if (fragmentType != null) {
val intent =
MainActivity.getIntent(context, MainActivity.FragmentType.valueOf(fragmentType))
startActivity(intent)
return
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

문자열-열거형 변환 시 예외 처리 필요

valueOf(fragmentType) 호출 시 잘못된 문자열이 전달되면 IllegalArgumentException이 발생할 수 있습니다. 안전한 변환을 위해 예외 처리나 유효성 검사를 추가하는 것을 고려해보세요.

다음과 같이 안전한 변환을 구현할 수 있습니다:

-                val intent =
-                    MainActivity.getIntent(context, MainActivity.FragmentType.valueOf(fragmentType))
+                val fragmentTypeEnum = try {
+                    MainActivity.FragmentType.valueOf(fragmentType)
+                } catch (e: IllegalArgumentException) {
+                    // 기본값 사용 또는 에러 처리
+                    return
+                }
+                val intent = MainActivity.getIntent(context, fragmentTypeEnum)
📝 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.

Suggested change
if (fragmentType != null) {
val intent =
MainActivity.getIntent(context, MainActivity.FragmentType.valueOf(fragmentType))
startActivity(intent)
return
if (fragmentType != null) {
val fragmentTypeEnum = try {
MainActivity.FragmentType.valueOf(fragmentType)
} catch (e: IllegalArgumentException) {
// 기본값 사용 또는 에러 처리
return
}
val intent = MainActivity.getIntent(context, fragmentTypeEnum)
startActivity(intent)
return
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt
around lines 34 to 38, the call to
MainActivity.FragmentType.valueOf(fragmentType) can throw an
IllegalArgumentException if fragmentType is invalid. To fix this, add a
try-catch block around the valueOf call to catch IllegalArgumentException and
handle it gracefully, or validate fragmentType against the enum values before
calling valueOf to ensure it is valid.

Comment on lines 49 to 52
fun resetScrollPosition() {
val viewModel: LibraryViewModel by viewModels()
viewModel.resetScrollPosition()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

ViewModel 인스턴스 불일치 문제

resetScrollPosition() 메서드에서 by viewModels() 델리게이트를 사용하여 새로운 ViewModel 인스턴스를 생성하고 있습니다. 이는 LibraryScreen 컴포저블에서 사용하는 ViewModel 인스턴스와 다른 인스턴스가 되어 스크롤 위치 초기화가 제대로 동작하지 않을 것입니다.

다음과 같이 수정하여 동일한 ViewModel 인스턴스를 사용하도록 해야 합니다:

+    private val libraryViewModel: LibraryViewModel by viewModels()
+
     fun resetScrollPosition() {
-        val viewModel: LibraryViewModel by viewModels()
-        viewModel.resetScrollPosition()
+        libraryViewModel.resetScrollPosition()
     }

또는 LibraryScreen 컴포저블에 ViewModel 인스턴스를 전달하는 방식으로 구현할 수 있습니다.

📝 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.

Suggested change
fun resetScrollPosition() {
val viewModel: LibraryViewModel by viewModels()
viewModel.resetScrollPosition()
}
// Use a single ViewModel instance for this Fragment
private val libraryViewModel: LibraryViewModel by viewModels()
fun resetScrollPosition() {
libraryViewModel.resetScrollPosition()
}
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt around
lines 49 to 52, the resetScrollPosition() method creates a new ViewModel
instance using by viewModels(), causing a mismatch with the ViewModel used in
LibraryScreen composable. To fix this, modify the method to use the existing
ViewModel instance shared with LibraryScreen, either by passing the ViewModel as
a parameter to this method or by accessing the same ViewModel instance through a
shared scope, ensuring the scroll position reset affects the correct ViewModel.

Comment on lines +229 to +236
fun valueOf(id: Int): FragmentType =
entries.find { it.resId == id }
?: throw IllegalArgumentException()

fun valueOf(fragmentName: String): FragmentType =
entries.find { it.name == fragmentName }
?: throw IllegalArgumentException()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리 시 의미 있는 메시지 추가

예외 생성 시 구체적인 오류 메시지를 포함하여 디버깅을 용이하게 하세요.

fun valueOf(id: Int): FragmentType =
    entries.find { it.resId == id }
-        ?: throw IllegalArgumentException()
+        ?: throw IllegalArgumentException("Unknown fragment resource id: $id")

fun valueOf(fragmentName: String): FragmentType =
    entries.find { it.name == fragmentName }
-        ?: throw IllegalArgumentException()
+        ?: throw IllegalArgumentException("Unknown fragment name: $fragmentName")
📝 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.

Suggested change
fun valueOf(id: Int): FragmentType =
entries.find { it.resId == id }
?: throw IllegalArgumentException()
fun valueOf(fragmentName: String): FragmentType =
entries.find { it.name == fragmentName }
?: throw IllegalArgumentException()
}
fun valueOf(id: Int): FragmentType =
entries.find { it.resId == id }
?: throw IllegalArgumentException("Unknown fragment resource id: $id")
fun valueOf(fragmentName: String): FragmentType =
entries.find { it.name == fragmentName }
?: throw IllegalArgumentException("Unknown fragment name: $fragmentName")
🧰 Tools
🪛 detekt (1.23.8)

[warning] 231-231: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)


[warning] 235-235: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)

🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/main/MainActivity.kt around lines 229
to 236, the IllegalArgumentException thrown when no matching FragmentType is
found lacks a descriptive message. Update both valueOf functions to include
meaningful error messages in the exceptions, specifying which id or fragmentName
caused the error to improve debugging clarity.

Comment on lines 5 to 11
class Converters {
@TypeConverter
fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",")

@TypeConverter
fun listToString(list: List<String>): String = list.joinToString(",")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

쉼표가 포함된 문자열 처리 시 데이터 손실 위험이 있습니다.

현재 구현은 리스트 항목에 쉼표가 포함된 경우 데이터 변환 과정에서 문제가 발생할 수 있습니다. 예를 들어, ["Hello, World", "Test"] 리스트가 "Hello, World,Test"로 변환되고, 다시 파싱할 때 ["Hello", " World", "Test"]가 되어 원본 데이터가 손실됩니다.

다음과 같은 개선 방안을 고려해보세요:

class Converters {
    @TypeConverter
-    fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",")
+    fun fromString(value: String): List<String> {
+        if (value.isEmpty()) return emptyList()
+        return try {
+            Json.decodeFromString<List<String>>(value)
+        } catch (e: Exception) {
+            // 레거시 쉼표 구분 방식에 대한 fallback
+            value.split(",").map { it.trim() }
+        }
+    }

    @TypeConverter
-    fun listToString(list: List<String>): String = list.joinToString(",")
+    fun listToString(list: List<String>): String = Json.encodeToString(list)
}

또는 이스케이프 처리를 통한 해결:

    @TypeConverter
-    fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",")
+    fun fromString(value: String): List<String> {
+        if (value.isEmpty()) return emptyList()
+        return value.split("\\|\\|").map { it.replace("\\|", ",") }
+    }

    @TypeConverter
-    fun listToString(list: List<String>): String = list.joinToString(",")
+    fun listToString(list: List<String>): String = 
+        list.map { it.replace(",", "\\|") }.joinToString("||")

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In core/database/src/main/java/com/into/websoso/core/database/Converters.kt
lines 5 to 11, the current string-to-list and list-to-string conversion using
commas fails when list items contain commas, causing data loss. To fix this,
implement an escaping mechanism for commas in list items during serialization
and unescaping during deserialization, or use a more robust serialization format
like JSON to safely encode and decode the list without losing data.

Comment on lines 9 to 11
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class MyLibraryFilterDataStore
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

internal 한정자 사용 시 모듈 간 DI 주입 불가 가능성

Qualifier가 다른 모듈(data, feature 등)에서 사용된다면 internal 범위는 접근을 막아 컴파일 오류가 납니다.
외부 모듈에서 주입해야 한다면 한정자를 public(생략)으로 수정해 주세요.

-internal annotation class MyLibraryFilterDataStore
+annotation class MyLibraryFilterDataStore
🤖 Prompt for AI Agents
In
core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt
at lines 9 to 11, the qualifier annotation class is marked as internal, which
restricts its visibility and prevents dependency injection across different
modules. To fix this, remove the internal modifier to make the annotation
public, allowing it to be accessed and used for DI in other modules.

Comment on lines 9 to 13
companion object {
fun valueOf(name: String): ReadStatus =
entries.find { readStatus -> readStatus.name == name }
?: throw IllegalArgumentException()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 메시지 추가 및 함수명 개선이 필요합니다.

정적 분석 도구의 경고가 유효합니다. 다음 개선사항들을 고려해주세요:

  1. IllegalArgumentException에 의미있는 메시지 추가
  2. 기본 enum valueOf와 혼동을 피하기 위해 함수명 변경
 companion object {
-    fun valueOf(name: String): ReadStatus =
+    fun fromName(name: String): ReadStatus =
         entries.find { readStatus -> readStatus.name == name }
-            ?: throw IllegalArgumentException()
+            ?: throw IllegalArgumentException("Unknown ReadStatus: $name")
 }
🧰 Tools
🪛 detekt (1.23.8)

[warning] 12-12: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)

🤖 Prompt for AI Agents
In
domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt
around lines 9 to 13, the custom valueOf function throws an
IllegalArgumentException without a message and its name conflicts with the
default enum valueOf method. Rename the function to a more descriptive name like
fromName or findByName, and update the exception to include a meaningful message
indicating the invalid name input.

Comment on lines 130 to 145
@Composable
private fun ReadStatusBadge(
readStatus: ReadStatusUiModel,
modifier: Modifier = Modifier,
) {
Text(
text = readStatus.label,
color = White,
style = WebsosoTheme.typography.label2,
modifier = modifier
.background(
color = readStatus.backgroundColor,
shape = RoundedCornerShape(4.dp),
).padding(horizontal = 8.dp, vertical = 4.dp),
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ReadStatusBadge 구현의 일관성을 확인해주세요.

이 파일의 ReadStatusBadgeLibraryListItem.kt의 동일한 함수 사이에 구현 차이가 있습니다. 현재 구현은 더 간단하지만, 일관성을 위해 동일한 구현을 사용하는 것을 고려해보세요.

LibraryListItem.kt의 구현과 통일하거나, 공통 컴포넌트로 추출하는 것을 권장합니다:

// 공통 컴포넌트로 추출
@Composable
internal fun ReadStatusBadge(
    readStatus: ReadStatusUiModel?,
    modifier: Modifier = Modifier,
    width: Dp? = null,
) {
    if (readStatus != null) {
        val badgeModifier = if (width != null) {
            modifier.width(width)
        } else modifier
        
        Box(
            modifier = badgeModifier
                .background(
                    color = readStatus.backgroundColor,
                    shape = RoundedCornerShape(4.dp),
                )
                .padding(horizontal = 8.dp, vertical = 4.dp),
            contentAlignment = Alignment.Center,
        ) {
            Text(
                text = readStatus.label,
                color = White,
                style = WebsosoTheme.typography.label2,
            )
        }
    }
}
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt
around lines 130 to 145, the ReadStatusBadge composable implementation differs
from the one in LibraryListItem.kt, causing inconsistency. To fix this, refactor
ReadStatusBadge to match the implementation in LibraryListItem.kt by accepting a
nullable ReadStatusUiModel, optionally applying a width modifier, wrapping the
Text inside a Box with background and padding, and centering the content.
Alternatively, extract this as a shared common component used by both files to
ensure consistency.

Comment on lines 158 to 180
private fun ReadStatusBadge(
readStatus: ReadStatusUiModel?,
width: Dp,
) {
if (readStatus != null) {
Box(
modifier = Modifier
.width(width)
.background(
color = readStatus.backgroundColor,
shape = RoundedCornerShape(8.dp),
)
.padding(vertical = 4.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = readStatus.label,
color = White,
style = WebsosoTheme.typography.label2,
)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ReadStatusBadge 구현의 일관성 개선 필요

LibraryGridListItem.ktReadStatusBadge와 구현이 다릅니다. 두 구현체는 파라미터 타입(nullable vs non-nullable), 코너 반경(8.dp vs 4.dp), 패딩 방식이 다릅니다.

공통 컴포넌트로 추출하여 재사용하는 것을 고려해보세요:

-@Composable
-private fun ReadStatusBadge(
-    readStatus: ReadStatusUiModel?,
-    width: Dp,
-) {
-    if (readStatus != null) {
-        Box(
-            modifier = Modifier
-                .width(width)
-                .background(
-                    color = readStatus.backgroundColor,
-                    shape = RoundedCornerShape(8.dp),
-                )
-                .padding(vertical = 4.dp),
-            contentAlignment = Alignment.Center,
-        ) {
-            Text(
-                text = readStatus.label,
-                color = White,
-                style = WebsosoTheme.typography.label2,
-            )
-        }
-    }
-}

core.designsystem 모듈에 공통 컴포넌트를 만들고:

// In core.designsystem module
@Composable
fun ReadStatusBadge(
    readStatus: ReadStatusUiModel,
    modifier: Modifier = Modifier,
    cornerRadius: Dp = 8.dp,
) {
    Box(
        modifier = modifier
            .background(
                color = readStatus.backgroundColor,
                shape = RoundedCornerShape(cornerRadius),
            )
            .padding(horizontal = 8.dp, vertical = 4.dp),
        contentAlignment = Alignment.Center,
    ) {
        Text(
            text = readStatus.label,
            color = White,
            style = WebsosoTheme.typography.label2,
        )
    }
}
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt
around lines 158 to 180, the ReadStatusBadge implementation differs from the one
in LibraryGridListItem.kt in parameter nullability, corner radius, and padding.
To fix this, refactor both implementations into a single reusable composable in
the core.designsystem module with non-nullable readStatus, a Modifier parameter,
and a cornerRadius parameter defaulting to 8.dp. Update usages to use this
common component with consistent styling and padding as suggested.

Comment on lines 18 to 44
internal fun LibraryTopBar(onSearchClick: () -> Unit = {}) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = library_title),
style = WebsosoTheme.typography.headline1,
color = Black,
)
/*
TODO: 추후 검색 기능 구현 시 롤백
IconButton(onClick = onSearchClick) {
Image(
imageVector = ImageVector.vectorResource(id = ic_common_search),
contentDescription = "검색",
modifier = Modifier.size(24.dp),
)
}
}
*/
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

레이아웃 구성 최적화 필요

현재 SpaceBetween 정렬을 사용하지만 실제로는 하나의 자식만 표시되어 의미가 없습니다. 검색 기능이 구현될 때까지 더 간단한 구성을 사용하는 것이 좋겠습니다.

@Composable
-internal fun LibraryTopBar(onSearchClick: () -> Unit = {}) {
+internal fun LibraryTopBar() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 20.dp, vertical = 10.dp),
-       horizontalArrangement = Arrangement.SpaceBetween,
+       horizontalArrangement = Arrangement.Start,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = stringResource(id = library_title),
            style = WebsosoTheme.typography.headline1,
            color = Black,
        )
-       /*
-       TODO: 추후 검색 기능 구현 시 롤백
-       ... commented code ...
-       */
    }
}

주석 처리된 코드는 TODO 이슈로 분리하거나 제거하는 것이 좋습니다.

📝 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.

Suggested change
internal fun LibraryTopBar(onSearchClick: () -> Unit = {}) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = library_title),
style = WebsosoTheme.typography.headline1,
color = Black,
)
/*
TODO: 추후 검색 기능 구현 시 롤백
IconButton(onClick = onSearchClick) {
Image(
imageVector = ImageVector.vectorResource(id = ic_common_search),
contentDescription = "검색",
modifier = Modifier.size(24.dp),
)
}
}
*/
}
}
@Composable
internal fun LibraryTopBar() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = library_title),
style = WebsosoTheme.typography.headline1,
color = Black,
)
}
}
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryTopBar.kt
between lines 18 and 44, the Row uses SpaceBetween arrangement but only has one
visible child, making the layout ineffective. Simplify the layout by removing
the SpaceBetween arrangement and use a simpler alignment until the search
feature is implemented. Also, remove or move the commented-out search IconButton
code to a separate TODO issue or file instead of keeping it commented here.

Comment on lines 54 to 80
viewModelScope.launch {
libraryRepository.myLibraryFilter.collect { myFilter ->
if (myFilter != null) {
_uiState.update { uiState ->
uiState.copy(
isInterested = myFilter.isInterest ?: false,
libraryFilterUiState = uiState.libraryFilterUiState.copy(
readStatuses = uiState.libraryFilterUiState.readStatuses +
(
myFilter.readStatuses?.map {
ReadStatus.valueOf(it) to true
} ?: emptyList()
),
attractivePoints = uiState.libraryFilterUiState.attractivePoints +
(
myFilter.attractivePoints?.map {
AttractivePoints.valueOf(it) to true
} ?: emptyList()
),
novelRating = myFilter.novelRating ?: 0f,
),
)
}
}
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

필터 상태 업데이트 시 중복 항목 방지 필요

readStatusesattractivePoints 업데이트 시 기존 리스트에 추가하는 방식으로 인해 중복 항목이 발생할 수 있습니다.

libraryFilterUiState = uiState.libraryFilterUiState.copy(
-    readStatuses = uiState.libraryFilterUiState.readStatuses +
-        (
-            myFilter.readStatuses?.map {
-                ReadStatus.valueOf(it) to true
-            } ?: emptyList()
-        ),
+    readStatuses = myFilter.readStatuses?.associate {
+        ReadStatus.valueOf(it) to true
+    } ?: emptyMap(),
-    attractivePoints = uiState.libraryFilterUiState.attractivePoints +
-        (
-            myFilter.attractivePoints?.map {
-                AttractivePoints.valueOf(it) to true
-            } ?: emptyList()
-        ),
+    attractivePoints = myFilter.attractivePoints?.associate {
+        AttractivePoints.valueOf(it) to true
+    } ?: emptyMap(),
     novelRating = myFilter.novelRating ?: 0f,
),
📝 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.

Suggested change
viewModelScope.launch {
libraryRepository.myLibraryFilter.collect { myFilter ->
if (myFilter != null) {
_uiState.update { uiState ->
uiState.copy(
isInterested = myFilter.isInterest ?: false,
libraryFilterUiState = uiState.libraryFilterUiState.copy(
readStatuses = uiState.libraryFilterUiState.readStatuses +
(
myFilter.readStatuses?.map {
ReadStatus.valueOf(it) to true
} ?: emptyList()
),
attractivePoints = uiState.libraryFilterUiState.attractivePoints +
(
myFilter.attractivePoints?.map {
AttractivePoints.valueOf(it) to true
} ?: emptyList()
),
novelRating = myFilter.novelRating ?: 0f,
),
)
}
}
}
}
}
viewModelScope.launch {
libraryRepository.myLibraryFilter.collect { myFilter ->
if (myFilter != null) {
_uiState.update { uiState ->
uiState.copy(
isInterested = myFilter.isInterest ?: false,
libraryFilterUiState = uiState.libraryFilterUiState.copy(
readStatuses = myFilter.readStatuses
?.associate { ReadStatus.valueOf(it) to true }
?: emptyMap(),
attractivePoints = myFilter.attractivePoints
?.associate { AttractivePoints.valueOf(it) to true }
?: emptyMap(),
novelRating = myFilter.novelRating ?: 0f,
),
)
}
}
}
}
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt
between lines 54 and 80, the update logic for readStatuses and attractivePoints
appends new items to existing lists without checking for duplicates, causing
duplicate entries. Modify the code to merge the existing list and new items into
a set or use a distinct operation to ensure no duplicates before updating the
state.

@yeonjeen yeonjeen changed the base branch from develop to feat/704 July 21, 2025 11:07
Copy link
Member

@s9hn s9hn left a comment

Choose a reason for hiding this comment

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

어려운 테스크였는데 고생했습니다!
다만 코드 수정은 필요해보여요!
FragmentManager를 통해 Fragment를 활용할 수 있는 구조로 변경하고,
객체간 역할과 책임을 고민해보면 도움이 될 것 같아요

Comment on lines 44 to 50
private val fragmentTags = mapOf(
menu_home to HomeFragment::class.java.name,
menu_explore to ExploreFragment::class.java.name,
menu_feed to FeedFragment::class.java.name,
menu_library to LibraryFragment::class.java.name,
menu_my_page to MyPageFragment::class.java.name,
)
Copy link
Member

Choose a reason for hiding this comment

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

r: 각 프래그먼트에서 관리하면 좋을 것 같아요!

Comment on lines 93 to 106
private fun setupInitialFragment() {
val initialItemId = menu_home
val initialTag = fragmentTags[initialItemId]!!
val initialFragment = findOrCreateFragment(initialTag)

if (!initialFragment.isAdded) {
supportFragmentManager
.beginTransaction()
.add(fcv_main, initialFragment, initialTag)
.commit()
}

currentFragment = initialFragment
}
Copy link
Member

Choose a reason for hiding this comment

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

r:
각 프래그먼트에 선언된 태그를 사용해 initialFragment를 가져오는 식으로 수정해보면 좋을 것 같아요
추가로 제공되는 fragment KTX DSL을 이용해주세요!

supportFragmentManager.commit {
        setReorderingAllowed(true)
        if (!initialFragment.isAdded) {
            add(R.id.fcv_main, initialFragment, initialTag)
            ...
            // 로직에 맞게
        }
    }

내부에 currentFragment를 직접 관리하는 것보다, supportFragmentManager에게 현재 프래그먼트를 요청해주세요

Comment on lines 125 to 141
private fun replaceCurrentFragment(itemId: Int) {
val tag = fragmentTags[itemId]!!
val targetFragment = findOrCreateFragment(tag)

val transaction = supportFragmentManager.beginTransaction()

currentFragment?.let { transaction.hide(it) }

if (!targetFragment.isAdded) {
transaction.add(fcv_main, targetFragment, tag)
} else {
transaction.show(targetFragment)
}

transaction.commit()
currentFragment = targetFragment
}
Copy link
Member

Choose a reason for hiding this comment

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

r: 위랑 동일합니다! fragment dsl로 변경해주세요!

Comment on lines 143 to 153
private fun findOrCreateFragment(tag: String): Fragment =
supportFragmentManager.findFragmentByTag(tag)
?: when (tag) {
HomeFragment::class.java.name -> HomeFragment()
ExploreFragment::class.java.name -> ExploreFragment()
FeedFragment::class.java.name -> FeedFragment()
LibraryFragment::class.java.name -> LibraryFragment()
MyPageFragment::class.java.name -> MyPageFragment()
else -> throw IllegalArgumentException("Unknown fragment tag: $tag")
}

Copy link
Member

Choose a reason for hiding this comment

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

c: 여기도 각 프래그먼트에 정의된 태그를 기반으로 !


private fun setupBottomNavListener() {
binding.bnvMain.setOnItemSelectedListener { item ->
if (item.itemId == currentSelectedItemId && item.itemId == menu_library) {
Copy link
Member

Choose a reason for hiding this comment

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

c: currentSelectedItemId 로 내부 상태를 이용하기보다 supportFragmentManager를 통해 조회 및 비교해보시죠
만약 현재 프래그먼트가 라이브러리가 맞다면, 굳이 아래에 libraryFragment를 가져오지 않고 바로 supportFragmentManager를 통해 불러와도 될 것 같아요

LibraryFragment::class.java.name,
) as? LibraryFragment

libraryFragment?.resetScrollPosition()
Copy link
Member

@s9hn s9hn Jul 21, 2025

Choose a reason for hiding this comment

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

c: fragment로 함수를 직접 호출하는 것보단, 메시지를 전달하는 구조가 좋을 것 같아요.
FragmentManager를 통해 Bundle 메시지 전달을 위임해도 좋아보입니다!

// 송신
supportFragmentManager.setFragmentResult( 키값, 번들.Empty)

// 수신
parentFragmentManager.setFragmentResultListener (키값, viewLifecycleowner) {
 위로 스크롤
}

이렇게 구현하면, libraryFragment를 직접 find할 필요도 없겠네요 !

Comment on lines 49 to 52
fun resetScrollPosition() {
val viewModel: LibraryViewModel by viewModels()
viewModel.resetScrollPosition()
}
Copy link
Member

Choose a reason for hiding this comment

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

r: 코드래빗 말대로 함수호출마다 매번 뷰모델을 생성하고 있어요
Fragment API를 이용해봅시다

Copy link
Member

Choose a reason for hiding this comment

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

이 Fragment와 LibraryScreen이 같은 함수를 공유해야하니, viewModel을 매번 생성하는 것보다 Fragment에서 생성하고 Screen에 주입하는 구조가 좋을 것 같습니다!

Comment on lines 43 to 48
var listState by mutableStateOf(LazyListState())
private set

var gridState by mutableStateOf(LazyGridState())
private set

Copy link
Member

Choose a reason for hiding this comment

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

r: mutableStateOf는 Compose API입니다! 뷰모델보단 뷰에 어울리는 의존성이예요

Comment on lines 103 to 111
fun resetScrollPosition() {
viewModelScope.launch {
if (uiState.value.isGrid) {
gridState.scrollToItem(0)
} else {
listState.scrollToItem(0)
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

r: 이벤트를 전파하고, 뷰에서 이를 감지해 동작하도록 구현해도 좋겠어요

)

LaunchedEffect(Unit) {
libraryViewModel.scrollToTopEvent.collect {
Copy link
Member

Choose a reason for hiding this comment

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

r: 현재 코드는 백그라운드에서도 수집을 계속해요!

LaunchedEffect(Unit) {
libraryViewModel.scrollToTopEvent.collect {
if (uiState.isGrid) {
gridState.scrollToItem(0)
Copy link
Member

Choose a reason for hiding this comment

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

a: 이런건 상수화하는 편입니다 '이동할포지션'이란 뜻으로?

var gridState by mutableStateOf(LazyGridState())
private set
private val _scrollToTopEvent = MutableSharedFlow<Unit>()
val scrollToTopEvent: SharedFlow<Unit> = _scrollToTopEvent
Copy link
Member

Choose a reason for hiding this comment

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

c: LoginViewModel 참고해서 Channel로 바꿔주세요 !

class MainActivity : BaseActivity<ActivityMainBinding>(activity_main) {
private val mainViewModel: MainViewModel by viewModels()
private var backPressedTime: Long = 0L
private var currentFragment: Fragment? = null
Copy link
Member

Choose a reason for hiding this comment

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

c: 근데 이 프로퍼티는 어디에 사용하나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

replaceCurrentFragment 함수에서 사용되고 있어요!

Comment on lines 76 to 82
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
libraryViewModel.scrollToTopEvent.collect {
if (uiState.isGrid) {
gridState.scrollToItem(SCROLL_POSITION_TOP)
} else {
listState.scrollToItem(SCROLL_POSITION_TOP)
}
Copy link
Member

Choose a reason for hiding this comment

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

r: 저희 core모듈 확장함수에 collectAsEventWithLifecycle 있습니다!

@yeonjeen yeonjeen merged commit 8b6228d into feat/704 Jul 24, 2025
1 check passed
@m6z1 m6z1 deleted the feat/725 branch August 11, 2025 15:07
@coderabbitai coderabbitai bot mentioned this pull request Sep 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍯 [FEAT] 새로운 기능을 개발합니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 서재UI/UX 개편 (11) - 탭 변경 시에도 스크롤 유지되도록 기능 구현

3 participants