Skip to content

Conversation

@s9hn
Copy link
Member

@s9hn s9hn commented Jul 22, 2025

📌𝘐𝘴𝘴𝘶𝘦𝘴

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

  • 서재 필터를 저장 및 조회하는 기능을 구현합니다.

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵

Case 1 Case 2 Case 3
최신순 -> 오래된순 필터 지정 관심 작품 모아보기

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

중간중간 급히 QA를 진행해 PR 사이즈가 좀 큽니다! 양해바랍니다 ㅠ.ㅠ

image 3928

어느정도 Compose + Paging3 + Room 구조가 잡힌 것 같아요
아직 최적화는 안되어 있지만, 기능은 정상동작합니다!

뷰 컴포넌트, 뷰 보델, 데이터 레이어까지 Float 타입을 비교할 일이 많아서 :core:common에 확장함수로 부동소수 확장함수를 구현해놓았습니다!

fun Float.isCloseTo(
    target: Float,
    epsilon: Float = 0.01f,
): Boolean = kotlin.math.abs(this - target) < epsilon

Summary by CodeRabbit

  • 신규 기능

    • 라이브러리 필터 바텀시트 UI 및 컴포넌트 추가(읽기 상태, 매력 요소, 평점 등 다양한 필터 지원)
    • 필터링된 소설 목록 페이징 지원 및 관련 데이터베이스/로컬 데이터 구조 도입
    • 라이브러리 목록 필터링, 정렬, 관심 작품 보기 등 기능 개선
  • 기능 개선

    • 라이브러리 화면 및 ViewModel 구조 개선: 필터 상태 관리 일원화, 스크롤 상태 관리 추가, 정렬 방식 단순화
    • 필터 적용 및 초기화 동작 개선, 바텀시트와의 연동 강화
    • 데이터 및 네트워크 계층에서 필터 파라미터 타입 및 전달 방식 개선
  • 버그 수정

    • 일부 네트워크/DB 엔티티 필드의 널 처리 및 타입 일관성 개선
  • 리팩터링/정리

    • 불필요한 코드, 미사용 파일 및 파라미터 정리
    • 내부 구조 단순화 및 네이밍 일관성 향상
  • 문서/스타일

    • UI 컴포넌트 프리뷰 추가 및 코드 스타일 개선

이번 업데이트로 라이브러리 필터링 경험과 사용성이 크게 향상됩니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 22, 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

이 변경 사항은 서재(라이브러리) 필터 조회 기능을 대대적으로 구현하고, 관련 UI/UX 및 데이터 흐름을 리팩터링합니다. 필터링된 소설 목록을 위한 데이터베이스, DAO, 리포지토리, RemoteMediator, 유즈케이스, 뷰모델, 컴포저블 UI가 추가/변경되었으며, 기존의 필터 상태 관리 및 화면 구조도 대폭 개선되었습니다.

Changes

파일/그룹 변경 요약
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt 프래그먼트 네비게이션 로직을 show/hide 방식으로 전환, FragmentType enum 리팩터링, 상태 추적 로직 추가
app/src/main/java/com/into/websoso/ui/main/MainViewModel.kt, data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt AccountRepository 주입 및 userId 갱신 메서드 추가
app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt resetScrollPosition() 메서드 추가
core/database/... 필터링된 소설 엔티티, DAO, DB 접근자, Hilt 모듈 등 추가 및 Room DB 엔티티/DAO 확장
core/datastore/... 필터 파라미터 구조 및 저장 방식 Map/Float 기반으로 변경, 관련 매퍼 삭제, 기본값 및 타입 정비
core/network/... NovelResponseDto의 readStatus를 nullable로 변경, 파라미터 동적화 등
data/library/build.gradle.kts data.account 모듈 의존성 추가
data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt 필터 기반 페이징 메서드 신설, 기존 메서드 파라미터 단순화, 필터 저장 방식 변경
data/library/src/main/java/com/into/websoso/data/library/datasource/FilteredLibraryLocalDataSource.kt 필터링 소설용 로컬 데이터소스/구현/Hilt 모듈 신설
data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt 필터 기반 RemoteMediator 신설, 페이징 및 필터 파라미터 처리
data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt 필터 파라미터 Map 기반으로 변경, 불필요 필드 제거
data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt DB/필터 DB 변환 함수 추가, 기존 확장 함수 정비
domain/library/src/main/java/com/into/websoso/domain/library/GetLibraryUseCase.kt GetLibraryUseCase 신설, 필터 파라미터에 따라 전체/필터 조회 분기
feature/library/src/main/java/com/into/websoso/feature/filter/... 필터 바텀시트, 각종 필터 UI 컴포저블, 헤더/버튼/그리드 등 신설
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt, LibraryViewModel.kt 필터 상태·이벤트 관리 로직을 ViewModel/컴포저블로 분리, 페이징 흐름 개선, 스크롤 상태 관리 추가
feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt 정렬/관심 필터 UI 파라미터 단순화, 관심 여부 상태 위치 변경
feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt 관심 여부 상태를 상위에서 필터 UI 상태로 이동
feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt 외부 SortType 의존성 제거, enum 단순화 및 valueOf 메서드 변경
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt 기존 필터 바텀시트 UI 전체 삭제, 새로운 컴포저블로 대체
기타 UI/매퍼 파일 패키지명 정정, 미미한 리팩터링, 프리뷰 추가 등

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LibraryScreen
    participant LibraryFilterViewModel
    participant LibraryRepository
    participant FilteredNovelRemoteMediator
    participant FilteredLibraryLocalDataSource

    User->>LibraryScreen: 필터 바텀시트 오픈/필터 선택
    LibraryScreen->>LibraryFilterViewModel: 필터 상태 변경
    LibraryFilterViewModel->>LibraryRepository: getFilteredLibrary(필터 파라미터)
    LibraryRepository->>FilteredNovelRemoteMediator: load(필터, 페이징)
    FilteredNovelRemoteMediator->>FilteredLibraryLocalDataSource: insert/selectAllNovels
    FilteredLibraryLocalDataSource-->>LibraryRepository: PagingSource 반환
    LibraryRepository-->>LibraryFilterViewModel: PagingData<NovelEntity>
    LibraryFilterViewModel-->>LibraryScreen: UI 상태/목록 갱신
Loading

Estimated code review effort

5 (~130분)

Possibly related PRs

  • #697: Room DB 및 DAO, 엔티티 초기 셋업 PR로, 본 PR의 필터 DB 확장 및 데이터 흐름과 밀접하게 연결됨.
  • #723: MainActivity의 프래그먼트 네비게이션 및 enum 처리 개선 관련 PR로, 본 PR의 MainActivity 리팩터링과 코드 수준에서 관련 있음.

Suggested reviewers

  • junseo511
  • yeonjeen
  • m6z1

Poem

🐇
필터 바람 따라 소설을 골라,
바텀시트 열고 토글을 눌러.
데이터 흐름은 새로이 흐르고,
토끼는 기뻐 깡총깡총 춤추네!
서재의 봄, UX는 한층 더 밝아졌어요.

— 우쏘 소프트웨어 토끼

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

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.

@github-actions github-actions bot requested review from junseo511, m6z1 and yeonjeen July 22, 2025 01:34
@s9hn s9hn changed the title Feat/712 feat: 서재UI/UX 개편 (9) - 서재 필터 조회 기능 구현 Jul 22, 2025
@s9hn s9hn added 🍯 [FEAT] 새로운 기능을 개발합니다. ⚖️ 재상 세훈 전지적 세훈 시점 labels Jul 22, 2025
@s9hn
Copy link
Member Author

s9hn commented Jul 22, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jul 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

1 similar comment
@coderabbitai
Copy link

coderabbitai bot commented Jul 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@s9hn s9hn added the ❌[DO NOT MERGE] 머지를 하면 안됩니다. label Jul 22, 2025
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: 11

🧹 Nitpick comments (10)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetHeader.kt (1)

46-47: 접근성 개선 제안

취소 아이콘에 대한 contentDescription을 추가하는 것을 고려해보세요.

Icon(
    imageVector = ImageVector.vectorResource(id = ic_cancel_modal),
-   contentDescription = null,
+   contentDescription = "필터 닫기",
)
data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt (1)

44-60: 코드 중복을 개선할 수 있습니다.

toNovelDatabase()toFilteredNovelDatabase() 메소드가 동일한 필드 매핑 로직을 가지고 있어 중복 코드가 발생하고 있습니다.

공통 매핑 로직을 추출하여 중복을 줄일 수 있습니다:

+    private fun toCommonFields() = mapOf(
+        "userNovelId" to userNovelId,
+        "novelId" to novelId,
+        "title" to title,
+        // ... 나머지 필드들
+    )
+
     internal fun toFilteredNovelDatabase(): InDatabaseFilteredNovelEntity =
         InDatabaseFilteredNovelEntity(
-            userNovelId = userNovelId,
-            novelId = novelId,
-            title = title,
-            novelImage = novelImage,
-            novelRating = novelRating,
-            readStatus = readStatus,
-            isInterest = isInterest,
-            userNovelRating = userNovelRating,
-            attractivePoints = attractivePoints,
-            startDate = startDate,
-            endDate = endDate,
-            keywords = keywords,
-            myFeeds = myFeeds,
+            userNovelId = userNovelId,
+            novelId = novelId,
+            title = title,
+            novelImage = novelImage,
+            novelRating = novelRating,
+            readStatus = readStatus,
+            isInterest = isInterest,
+            userNovelRating = userNovelRating,
+            attractivePoints = attractivePoints,
+            startDate = startDate,
+            endDate = endDate,
+            keywords = keywords,
+            myFeeds = myFeeds,
         )
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt (1)

40-82: 하드코딩된 평점 값을 설정 가능하게 만들어보세요.

현재 평점 임계값들(3.5, 4.0, 4.5, 4.8)이 하드코딩되어 있습니다. 향후 요구사항 변경에 유연하게 대응하기 어려울 수 있습니다.

평점 옵션을 파라미터로 받도록 개선할 수 있습니다:

 @Composable
 internal fun LibraryFilterBottomSheetNovelRatingGrid(
     selectedRating: Float,
     onRatingClick: (rating: Float) -> Unit,
+    ratingOptions: List<Float> = listOf(3.5f, 4.0f, 4.5f, 4.8f),
 ) {
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetButtons.kt (1)

53-62: 접근성 개선을 고려해보세요.

아이콘과 텍스트에 적절한 접근성 설명이 필요할 수 있습니다.

접근성을 위해 다음과 같이 개선할 수 있습니다:

             Icon(
                 imageVector = ImageVector.vectorResource(id = ic_library_reset),
-                contentDescription = null,
+                contentDescription = "필터 초기화",
                 modifier = Modifier.size(size = 14.dp),
             )

또한 전체 버튼에 의미론적 설명을 추가할 수 있습니다:

             modifier = Modifier
                 .fillMaxHeight()
                 .background(color = Gray50)
                 .clickable { onResetClick() }
+                .semantics { contentDescription = "필터 설정 초기화" }
data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt (1)

41-44: 필터 파라미터 변환 로직을 개선할 수 있습니다.

조건부 null 변환 로직이 일관성 있게 처리되었지만, 더 명확하게 표현할 수 있습니다.

다음과 같이 리팩터링을 고려해보세요:

-                isInterest = if (!isInterested) null else true,
-                readStatuses = readStatuses.ifEmpty { null },
-                attractivePoints = attractivePoints.ifEmpty { null },
-                novelRating = if (novelRating == 0f) null else novelRating,
+                isInterest = isInterested.takeIf { it },
+                readStatuses = readStatuses.takeIf { it.isNotEmpty() },
+                attractivePoints = attractivePoints.takeIf { it.isNotEmpty() },
+                novelRating = novelRating.takeIf { it != 0f },
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetAttractivePoints.kt (2)

39-47: 하드코딩된 값들을 상수로 추출하는 것을 고려해보세요.

horizontalPadding = 12.dpiconSize = 36.dp가 반복되므로 상수로 추출하면 유지보수성이 향상됩니다.

다음과 같이 상수를 정의하세요:

+private val ITEM_HORIZONTAL_PADDING = 12.dp
+private val ITEM_ICON_SIZE = 36.dp
+
 @SuppressLint("ResourceType")
 @Composable
 internal fun LibraryFilterBottomSheetAttractivePoints(
     attractivePoints: Map<AttractivePoints, Boolean>,
     onAttractivePointClick: (AttractivePoints) -> Unit,
 ) {
     Row(
         horizontalArrangement = Arrangement.SpaceBetween,
         verticalAlignment = Alignment.CenterVertically,
         modifier = Modifier.fillMaxWidth(),
     ) {
         LibraryFilterBottomSheetClickableItem(
             icon = ic_library_world_view,
             iconTitle = "세계관",
-            horizontalPadding = 12.dp,
-            iconSize = 36.dp,
+            horizontalPadding = ITEM_HORIZONTAL_PADDING,
+            iconSize = ITEM_ICON_SIZE,
             isSelected = attractivePoints[WORLDVIEW] ?: false,
             onClick = { onAttractivePointClick(WORLDVIEW) },
         )
         // 다른 아이템들도 동일하게 적용
     }
 }

83-86: 프리뷰에서 더 의미있는 데이터를 사용해보세요.

현재 빈 맵을 사용하고 있는데, 일부 선택된 상태를 포함한 샘플 데이터로 프리뷰하면 더 유용할 것입니다.

 LibraryFilterBottomSheetAttractivePoints(
-    attractivePoints = mapOf(),
+    attractivePoints = mapOf(
+        WORLDVIEW to true,
+        CHARACTER to false,
+        MATERIAL to true,
+        RELATIONSHIP to false,
+        VIBE to false,
+    ),
     onAttractivePointClick = { },
 )
feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (1)

100-101: 주석을 더 구체적으로 작성하세요

"정렬 레포지토리 업데이트"와 "관심도 로컬ㄹ 쿼리 업데이트" 주석이 불완전합니다. TODO 주석인 경우 명확히 표시하고, 구현이 필요한 내용을 구체적으로 작성하세요.

-// 정렬 레포지토리 업데이트
+// TODO: 정렬 기준 변경을 레포지토리에 저장
-// 관심도 로컬ㄹ 쿼리 업데이트
+// TODO: 관심도 필터 변경을 로컬 데이터베이스에 저장

Also applies to: 104-105

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

86-86: SuppressLint 어노테이션을 더 구체적으로 지정하세요

@SuppressLint("CommitTransaction")이 너무 포괄적입니다. 실제로 억제하려는 경고를 명확히 하거나, 가능하면 경고를 해결하는 것이 좋습니다.

commitNow()를 사용하거나 commit() 대신 commitAllowingStateLoss()를 고려해보세요.

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

37-49: 페이지 크기 설정 개선 제안

현재 페이지 크기가 20으로 하드코딩되어 있습니다. 향후 유연성을 위해 상수로 정의하거나 설정 가능하도록 개선하는 것을 고려해보세요.

+    companion object {
+        private const val DEFAULT_PAGE_SIZE = 20
+        private const val FILTERED_PAGE_SIZE = 40
+    }
+
     @OptIn(ExperimentalPagingApi::class)
     fun getLibrary(): Flow<PagingData<NovelEntity>> =
         Pager(
-            config = PagingConfig(pageSize = 20),
+            config = PagingConfig(pageSize = DEFAULT_PAGE_SIZE),
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 41fc9aa and 91daa7d.

📒 Files selected for processing (42)
  • app/src/main/java/com/into/websoso/ui/main/MainActivity.kt (5 hunks)
  • app/src/main/java/com/into/websoso/ui/main/MainViewModel.kt (3 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt (2 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (2 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/dao/FilteredNovelDao.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseFilteredNovelEntity.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 (0 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt (1 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/KakaoLoginResponseDto.kt (1 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/datasource/library/DefaultLibraryDataSource.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/UserNovelsResponseDto.kt (1 hunks)
  • data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (1 hunks)
  • data/library/build.gradle.kts (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt (2 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/datasource/FilteredLibraryLocalDataSource.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/MyLibraryFilterLocalDataSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/mediator/NovelRemoteMediator.kt (3 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/LibraryReadStatus.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt (3 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/GetLibraryUseCase.kt (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt (0 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/LibraryFilterBottomSheetScreen.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/LibraryFilterViewModel.kt (4 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetAttractivePoints.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetButtons.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetClickableItem.kt (3 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetHeader.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetReadStatus.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (3 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (4 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (0 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/LibrayFilterTopBar.kt (4 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt (1 hunks)
🧠 Learnings (1)
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 (3)
data/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/filter/component/LibraryFilterBottomSheetAttractivePoints.kt (1)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetClickableItem.kt (1)
  • LibraryFilterBottomSheetClickableItem (27-56)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetReadStatus.kt (1)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetClickableItem.kt (1)
  • LibraryFilterBottomSheetClickableItem (27-56)
💤 Files with no reviewable changes (3)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt
  • domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt
🧰 Additional context used
🧠 Learnings (1)
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 (3)
data/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/filter/component/LibraryFilterBottomSheetAttractivePoints.kt (1)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetClickableItem.kt (1)
  • LibraryFilterBottomSheetClickableItem (27-56)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetReadStatus.kt (1)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetClickableItem.kt (1)
  • LibraryFilterBottomSheetClickableItem (27-56)
🔇 Additional comments (53)
core/network/src/main/java/com/into/websoso/core/network/datasource/account/model/response/KakaoLoginResponseDto.kt (2)

9-16: data class 전환으로 DTO 불변성·유틸리티 확보 👍

equals / hashCode / copy / toString 자동 생성 덕분에 테스트‧디버깅이 용이해졌습니다. 별다른 부작용도 없어 보입니다.


10-12: JSON 필드명 대소문자 확인 필요

@SerialName("Authorization")는 일반적인 JSON 키(소문자 스네이크/카멜)와 달리 대문자를 포함하고 있습니다. 서버 응답이 정확히 같은 대소문자를 갖지 않으면 역직렬화가 실패합니다. 실제 응답 스펙을 한 번 더 확인해 주세요.

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

21-26: 사용자 ID 상태 관리 로직이 올바르게 구현되었습니다.

캡슐화를 유지하면서 controlled update를 위한 public method를 제공하는 접근방식이 적절합니다. TODO 주석에 따라 향후 UserRepository로 이동될 예정이므로 현재는 임시 솔루션으로 보입니다.

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

13-13: 필요한 의존성 추가가 올바르게 구현되었습니다.

LibraryRepository에서 AccountRepository 사용을 위한 의존성 추가가 적절한 위치에 배치되었습니다.

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

28-28: 매핑 함수명 변경이 적절하게 적용되었습니다.

toDatabase에서 toNovelDatabase로의 변경은 새로운 필터링된 소설 엔티티 지원을 위한 리팩터링의 일부로, 더 구체적이고 명확한 메서드명을 사용하고 있습니다.

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

16-21: 함수 본문 단순화가 적절하게 적용되었습니다.

블록 본문에서 표현식 본문으로의 변경과 디버그 로그 제거는 코드 가독성을 향상시키는 좋은 리팩터링입니다.

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

169-169: Modifier 체이닝 포맷팅이 개선되었습니다.

.background().padding() modifier를 단일 표현식으로 체이닝하여 코드 가독성이 향상되었습니다.

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

6-11: 단일 표현식 함수로의 리팩터링이 적절합니다.

블록 본문에서 단일 표현식 함수로의 변경이 코드를 더 간결하게 만들고 Kotlin 모범 사례를 따릅니다. 함수 로직은 동일하게 유지되었습니다.

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

3-8: 깔끔한 enum 구현입니다.

필터 카테고리를 명확하게 표현하는 내부 enum 클래스입니다. 명명 규칙이 일관되고 internal 범위 설정이 적절합니다.

app/src/main/java/com/into/websoso/ui/main/MainViewModel.kt (2)

20-20: 의존성 주입이 적절히 구현되었습니다.

AccountRepository가 생성자를 통해 올바르게 주입되었습니다.


39-39: 사용자 ID 동기화 로직이 적절한 위치에 배치되었습니다.

userId 획득 후, UI 상태 업데이트 전에 accountRepository.updateUserId() 호출이 배치되어 데이터 일관성을 보장합니다.

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

13-13: 필터 상태 구조 개선이 적절합니다.

isInterested 속성을 LibraryFilterUiState로 이동하여 필터 관련 상태의 응집성을 높였습니다.


28-58: 계산된 속성과 헬퍼 함수 구현이 우수합니다.

  • isRatingSelected, labelText 속성들이 파생 상태를 명확하게 표현합니다
  • buildLabel 함수가 "첫 번째 항목 외 N개" 형식의 라벨을 올바르게 생성합니다
  • Kotlin의 filterValues, map 연산을 효과적으로 활용했습니다
core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt (2)

20-20: Nullable 타입 변경이 적절합니다.

readStatusString?로 변경하여 서버에서 null 값을 반환할 수 있는 경우를 올바르게 처리했습니다.


43-43: Null 안전 처리가 적절합니다.

readStatus.orEmpty()를 사용하여 null 값을 빈 문자열로 안전하게 변환하는 처리가 적절합니다.

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

36-36: 동적 파라미터 사용으로 개선되었습니다.

하드코딩된 값 대신 메소드 파라미터를 직접 사용하여 필터링 기능의 유연성이 향상되었습니다.

Also applies to: 40-40

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

1-2: 패키지 구조 변경이 적절합니다.

패키지가 com.into.websoso.data.library.mediator로 변경되어 더 명확한 구조를 가지게 되었습니다.


14-14: 생성자 파라미터 이름 오타가 수정되었습니다.

libraryRemoteDataSourcea에서 libraryRemoteDataSource로 올바르게 수정되었습니다.


30-30: 수정된 파라미터 이름을 올바르게 사용합니다.

수정된 libraryRemoteDataSource 파라미터 이름을 일관되게 사용하고 있습니다.

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

1-1: 패키지 구조 개편이 적절합니다.

필터 관련 컴포넌트를 com.into.websoso.feature.filter.component 패키지로 분리하여 모듈화가 개선되었습니다.


18-18: Preview 지원을 위한 import가 추가되었습니다.

개발 생산성 향상을 위한 Compose Preview 관련 import가 적절히 추가되었습니다.

Also applies to: 25-25


58-71: Preview 함수가 잘 구성되었습니다.

적절한 샘플 데이터와 테마 적용으로 개발 시 컴포넌트 미리보기가 가능하도록 잘 구현되었습니다.

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

9-13: 메소드 시그니처 개선이 적절합니다.

LibraryFilterParams 단일 파라미터 대신 개별 파라미터로 분리하여 인터페이스가 더욱 명시적이고 타입 안전하게 되었습니다. Map<String, Boolean> 타입을 사용한 필터 상태 관리 방식도 적절합니다.

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

28-28: DAO 추가 구현이 올바릅니다

새로운 필터링 기능을 위한 DAO 접근자 메서드가 적절히 추가되었습니다.

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

22-51: 잘 구조화된 Compose 컴포넌트입니다

헤더 컴포넌트가 Compose 모범 사례를 잘 따르고 있습니다:

  • 명확한 매개변수 분리
  • 적절한 Modifier 사용
  • 접근성을 위한 contentDescription (null이지만 의도적으로 보임)
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetReadStatus.kt (1)

32-56: Map 기반 상태 관리가 적절합니다

Map을 사용한 선택 상태 관리가 명확하고 확장 가능한 구조로 잘 구현되었습니다. 각 ReadStatus에 대한 개별 처리가 깔끔합니다.

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

16-26: DAO 구현이 Room 모범 사례를 잘 따릅니다

  • PagingSource 지원으로 효율적인 페이징 구현
  • OnConflictStrategy.REPLACE로 적절한 충돌 해결
  • userNovelId DESC 정렬로 최신 순 조회
  • 명확한 메서드 명명

28-34: Dagger Hilt 모듈 구성이 적절합니다

싱글톤 스코프와 적절한 컴포넌트 설치로 DI가 올바르게 구성되었습니다.

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

6-7: 필터 상태 관리 개선이 우수합니다

List 기반에서 Map 기반으로 변경한 것이 다음과 같은 이점을 제공합니다:

  • 타입 안정성 향상 (non-nullable)
  • 선택 상태의 명시적 표현
  • null 관련 오류 방지

이는 UI 레이어의 상태 관리와도 일관성을 유지합니다.

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

27-42: 매핑 메소드가 정확하게 구현되었습니다.

필드별 매핑이 올바르게 수행되고 있으며, internal 접근 제한자를 통해 적절한 캡슐화가 이루어지고 있습니다.


79-94: 확장 함수가 올바르게 구현되었습니다.

InDatabaseFilteredNovelEntity에서 NovelEntity로의 변환이 정확하게 구현되어 있습니다.

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

6-7: Enum 상수 정의가 적절합니다.

한국어 표시명이 명확하고 직관적으로 정의되어 있습니다.


11-11: 안전한 문자열 매핑 구현을 확인했습니다.

entries.firstOrNull을 사용하여 일치하는 항목이 없을 경우 NEWEST로 기본값 처리하는 것이 적절합니다. 이는 런타임 오류를 방지합니다.

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

43-47: 메소드 시그니처 개선이 적절합니다.

단일 파라미터 객체에서 개별 파라미터로 변경하여 API가 더 명확해졌습니다. 각 필터 옵션의 역할이 더 명확하게 드러납니다.

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

26-84: Grid 레이아웃과 상호작용이 잘 구현되었습니다.

Compose 모범 사례를 잘 따르고 있으며, 선택 상태에 따른 시각적 피드백이 적절합니다.

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

31-80: 버튼 레이아웃과 스타일링이 잘 구현되었습니다.

IntrinsicSize.Min을 사용한 높이 정렬과 적절한 색상 테마 적용이 인상적입니다. 사용자 경험을 고려한 명확한 버튼 구분도 좋습니다.

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

22-23: 상태 변수명 변경이 적절합니다.

_libraryFilterUiState에서 _uiState로 변경하여 더 간결하고 명확해졌습니다.

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

14-22: 데이터 변환 메서드가 잘 구현되었습니다.

toData() 메서드를 통해 데이터 레이어 모델로의 변환이 깔끔하게 처리되었습니다.

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

6-21: 엔티티 구조가 적절하게 설계되었습니다.

필터된 소설 정보를 저장하기 위한 포괄적인 필드들이 잘 정의되었고, userNovelId를 기본 키로 사용하는 것이 적절합니다.


16-20: List 타입 변환 설정 확인 완료

Converters.kt에 List ↔ String 변환 메서드가 구현되어 있고,
WebsosoDatabase.kt@TypeConverters(Converters::class)로 정상 등록되어 있습니다.
따라서 별도 추가 설정이 필요하지 않습니다.

data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt (2)

26-33: 페이징 로직이 올바르게 구현되었습니다.

REFRESH, PREPEND, APPEND에 대한 처리가 적절하고, lastUserNovelId를 이용한 커서 기반 페이징이 잘 구현되었습니다.


49-52: 캐시 관리 로직이 적절합니다.

REFRESH 시 기존 데이터를 삭제하고 새 데이터를 삽입하는 로직이 올바르게 구현되었습니다.

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

31-76: UI 컴포넌트가 잘 구현되었습니다.

재사용 가능한 LibraryFilterBottomSheetClickableItem을 활용하여 일관된 UI를 구성했고, 콜백 패턴으로 관심사 분리가 잘 되어 있습니다.

data/library/src/main/java/com/into/websoso/data/library/datasource/FilteredLibraryLocalDataSource.kt (2)

14-20: 인터페이스 설계가 깔끔합니다

필터링된 도서관 데이터 소스의 인터페이스가 명확하고 필요한 기능들을 잘 정의하고 있습니다.


27-29: 맵핑 함수 참조가 누락되었습니다

NovelEntity::toFilteredNovelDatabase는 함수 참조가 아닌 함수 호출이어야 합니다.

-filteredNovelDao.insertFilteredNovels(novels.map(NovelEntity::toFilteredNovelDatabase))
+filteredNovelDao.insertFilteredNovels(novels.map { it.toFilteredNovelDatabase() })

Likely an incorrect or invalid review comment.

domain/library/src/main/java/com/into/websoso/domain/library/GetLibraryUseCase.kt (2)

23-25: 필터 비어있음 체크 로직이 정확합니다

읽기 상태, 매력 포인트, 별점을 모두 확인하여 필터가 비어있는지 판단하는 로직이 적절합니다.


40-44: 확장 함수의 재사용성이 좋습니다

Map의 선택된 값들을 문자열 리스트로 변환하는 확장 함수가 깔끔하게 구현되었습니다.

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

122-125: 필터 검색 시 시트 닫기 순서가 적절합니다

필터 검색 버튼 클릭 시 먼저 시트를 닫고 검색을 수행하는 순서가 사용자 경험에 좋습니다.

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

39-52: 반응형 페이징 플로우가 잘 구현되었습니다

distinctUntilChanged()를 사용하여 불필요한 재조회를 방지하고, flatMapLatest로 최신 필터 상태를 반영하는 구조가 좋습니다.

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

110-119: 라이브러리 프래그먼트 스크롤 리셋 로직이 잘 구현되었습니다

동일한 메뉴를 재선택했을 때 라이브러리 프래그먼트의 스크롤 위치를 리셋하는 기능이 사용자 경험을 향상시킵니다.


143-152: 프래그먼트 생성 로직이 깔끔합니다

태그 기반으로 프래그먼트를 찾거나 생성하는 로직이 명확하고 유지보수하기 좋습니다.

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

49-82: UI 상태 관리 중앙화 개선 확인

onSortClick 콜백이 매개변수 없는 함수로 단순화되어 상태 관리가 ViewModel로 중앙화된 것으로 보입니다. 이는 좋은 아키텍처 개선입니다.


84-127: 필터 상태 접근 방식 개선

isInterested 상태를 libraryFilterUiState에서 직접 가져오도록 변경한 것이 적절합니다. 상태 관리가 일관성 있게 개선되었습니다.

Comment on lines 19 to 23
@Database(
entities = [InDatabaseNovelEntity::class],
entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class],
version = 1,
exportSchema = false,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

데이터베이스 버전 증가 필요

새로운 엔티티 InDatabaseFilteredNovelEntity를 추가했지만 데이터베이스 버전이 1로 유지되고 있습니다. 기존 사용자가 앱을 업데이트할 때 스키마 충돌이 발생할 수 있습니다.

@Database(
    entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class],
-    version = 1,
+    version = 2,
    exportSchema = false,
)
📝 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
@Database(
entities = [InDatabaseNovelEntity::class],
entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class],
version = 1,
exportSchema = false,
)
@Database(
entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class],
version = 2,
exportSchema = false,
)
🤖 Prompt for AI Agents
In core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt
around lines 19 to 23, the database version remains at 1 despite adding a new
entity InDatabaseFilteredNovelEntity. To prevent schema conflicts during app
updates, increment the database version number to a higher integer, such as 2,
in the @Database annotation.

Comment on lines 51 to 57
Json.encodeToString(
LibraryFilterPreferences(
readStatuses = readStatuses,
attractivePoints = attractivePoints,
novelRating = novelRating,
),
)
Copy link

@coderabbitai coderabbitai bot Jul 22, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

JSON 직렬화 성능을 검토해주세요.

DataStore edit 블록 내에서 JSON 직렬화를 수행하고 있습니다. 복잡한 필터 데이터의 경우 직렬화 시간이 DataStore 쓰기를 지연시킬 수 있습니다.

다음 스크립트로 LibraryFilterPreferences의 복잡도를 확인해보세요:

JSON 직렬화가 성능에 미치는 영향을 측정하고, 필요시 더 가벼운 직렬화 방식을 고려해보세요.


🏁 Script executed:

#!/bin/bash
# LibraryFilterPreferences 클래스의 필드 수와 복잡도 확인
ast-grep --pattern 'data class LibraryFilterPreferences($$$)'

Length of output: 63


LibraryFilterPreferences 정의 위치와 필드 수를 다시 확인하기 위해 아래 스크립트를 실행해주세요.
필드가 많을수록 JSON 직렬화 비용이 커질 수 있습니다.


🏁 Script executed:

#!/bin/bash
# LibraryFilterPreferences 클래스 정의와 필드 목록 확인
rg -n "data class LibraryFilterPreferences" -A5

Length of output: 986


DataStore edit 블록 내 JSON 직렬화 비용 검토
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt 51–57라인:

Json.encodeToString(
    LibraryFilterPreferences(
        readStatuses = readStatuses,
        attractivePoints = attractivePoints,
        novelRating = novelRating,
    ),
)

LibraryFilterPreferences 클래스(5개 프로퍼티: String, Boolean, Map×2, Float)는 Map 엔트리 수에 따라 직렬화 비용이 크게 증가할 수 있습니다.

  • 실제 엔트리 수(평균/최대) 및 Json.encodeToString 호출당 소요 시간 측정
  • 성능 저하가 확인되면 ProtoBuf/CBOR 등 경량 포맷 or DataStore Preferences(Core) 포맷 고려
  • 블로킹 직렬화를 IO 스레드로 오프로드하거나, 빈번한 쓰기 시 캐싱된 직렬화 결과 재사용 검토
🤖 Prompt for AI Agents
In
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt
around lines 51 to 57, the JSON serialization of LibraryFilterPreferences may
cause high CPU cost due to large Map entries. Measure the average and maximum
Map entry counts and the time taken by Json.encodeToString per call. If
performance degradation is observed, consider switching to a more efficient
serialization format like ProtoBuf or CBOR, or use DataStore Preferences(Core)
format. Additionally, offload the blocking serialization to an IO thread or
implement caching of serialized results to reduce frequent serialization
overhead.

Copy link
Member Author

Choose a reason for hiding this comment

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

직렬화를 먼저하고, edit하면 돼?

Copy link

Choose a reason for hiding this comment

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

@s9hn 네, 맞습니다! 직렬화를 먼저 하고 edit 블록에서는 이미 직렬화된 문자열만 저장하는 것이 더 효율적입니다.

현재 코드를 다음과 같이 개선할 수 있습니다:

override suspend fun updateMyLibraryFilter(
    readStatuses: Map<String, Boolean>,
    attractivePoints: Map<String, Boolean>,
    novelRating: Float,
) {
    // 먼저 직렬화 수행 (edit 블록 밖에서)
    val serializedData = withContext(dispatcher) {
        Json.encodeToString(
            LibraryFilterPreferences(
                readStatuses = readStatuses,
                attractivePoints = attractivePoints,
                novelRating = novelRating,
            ),
        )
    }
    
    // DataStore edit은 빠르게 처리
    myLibraryFilterDataStore.edit { prefs ->
        prefs[LIBRARY_FILTER_PARAMS_KEY] = serializedData
    }
}

이렇게 하면:

  • DataStore 트랜잭션 시간이 단축됩니다
  • JSON 직렬화가 DataStore 쓰기를 블로킹하지 않습니다
  • 더 원자적이고 효율적인 DataStore 작업이 가능합니다

좋은 최적화 아이디어네요! 🐰

  /|   /|  
 ( :v:  )
  |(_)|

🧠 Learnings used
Learnt from: s9hn
PR: Team-WSS/WSS-Android#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.

Comment on lines +10 to +12
val readStatuses: Map<String, Boolean>,
val attractivePoints: Map<String, Boolean>,
val novelRating: Float,
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 매개변수에 기본값이 없어 인스턴스 생성 시 문제가 될 수 있습니다.

다음과 같이 기본값을 추가하세요:

-    val readStatuses: Map<String, Boolean>,
-    val attractivePoints: Map<String, Boolean>,
+    val readStatuses: Map<String, Boolean> = emptyMap(),
+    val attractivePoints: Map<String, Boolean> = emptyMap(),
📝 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
val readStatuses: Map<String, Boolean>,
val attractivePoints: Map<String, Boolean>,
val novelRating: Float,
val readStatuses: Map<String, Boolean> = emptyMap(),
val attractivePoints: Map<String, Boolean> = emptyMap(),
val novelRating: Float,
🤖 Prompt for AI Agents
In
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt
around lines 10 to 12, the properties readStatuses and attractivePoints lack
default values, which can cause compilation errors when creating instances
without explicitly providing these arguments. Add default values to these
parameters, such as empty maps, to ensure instances can be created without
specifying them.

Comment on lines 83 to 85
Log.d("123123 readStatuses", readStatuses.toString())
Log.d("123123 attractivePoints", attractivePoints.toString())
Log.d("123123 novelRating", novelRating.toString())
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

디버그 로그 제거 필요

임시 디버그 로그가 프로덕션 코드에 포함되어 있습니다. 이를 제거하거나 적절한 로그 태그와 레벨을 사용하도록 수정해주세요.

-        Log.d("123123 readStatuses", readStatuses.toString())
-        Log.d("123123 attractivePoints", attractivePoints.toString())
-        Log.d("123123 novelRating", novelRating.toString())

또는 필요한 경우:

-        Log.d("123123 readStatuses", readStatuses.toString())
-        Log.d("123123 attractivePoints", attractivePoints.toString())
-        Log.d("123123 novelRating", novelRating.toString())
+        if (BuildConfig.DEBUG) {
+            Log.d("LibraryRepository", "Updating filter - readStatuses: $readStatuses")
+            Log.d("LibraryRepository", "Updating filter - attractivePoints: $attractivePoints")
+            Log.d("LibraryRepository", "Updating filter - novelRating: $novelRating")
+        }
🤖 Prompt for AI Agents
In data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
around lines 83 to 85, remove the temporary debug logs or replace them with
appropriate logging calls using proper tags and log levels suitable for
production. Ensure that any logging left is meaningful and uses consistent,
descriptive tags instead of placeholder strings like "123123".

@SuppressLint("ResourceType")
@Composable
internal fun LibraryFilterBottomSheetReadStatus(
readStatues: Map<ReadStatus, Boolean>,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

매개변수 이름 오타 수정 필요

매개변수 이름에 오타가 있습니다.

-readStatues: Map<ReadStatus, Boolean>,
+readStatuses: Map<ReadStatus, Boolean>,
📝 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
readStatues: Map<ReadStatus, Boolean>,
- readStatues: Map<ReadStatus, Boolean>,
+ readStatuses: Map<ReadStatus, Boolean>,
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetReadStatus.kt
at line 24, the parameter name "readStatues" contains a typo. Rename the
parameter to "readStatuses" to correct the spelling.

private fun LibraryFilterBottomSheetReadStatusPreview() {
WebsosoTheme {
LibraryFilterBottomSheetReadStatus(
readStatues = mapOf(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

프리뷰에서 매개변수 이름 일관성 유지

프리뷰에서도 동일한 매개변수 이름 수정이 필요합니다.

-readStatues = mapOf(),
+readStatuses = mapOf(),
📝 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
readStatues = mapOf(),
readStatuses = mapOf(),
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/filter/component/LibraryFilterBottomSheetReadStatus.kt
at line 64, the parameter name "readStatues" is inconsistent with other usages.
Rename this parameter to match the correct and consistent name used elsewhere,
likely "readStatus", to maintain naming consistency across the preview and
related code.

Comment on lines 84 to 113
Text(
text = "읽기 상태",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
LibraryFilterBottomSheetReadStatus(
onReadStatusClick = onReadStatusClick,
readStatues = readStatues,
)
Spacer(modifier = Modifier.height(height = 32.dp))
Text(
text = "매력포인트",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
LibraryFilterBottomSheetAttractivePoints(
onAttractivePointClick = onAttractivePointClick,
attractivePoints = attractivePoints,
)
Spacer(modifier = Modifier.height(height = 32.dp))
Text(
text = "별점",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.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

하드코딩된 문자열을 리소스로 분리하세요

"읽기 상태", "매력포인트", "별점" 등의 UI 텍스트가 하드코딩되어 있습니다. 다국어 지원과 유지보수를 위해 문자열 리소스로 분리하는 것이 좋습니다.

-Text(
-    text = "읽기 상태",
+Text(
+    text = stringResource(R.string.library_filter_read_status),
📝 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
Text(
text = "읽기 상태",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
LibraryFilterBottomSheetReadStatus(
onReadStatusClick = onReadStatusClick,
readStatues = readStatues,
)
Spacer(modifier = Modifier.height(height = 32.dp))
Text(
text = "매력포인트",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
LibraryFilterBottomSheetAttractivePoints(
onAttractivePointClick = onAttractivePointClick,
attractivePoints = attractivePoints,
)
Spacer(modifier = Modifier.height(height = 32.dp))
Text(
text = "별점",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Text(
text = stringResource(R.string.library_filter_read_status),
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
LibraryFilterBottomSheetReadStatus(
onReadStatusClick = onReadStatusClick,
readStatues = readStatues,
)
Spacer(modifier = Modifier.height(height = 32.dp))
Text(
text = "매력포인트",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
LibraryFilterBottomSheetAttractivePoints(
onAttractivePointClick = onAttractivePointClick,
attractivePoints = attractivePoints,
)
Spacer(modifier = Modifier.height(height = 32.dp))
Text(
text = "별점",
style = WebsosoTheme.typography.title2,
color = Black,
modifier = Modifier.padding(vertical = 10.dp),
)
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/filter/LibraryFilterBottomSheetScreen.kt
between lines 84 and 113, the UI text strings "읽기 상태", "매력포인트", and "별점" are
hardcoded. To support localization and improve maintainability, move these
strings into the appropriate string resource files and replace the hardcoded
text with references to these string resources in the code.

Comment on lines 70 to 72
readStatuses = uiState.value.readStatuses.mapKeys { it.key.name },
attractivePoints = uiState.value.attractivePoints.mapKeys { it.key.name },
novelRating = uiState.value.novelRating,
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

필터 파라미터 변환 로직을 확인해주세요.

enum의 name 속성을 사용하여 문자열로 변환하고 있는데, 서버 API에서 예상하는 정확한 문자열 형식과 일치하는지 검증이 필요합니다.

다음 스크립트로 AttractivePoints와 ReadStatus enum의 name 속성이 올바른 형식인지 확인해보세요:


🏁 Script executed:

#!/bin/bash
# AttractivePoints와 ReadStatus enum 정의 확인
ast-grep --pattern 'enum class AttractivePoints {
  $$$
}'

ast-grep --pattern 'enum class ReadStatus {
  $$$
}'

Length of output: 2955


🏁 Script executed:

#!/bin/bash
rg -n "enum class AttractivePoints" -A 10

Length of output: 2433


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt

Length of output: 633


AttractivePoints 필터 키 변환 로직 수정 필요
현재 mapKeys { it.key.name } 으로 변환 시 “WORLDVIEW” 등의 대문자 이름이 전달되지만, 서버 API는 domain 모델의 key 프로퍼티("worldview", "material" 등)를 기대합니다. 아래처럼 수정해주세요.

  • 파일: feature/library/src/main/java/com/into/websoso/feature/filter/LibraryFilterViewModel.kt
  • 라인: 70–72
 readStatuses        = uiState.value.readStatuses.mapKeys { it.key.name },
-attractivePoints    = uiState.value.attractivePoints.mapKeys { it.key.name },
+attractivePoints    = uiState.value.attractivePoints.mapKeys { it.key.key },
 novelRating         = uiState.value.novelRating,

추가 검증:

  • ReadStatus 역시 서버가 대문자("WATCHING", "WATCHED", "QUIT")를 사용하는지 API 스펙 문서를 확인하세요.
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/filter/LibraryFilterViewModel.kt
lines 70 to 72, the mapKeys transformation for attractivePoints incorrectly uses
it.key.name which produces uppercase keys like "WORLDVIEW" but the server API
expects the domain model's key property in lowercase like "worldview". Change
the mapKeys lambda to use it.key.key instead of it.key.name for
attractivePoints. Also verify the API spec for readStatuses to confirm if
uppercase keys are expected; if not, adjust similarly.

Comment on lines +77 to 84
scope
.launch {
isShowBottomSheet = true
bottomSheetState.show()
}.invokeOnCompletion {
libraryFilterViewModel.updateMyLibraryFilter(uiState.libraryFilterUiState)
}
},
Copy link

@coderabbitai coderabbitai bot Jul 22, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

필터 업데이트 타이밍 문제 가능성

invokeOnCompletion은 코루틴이 완료된 후 호출되므로, 바텀시트가 완전히 표시되기 전에 필터가 업데이트될 수 있습니다. 이는 의도한 동작이 아닐 수 있습니다.

 onFilterClick = {
-    scope
-        .launch {
-            isShowBottomSheet = true
-            bottomSheetState.show()
-        }.invokeOnCompletion {
-            libraryFilterViewModel.updateMyLibraryFilter(uiState.libraryFilterUiState)
-        }
+    isShowBottomSheet = true
+    scope.launch {
+        bottomSheetState.show()
+    }
 },

필터 업데이트는 바텀시트 내부에서 사용자 액션에 따라 처리되어야 합니다.

📝 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
scope
.launch {
isShowBottomSheet = true
bottomSheetState.show()
}.invokeOnCompletion {
libraryFilterViewModel.updateMyLibraryFilter(uiState.libraryFilterUiState)
}
},
onFilterClick = {
isShowBottomSheet = true
scope.launch {
bottomSheetState.show()
}
},
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt
around lines 77 to 84, the filter update is currently triggered in
invokeOnCompletion of the coroutine that shows the bottom sheet, which may run
before the bottom sheet is fully displayed. To fix this, remove the filter
update call from invokeOnCompletion and instead handle filter updates inside the
bottom sheet UI based on user actions, ensuring the filter updates occur only
after relevant user interactions within the bottom sheet.

Copy link
Member Author

Choose a reason for hiding this comment

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

코루틴이 완료된 후 호출되는데 어떻게 바텀시트가 완전히 표시되기전에 필터가 업데이트돼?
해당 코루인 Job이 isComplete가 되어야 아래 업데이트가 실행되는거 아니야?

Copy link

Choose a reason for hiding this comment

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

@s9hn 맞습니다! 제가 잘못 분석했네요.

bottomSheetState.show()는 바텀시트 애니메이션이 완료될 때까지 suspend하는 함수이므로, 코루틴이 완료(isComplete)되는 시점은 바텀시트가 완전히 표시된 후입니다. 따라서 invokeOnCompletion의 필터 업데이트는 바텀시트가 완전히 표시된 후에 실행되는 것이 맞습니다.

현재 코드의 동작 순서:

  1. isShowBottomSheet = true 실행
  2. bottomSheetState.show() 실행 (바텀시트 애니메이션 완료까지 대기)
  3. 코루틴 완료 후 invokeOnCompletion의 필터 업데이트 실행

제 이전 코멘트는 잘못된 분석이었습니다. 죄송합니다!


✏️ Learnings added
Learnt from: s9hn
PR: Team-WSS/WSS-Android#728
File: feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt:77-84
Timestamp: 2025-07-24T09:02:00.963Z
Learning: `bottomSheetState.show()`는 바텀시트 애니메이션이 완료될 때까지 suspend하는 함수이므로, `invokeOnCompletion`에서 실행되는 코드는 바텀시트가 완전히 표시된 후에 실행된다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@Team-WSS Team-WSS deleted a comment from coderabbitai bot Jul 24, 2025
@Team-WSS Team-WSS deleted a comment from coderabbitai bot Jul 24, 2025
@s9hn s9hn removed the ❌[DO NOT MERGE] 머지를 하면 안됩니다. label Jul 24, 2025
Copy link
Member

@yeonjeen yeonjeen left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!! 그동안 엄청난 길을 혼자 걸어오셨군요 ㅋㅋㅋ 이렇게 룸디비 사용하는 경우는 생각도 못해봤는데 덕분에 좋은 인사이트 얻었네요!! 코멘트 확인해보시고 답변 부탁드립니다:)

entities = [InDatabaseNovelEntity::class],
version = 1,
entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class],
version = 2,
Copy link
Member

Choose a reason for hiding this comment

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

a: 코드레빗 리뷰로 해당 부분 봤습니다! 버전을 올리라는 리뷰같았는데 왜 올려야하는지 이유는 잘 이해가 안되서요..! 왜 올려야하는 건가요??

Copy link
Member Author

Choose a reason for hiding this comment

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

version을 올리는 이유는 DB 스키마가 변경되었음을 Room에게 알려주기 위해서입니다! 구조가 바뀌었는데도 버전을 올리지 않으면, Room이 예외를 던집니닷
구조 변경 여부를 식별하는 기준이라고 생각하면 될 것 같아요!

import javax.inject.Singleton

@Singleton
class LibraryRepository
Copy link
Member

Choose a reason for hiding this comment

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

r: 현재 레포지터리에서는 기존 필터 값을 불러오고 조건별로 병합한 뒤 저장하는 로직까지 함께 처리하고 있는데 이 부분은 도메인 로직에 가깝다고 생각합니다! 현재 서재레포지터리는 데이터원천의 접근이나 추상화에 집중하고 필터 병합같이 도메인 규칙이 적용되는 로직은 유즈케이스나 별도의 매니저로 분리하는 것은 어떨까요? 해당 로직을 레포지터리에 두신 특별한 이유가 있으시다면 답변 부탁드립니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

updateMyLibraryFilter는 단순히 파라미터로 넘어온 값을 데이터스토어에 저장합니다!
현재 서재필터는 부분필터 적용이 가능하므로, 그냥 단순히 파라미터로 넘어온 필터값만 저장하는 역할입니다

Comment on lines +15 to +19
suspend fun insertNovels(novels: List<NovelEntity>)

fun selectAllNovels(): PagingSource<Int, InDatabaseFilteredNovelEntity>

suspend fun deleteAllNovels()
Copy link
Member

Choose a reason for hiding this comment

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

c: 현재 insertNovels / deleteAllNovels / selectAllNovels은 다 괜찮지만 약간 더 표현이 구체적이면 좋겠다는 생각이 듭니다! 특히 이 데이터소스는 "Filtered" 전용 DAO를 위한 인터페이스이기 때문에 함수명에도 일관되게 Filtered를 넣으면 혼동 방지에 좋을 것 같습니다!

Copy link
Member Author

@s9hn s9hn Jul 24, 2025

Choose a reason for hiding this comment

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

다른 관점에서 의견드립니다!
클래스 내부의 책임이나 상태가 클래스 네이밍처럼 구체적이지 않다고 볼 수는 있겠지만, 예를 들어, FilteredLibraryLocalDataSource라는 클래스가 자신이 필터된 novel만을 다루는 객체임을 꼭 인지해야 하는지는 의문입니다.

이 클래스는 단순히 novel 데이터를 다루는 로컬 데이터 소스일 뿐이고, 그것이 필터된 데이터인지 아닌지는 외부에서 정의된 맥락이지 이 객체가 책임져야 할 부분은 아니라고 생각해요. 다시 말해, 이 클래스는 novel과 관련된 CRUD 작업만 잘 수행하면 충분하며, 자신이 다루는 데이터가 filtered인지 아닌지를 내부적으로 인지하고 있을 필요는 없습니다. 오히려 ‘Filtered’라는 네이밍은 개발자 입장에서 FilteredLibrary와 Library라는 구조적 구분을 명확히 하고 싶은 심리라고 생각하고, 객체 스스로의 책임 관점에서는 불필요한 정보라고 생각합니다!

같은 맥락에서 LibraryFilterBottomSheetScreen이 ViewModel의 이름에 "Filter"나 "Library"와 같은 구체적인 정보가 반드시 들어가야 한다고는 생각하지 않습니다. 이 화면이 참조하는 ViewModel은 단순히 필요한 상태와 로직을 제공해주면 되는 것이고, 그것이 어떤 화면에서 사용될지를 고려해 네이밍까지 구체화하는 건 오히려 관심사의 분리를 흐릴 수 있다고 생각해요. 결국 객체는 자신이 수행해야 할 핵심 책임만 알고 있으면 충분하고, 외부와의 관계나 구체적인 사용처까지 네이밍에 반영하려고 하면 오히려 유연성이 떨어질 수 있다고 생각합니다.

Copy link
Member

Choose a reason for hiding this comment

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

음 이야기를 듣고 나니 책임 관점에서 본 객체는 '데이터가 필터된 건지 아닌지'보다는 단순히 노벨 데이터를 다루는 로컬 소스일 뿐이라는 점이 더 명확하게 와닿은 것 같습니다! 특히 '객체는 자신이 사용될 맥락을 몰라도 된다'는 점은 확실히 책임분리 관점에서 보면 맞는 이야기라고 생각해요!

제가 처음에 함수 네이밍을 제안했던 건 실제로 개발작업 환경에서 비슷한 구조가 병렬로 존재할 때 함수명만 보면 어떤 데이터 흐름인지 즉시 구분하기 어렵다는 점 때문이었습니다! 예를 들어서 뷰모델에서 insertNovels() 같은 함수를 쓸 때 해당 함수가 필터된 DAO를 대상으로 하는 건지 일반 DAO를 대상으로 하는 건지 빠르게 인지하고 싶었달까요...ㅎ 그래서 개인적으로는 조금 더 구체적인 네이밍이 오히려 사용자 입장에서 명시적일 수 있다고 생각했습니다!

그런데 말씀하신 것처럼 객체 스스로는 '내가 필터된 데이터를 다룬다'는 사실을 몰라도 되고 그 구체성은 외부에서 컨텐스트로 부여하는게 더 적절하다는 관점도 충분히 이해되었습니다! 네이밍이 구체화되면 오히려 나중에 재사용성이나 유연성을 해칠 수 있다는 부분도 동의합니다! 그래서 이 부분은 어떤 네이밍이 더 협업과 이해에 도움이 되는가를 기준으로 정해봐도 괜찮을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

맞습니다! 제가 의도한 설계는
저장소는 '저장'만 하면 됩니다. 그것들을 활용하고 조합하는건 레포지토리의 몫이죠.
레포지토리는 A저장소에 '저장'할것인지 B저장소에 '저장'할것인지만 선택하면 됩니다!
각각의 저장소는 저장에만 책임을 다하면 됩니다

private val filteredNovelDao: FilteredNovelDao,
) : FilteredLibraryLocalDataSource {
override suspend fun insertNovels(novels: List<NovelEntity>) {
filteredNovelDao.insertFilteredNovels(novels.map(NovelEntity::toFilteredNovelDatabase))
Copy link
Member

Choose a reason for hiding this comment

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

r: 지금 NovelEntity쪽에서 변환 책임을 지고 있는 것 같은데 맞나요? 그렇다면 mapper 클래스를 만들어서 변환 책임을 분리하는게 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

우리가 액티비티 혹은 프래그먼트가 스스로 자신의 인텐트를 만들 수 있도록 했던 것과 같은 맥락이지 않을까요? mapper라는 클래스는 어떤 의미를 가지나요?

Copy link
Member

Choose a reason for hiding this comment

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

말씀하신 것처럼 객체가 자기 변환 책임을 가지는 것도 자연스러운 접근이라고 생각합니다! 다만 이 경우엔 NovelEntity와 DB 모델이 계층적으로 역할이 다르다 보니 변환 책임을 mapper로 분리하면 모델은 순수하게 유지되고 변환 로직도 테스타 확장성 측면에서 유리하다고 생각했습니다! 구조가 복잡해질 여지가 없으면 지금처럼 유지해도 괜찮을 것 같고 추후 변환 방향이 다양해질 경우에 대비한다면 mapper 분리도 고려해볼 수 있는 점이라고 생각합니다!

internal fun LibraryFilterBottomSheetScreen(
onDismissRequest: () -> Unit,
sheetState: SheetState,
viewModel: LibraryFilterViewModel,
Copy link
Member

Choose a reason for hiding this comment

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

c: 서재 화면에서 서재뷰모델이랑 필터뷰모델을 둘 다 호출하고 있어서 뷰모델 변수명을 명확하게 명시하고 있습니다! 해당 부분은 하나의 뷰모델만 호출하고 있기는 하지만 언라인을 맞추는 변수명은 어떨까요?

) {
LibraryFilterBottomSheetHeader(onDismissRequest = onDismissRequest)
Text(
text = "읽기 상태",
Copy link
Member

Choose a reason for hiding this comment

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

r: 스트링 파일 만들어놨습니다! 추출 부탁드려요!

Copy link
Member Author

@s9hn s9hn Jul 24, 2025

Choose a reason for hiding this comment

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

스트링 추출도 조금 회의적이라 냅두었는데요! 지역화할 예정이 없고, 재사용에 이점을 하나도 살리고 있지 못한데 당장은 스트링 추출이 오히려 생산성을 저하시킨다고 생각해요
이 부분은 팀원들과 한번 상의해보는거 어떤가요

Copy link
Member Author

@s9hn s9hn Jul 25, 2025

Choose a reason for hiding this comment

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

뒤늦게 확인했는데, 스트링 파일은 만들면 안됩니다 ㅠ
저희 :core:resource에 일원화되어있어요
피쳐모듈에서 스트링 패키지는 제거하겠습니다!

@s9hn s9hn merged commit c5f9970 into feat/704 Jul 26, 2025
1 check passed
@m6z1 m6z1 deleted the feat/712 branch August 11, 2025 15:07
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.

3 participants