Skip to content

Conversation

@s9hn
Copy link
Member

@s9hn s9hn commented May 22, 2025

📌𝘐𝘴𝘴𝘶𝘦𝘴

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

  • New Features

    • 라이브러리(서재) 화면이 Jetpack Compose 기반으로 전면 개편되어, 필터, 정렬, 그리드/리스트 보기 전환, 관심 작품, 별점, 매력 포인트 등 다양한 필터링 및 탐색 기능이 추가되었습니다.
    • 필터 및 정렬 기능을 위한 모달 바텀시트, 필터 토글, 매력 포인트/읽기 상태/별점 선택 UI가 제공됩니다.
    • 소설 리스트가 페이징 처리되어 대용량 데이터도 부드럽게 탐색할 수 있습니다.
    • 새로운 아이콘 및 디자인 리소스가 추가되어 시각적 완성도가 향상되었습니다.
    • 내비게이터 기능이 확장되어 유저 저장소, 소설 상세, 탐색 화면으로의 이동이 간편해졌습니다.
    • 계정 관련 처리에서 사용자 ID 관리가 강화되고, 로그인 및 토큰 생성 로직이 개선되었습니다.
    • 캐시 삭제 기능이 추가되어 사용자가 앱 데이터를 직접 정리할 수 있습니다.
  • 개선 및 리팩터링

    • 기존 뷰 기반의 라이브러리, 유저 저장소, 관련 어댑터/뷰모델/데이터 모델 등 구 코드가 모두 제거되고, Compose 기반으로 일원화되었습니다.
    • 라이브러리/유저 저장소 화면의 네비게이션 및 UI 상태 관리가 간소화되고, 관심 작품, 정렬, 필터 등 기능이 명확하게 분리되었습니다.
    • 데이터 계층에 Paging, Room, DataStore, Kotlin Serialization 등 최신 아키텍처가 적용되어 성능과 유지보수성이 개선되었습니다.
    • 메인 화면의 프래그먼트 관리 및 내비게이션 로직이 개선되어 프래그먼트 상태 관리가 안정화되었습니다.
    • 피드, 소설, 사용자 관심사 등 데이터 동기화 시 로컬 데이터베이스와 연동하여 일관성 및 성능이 향상되었습니다.

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵

KakaoTalk_Video_2025-08-09-19-17-35.mp4

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

드디어 3개월동안 진행된 서재 리뉴얼 작업이 마무리되는군요..

Summary by CodeRabbit

  • New Features

    • 라이브러리(서재) 기능이 Jetpack Compose 기반의 새로운 UI와 필터, 정렬, 관심작품, 별점, 읽기 상태, 매력 포인트 등 다양한 필터링 옵션과 함께 추가되었습니다.
    • 라이브러리 화면에서 그리드/리스트 보기 전환, 검색, 탐색, 상세 보기, 필터 바텀시트 등 다양한 상호작용이 지원됩니다.
    • 캐시 데이터 삭제 기능이 계정 정보 화면에 추가되었습니다.
  • UI/UX 개선

    • 주요 화면(홈, 피드, 탐색, 마이페이지 등)과 라이브러리 관련 레이아웃이 흰색 배경으로 일관성 있게 개선되었습니다.
    • 라이브러리 및 유저 서재 화면이 Compose 기반으로 전환되어 더욱 부드러운 화면 전환과 최신 UI를 제공합니다.
  • 버그 수정 및 안정성

    • 토큰 관리 및 자동 로그인, 관심작품, 피드, 별점 등에서 로컬 데이터와 동기화 및 예외 처리가 강화되었습니다.
    • 로그아웃, 회원탈퇴 시 라이브러리 및 필터 데이터가 안전하게 삭제됩니다.
  • 기타

    • 다양한 아이콘 및 벡터 리소스가 새롭게 추가되었습니다.
    • 내부 구조 및 네비게이션, 데이터 저장소, 페이징 처리 등이 대폭 리팩터링되어 성능이 향상되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented May 22, 2025

Walkthrough

이번 변경은 서재(라이브러리) 기능의 전면적인 UI/UX 개편 및 데이터 구조 리팩터링을 포함합니다. Jetpack Compose 기반의 라이브러리 화면, 필터/정렬/페이징 기능, Room 기반 로컬 캐싱, 도메인/데이터/코어 계층의 대대적 구조 개선, 관련 모델 및 API의 전면 교체, 네비게이터 확장, 기존 뷰 및 어댑터/모델/로직 제거가 이루어졌습니다.

Changes

Cohort / File(s) Change Summary
라이브러리 Compose UI 및 ViewModel 신설
feature/library/src/main/java/com/into/websoso/feature/library/*
Jetpack Compose 기반 라이브러리 화면, 필터 바텀시트, 그리드/리스트/아이템 컴포넌트, 상태/필터/모델/매퍼/스타 UI 모델 등 신규 구현.
라이브러리 ViewModel/Fragment/Activity Compose 전환
app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt, app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt, feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt
기존 Fragment, Activity, ViewModel을 Compose 기반으로 전환 및 의존성 주입, 기존 로직/상태/어댑터/뷰 제거.
기존 라이브러리/유저스토리지 관련 코드/레이아웃 제거
app/src/main/java/com/into/websoso/ui/main/library/adapter/*, app/src/main/java/com/into/websoso/ui/main/library/model/*, app/src/main/java/com/into/websoso/ui/userStorage/*, app/src/main/res/layout/item_storage*.xml, menu_storage_popup.xml
RecyclerView 어댑터, ViewHolder, 상태/모델, 정렬 팝업, 레이아웃 등 기존 코드 및 리소스 일괄 삭제.
Room 기반 DB 및 로컬 데이터소스 도입
core/database/src/main/java/com/into/websoso/core/database/*
Room DB, NovelDao, 엔티티, 매퍼, 로컬 데이터소스, PagingSource, RemoteMediator, DI 모듈 등 신규 구현.
Paging/Room/도메인/데이터 계층 모델 및 리포지터리 구현
data/library/src/main/java/com/into/websoso/data/library/*, domain/library/src/main/java/com/into/websoso/domain/library/model/*
NovelEntity, UserNovelsEntity, PagingSource/RemoteMediator, 리포지터리/필터/로컬/리모트 데이터소스, 도메인 모델, DI 모듈 등 신규 도입.
필터/정렬/관심/상태/평점 등 도메인 및 UI 모델 신설
feature/library/src/main/java/com/into/websoso/feature/library/model/*, domain/library/src/main/java/com/into/websoso/domain/library/model/*
필터/정렬/관심/상태/평점 등 도메인 및 UI 모델, enum, 변환 로직 추가.
데이터스토어 기반 필터 저장/불러오기
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/*, data/library/src/main/java/com/into/websoso/data/filter/*
DataStore 기반 필터 저장/불러오기, 매퍼, DI 모듈, 리포지터리, 인터페이스 등 구현.
API/네트워크/DTO/매퍼 전면 교체
core/network/src/main/java/com/into/websoso/core/network/datasource/library/*
기존 UserStorage API/DTO/매퍼 제거, UserNovels 기반 API/DTO/매퍼/모델로 교체, 파라미터 확장 및 페이징/필터 지원.
네비게이터 확장 및 인터페이스 변경
core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt, app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt, 관련 Fragment/Activity
메인/유저스토리지/노블디테일/익스플로어 네비게이션 메소드 추가 및 파라미터 확장, 관련 코드 일괄 적용.
앱 메인/탭/네비게이션/유저ID 전달 개선
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt, app/src/main/java/com/into/websoso/ui/main/MainViewModel.kt, 관련 Fragment
Fragment show/hide 방식으로 전환, currentFragment 관리, 유저ID 전달 로직 개선, TAG 상수 추가 등.
기타: 빌드스크립트/리소스/유틸/토큰로직/레이아웃 등
*.gradle.kts, core/resource/src/main/res/drawable/*, core/common/src/main/java/com/into/websoso/core/common/extensions/*, app/src/main/java/com/into/websoso/data/account/*, app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt
Room/Paging 의존성 추가, 벡터 아이콘 리소스 대량 추가, 날짜/Float 유틸, 토큰 갱신/메소드명 리팩터, 레이아웃 단순화 등.

Sequence Diagram(s)

sequenceDiagram
participant UI as LibraryScreen (Compose)
participant VM as LibraryViewModel
participant Repo as LibraryRepository
participant FilterRepo as FilterRepository
participant Remote as LibraryRemoteDataSource
participant Local as LibraryLocalDataSource
participant DB as Room DB

UI->>VM: 사용자 입력(필터/정렬/관심/탭/검색)
VM->>FilterRepo: updateFilter(...)
FilterRepo-->>VM: filterFlow emit
VM->>Repo: libraryFlow (paging)
Repo->>Remote: getUserNovels(..., filter)
Remote-->>Repo: UserNovelsEntity
Repo->>Local: insertNovels(...)
Local->>DB: insert
Repo-->>VM: PagingData<NovelEntity>
VM-->>UI: novels.collectAsLazyPagingItems()
UI-->>User: 라이브러리 리스트/그리드/필터/정렬 UI 표시
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

  • 대규모 구조/아키텍처 변경, 수십 개 파일/모듈/레이아웃/리소스/도메인/데이터/네트워크/DI/네비게이션/Compose UI/로컬DB/페이징/필터 등 전방위적 변경
  • 신규 코드, 삭제, 리팩터, API/모델/로직/리소스 교체, 테스트/리뷰 난이도 매우 높음

Assessment against linked issues

Objective Addressed Explanation
서재(라이브러리) UI/UX 전면 개편 (#704, #693, #702, #694, #696, #698, #699, #700, #701, #711, #712, #713, #725)
기존 유저스토리지/라이브러리 View/Model/Adapter/레이아웃 제거 (#704)
필터/정렬/관심/상태/평점/검색 등 도메인/데이터/UI 모델 및 기능 구현 (#704, #699, #700, #701)
Room 기반 로컬 캐싱 및 Paging 처리 (#704, #698, #696)
네트워크/DTO/매퍼/리포지터리/네비게이터 등 구조적 리팩터 (#704, #694, #698, #725)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
토큰 갱신 메소드명 변경 및 1초 딜레이 추가 (e.g., app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt, core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt) 서재(라이브러리) UI/UX 개편 이슈(#704)와 직접 관련 없는 인증/토큰 관리 로직 변경이 포함됨.
Feed/Home/Explore/MyPageFragment에 TAG 상수 추가 (e.g., app/src/main/java/com/into/websoso/ui/main/feed/FeedFragment.kt 등) 라이브러리 개편과 무관한 Fragment TAG 상수 추가.
fragment_explore.xml, fragment_feed.xml, fragment_home.xml 등 레이아웃 배경색/속성 변경 서재 개편 이슈 범위를 벗어난 UI 속성 변경.
KakaoAuthClientModule에 @ActivityScoped 추가 인증 DI 스코프 변경은 서재 개편과 직접적 관련 없음.

Possibly related PRs

  • #697: Room DB 및 라이브러리 기능의 DB/DAO/엔티티/DI 등 인프라 도입과 직접적으로 연관됨.
  • #723: 라이브러리 Compose UI, Paging, 필터, 네비게이터 등 대규모 리팩터/확장과 상당 부분 코드 중복 및 연관.
  • #734: SplashViewModel의 토큰 갱신 로직 변경(1초 딜레이, createTokens()로 교체)과 동일한 코드 변경점 존재.

Suggested labels

🚧 [BUILD]

Suggested reviewers

  • junseo511
  • m6z1
  • yeonjeen

Poem

🐰
서재를 새로 빚었어요,
책장엔 별점과 필터가 춤추고,
그리드와 리스트가 번갈아 인사해요.
방대한 코드, 토끼도 놀라
페이징과 캐싱, Compose의 향연!
리뷰어님, 커피 한 잔 준비하세요
오늘은 토끼도 책을 읽다 졸았답니다. 📚✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

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

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

Support

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

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.

@s9hn s9hn changed the title Feat/704 feat: 서재 사일로 - UI/UX 개편 May 22, 2025
@github-actions github-actions bot requested review from junseo511, m6z1 and yeonjeen May 22, 2025 07:56
@s9hn s9hn added 🍯 [FEAT] 새로운 기능을 개발합니다. ♻️ [REFACTOR] 기존 코드를 리팩토링합니다. ⚖️ 재상 세훈 전지적 세훈 시점 👛 황후 연진 ❌[DO NOT MERGE] 머지를 하면 안됩니다. labels May 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: 4

🧹 Nitpick comments (6)
core/database/src/main/AndroidManifest.xml (1)

1-4: AndroidManifest.xml 파일이 최소한으로만 구성되었습니다.

이 최소한의 매니페스트 파일은 라이브러리 모듈에 충분하지만, 필요에 따라 namespace 속성을 추가하는 것이 좋습니다.

<?xml version="1.0" encoding="utf-8"?>
-<manifest>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.into.websoso.core.database">

</manifest>
core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (2)

70-72: 메소드 호출 이름 변경이 적절합니다

renewTokens()에서 createTokens()로의 메소드 호출 변경이 AccountRepository의 인터페이스 변경과 일관성을 유지하도록 잘 수정되었습니다. 다만, 이 메소드를 포함하는 함수 이름은 여전히 renewToken()입니다. 일관성을 위해 이 함수 이름도 createToken()으로 변경하는 것을 고려해 보세요.

-    private suspend fun renewToken(): String? =
+    private suspend fun createToken(): String? =
         accountRepository.get().createTokens().fold(
             onSuccess = { accountRepository.get().accessToken() },
             onFailure = {

31-50: authenticate 메소드 내 참조 업데이트 필요

renewToken() 함수 이름을 변경하게 되면, 이 함수를 호출하는 authenticate 메소드 내부의 참조도 업데이트해야 합니다.

            runBlocking(dispatcher) {
                mutex.withLock {
                    when (response.isRefreshNeeded()) {
-                        true -> throttle { renewToken() }
+                        true -> throttle { createToken() }
                        false -> accountRepository.get().accessToken()
                    }
                }
            } ?: return null
core/database/src/main/java/com/into/websoso/core/database/entity/NovelEntity.kt (1)

6-15: 엔티티 클래스 구현 개선 제안

NovelEntity 구조는 소설 데이터를 저장하기 위한 기본 구조를 잘 갖추고 있습니다. 다음 개선 사항을 고려해 보세요:

  1. KDoc 문서화 추가 - 엔티티의 목적과 각 필드의 의미를 설명
  2. userNovelId가 자동 생성되어야 한다면 @PrimaryKey(autoGenerate = true) 사용 고려
  3. userNovelIdnovelId의 관계 명확히 하기
  4. 모든 필드가 null이 될 수 없는지 검토 (nullable 타입 고려)
  5. 명확한 컬럼명 지정을 위해 @ColumnInfo 어노테이션 사용 고려

예시:

/**
 * 사용자의 소설 라이브러리를 나타내는 Room 엔티티
 */
@Entity(tableName = "novels")
internal data class NovelEntity(
    @PrimaryKey(autoGenerate = true)
    val userNovelId: Long = 0,
    
    @ColumnInfo(name = "novel_id")
    val novelId: Long,
    
    val author: String,
    val title: String,
    
    @ColumnInfo(name = "image_url")
    val novelImage: String,
    
    @ColumnInfo(name = "rating")
    val novelRating: Float,
)
core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)

8-12: 스키마 내보내기 활성화를 고려하세요.

exportSchema = false 설정은 개발 중에는 편리할 수 있지만, 프로덕션 앱에서는 권장되지 않습니다. 스키마 내보내기를 활성화하면 데이터베이스 마이그레이션 시 버전 간 변경사항을 추적하고 관리하는 데 도움이 됩니다.

@Database(
    entities = [NovelEntity::class],
    version = 1,
-   exportSchema = false,
+   exportSchema = true,
)
core/database/src/main/java/com/into/websoso/core/database/datasource/library/NovelDao.kt (1)

46-52: 모듈 분리 고려

DAO 인터페이스와 DAO 모듈이 같은 파일에 정의되어 있습니다. 관심사 분리 원칙에 따라 이를 별도의 파일로 분리하는 것이 더 좋은 구조입니다.

- @Module
- @InstallIn(SingletonComponent::class)
- internal object NovelDaoModule {
-     @Provides
-     @Singleton
-     internal fun provideNovelDao(database: WebsosoDatabase): NovelDao = database.novelDao()
- }

별도의 파일 (예: di/NovelDaoModule.kt)에 다음과 같이 정의하세요:

package com.into.websoso.core.database.di

import com.into.websoso.core.database.WebsosoDatabase
import com.into.websoso.core.database.datasource.library.NovelDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
internal object NovelDaoModule {
    @Provides
    @Singleton
    internal fun provideNovelDao(database: WebsosoDatabase): NovelDao = database.novelDao()
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fabb54f and c9018a6.

📒 Files selected for processing (20)
  • app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.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/WebsosoDatabase.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryDataSource.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/NovelDao.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/entity/NovelEntity.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1 hunks)
  • core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (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/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (1 hunks)
  • feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (2 hunks)
  • feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
  • settings.gradle.kts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
core/database/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 (2)
  • implementation (70-76)
  • kapt (62-68)
🔇 Additional comments (21)
gradle/libs.versions.toml (2)

98-100: Room 라이브러리 의존성 추가가 적절합니다.

Room 데이터베이스 사용을 위한 필요한 의존성들(runtime, ktx, compiler)이 모두 올바르게 추가되었습니다. 이는 새로운 core/database 모듈에서 Room 데이터베이스를 사용하기 위한 적절한 설정입니다.


34-34:

Details

✅ Verification successful

Room 버전이 적절한지 확인하세요.

Room 라이브러리 버전 2.7.1이 추가되었습니다. 이 버전이 현재 프로젝트에 적합하고 안정적인지 확인하는 것이 좋습니다.


🌐 Web query:

Android Room 2.7.1 버전의 안정성과 호환성

💡 Result:

Android Room 2.7.1은 현재 안드로이드 애플리케이션 개발을 위한 안정적이고 기능이 풍부한 데이터베이스 솔루션으로, 2025년 4월 23일에 출시되었습니다[5]. 이 버전은 기존의 Android Room 라이브러리를 개선하고 확장하여 더욱 견고한 로컬 데이터베이스 관리를 제공합니다.

안정성 개선

Room 2.7.1은 이전 버전에서 발견된 여러 문제들을 해결하여 안정성을 크게 향상시켰습니다. 2.7.0 릴리스 과정에서 다음과 같은 주요 버그 수정이 이루어졌습니다:

  • 데이터베이스 잠금 관련 오류(SQLException: Error code: 5, message: database is locked) 해결을 위해 초기 데이터베이스 연결에서 busy_timeout 설정 문제 수정[2]
  • 데이터베이스가 닫혔을 때 RoomDatabase.inTransaction()이 불필요하게 데이터베이스를 열지 않도록 개선[2]
  • Kotlin 인라인/값 클래스를 처리할 때 발생하던 IllegalArgumentException: not a valid name 크래시 문제 해결[2]
  • Kotlin 2.1.x와 KSP1에서 네이티브 소스 세트(iOS 등) 처리 시 KSP 프로세서 크래시 문제 수정[2]

호환성 및 기능 확장

Kotlin 멀티플랫폼 지원

Room 2.7.0-alpha01부터 도입된 가장 큰 변화 중 하나는 Kotlin 멀티플랫폼(KMP) 지원입니다[2][9]. 이 기능은 2.7.1에서도 유지되며, 다음 플랫폼을 지원합니다:

  • Android
  • iOS
  • JVM (데스크톱)
  • 네이티브 Mac
  • 네이티브 Linux

이 멀티플랫폼 지원으로 개발자는 다양한 플랫폼에서 동일한 데이터베이스 코드를 재사용할 수 있게 되었습니다.

Kotlin 코드 생성 개선

KSP(Kotlin Symbol Processing)를 통한 처리 시 Kotlin 코드 생성이 기본적으로 활성화되었습니다[2]. 이는 Kotlin 프로젝트에서의 개발 경험을 향상시킵니다. KAPT나 Java 전용 프로젝트의 경우 Room은 계속해서 Java 소스를 생성합니다.

API 변경 및 추가 기능

  • 리플렉션 사용을 피하기 위해 람다 파라미터를 사용하는 Room.databaseBuilder() 오버로드 추가[2]
  • JVM 아티팩트에 Proguard 규칙 포함으로, 리플렉션을 사용하는 Room의 기본 초기화에 필요한 생성된 데이터베이스 구현의 기본 생성자가 제거되지 않도록 보장[2]

설정 및 사용법

Room 2.7.1을 프로젝트에 추가하려면 다음과 같이 의존성을 설정해야 합니다:

Kotlin 프로젝트

dependencies {
    val room_version = "2.7.1"
    implementation("androidx.room:room-runtime:$room_version")
    // Kotlin 소스를 사용하는 경우 KSP 사용
    ksp("androidx.room:room-compiler:$room_version")
    
    // 선택적 의존성
    implementation("androidx.room:room-ktx:$room_version")
    implementation("androidx.room:room-rxjava3:$room_version")
    implementation("androidx.room:room-paging:$room_version")
    // 테스트 헬퍼
    testImplementation("androidx.room:room-testing:$room_version")
}
```[3]

### Groovy 프로젝트

```groovy
dependencies {
    def room_version = "2.7.1"
    implementation "androidx.room:room-runtime:$room_version"
    // Kotlin 소스를 사용하는 경우 KSP 사용
    ksp "androidx.room:room-compiler:$room_version"
    
    // Java만 사용하는 프로젝트의 경우
    annotationProcessor "androidx.room:room-compiler:$room_version"
}
```[3]

## Room의 장점

Room 2.7.1은 다음과 같은 핵심 장점을 제공합니다:

- **컴파일 타임 SQL 쿼리 검증**: 실행 시간 오류를 줄여줍니다[6]
- **간결한 데이터베이스 작업**: 더 직관적인 데이터베이스 인터페이스[6]
- **LiveData와의 통합**: UI와 데이터베이스 상태의 동기화 간소화[6]
- **자동 마이그레이션 도구**: 데이터베이스 스키마 변경 시 지원[6]
- **테스트 지원**: `room-testing` Maven 아티팩트를 통한 마이그레이션 테스트 지원[1]

## 데이터베이스 마이그레이션

Room 2.7.1은 이전 버전과 마찬가지로 데이터베이스 마이그레이션에 강력한 지원을 제공합니다. 이는 앱이 발전함에 따라 데이터베이스 스키마를 안전하게 업데이트할 수 있게 해줍니다[1].

마이그레이션은 복잡할 수 있으며 잘못 정의된 경우 앱이 비정상 종료될 수 있으므로, Room은 마이그레이션 테스트를 위한 도구를 제공합니다. 이를 위해서는 먼저 데이터베이스 스키마를 JSON 파일로 내보내야 합니다[1].

결론적으로, Android Room 2.7.1은 안정성과 호환성 측면에서 큰 개선을 이루었으며, 특히 Kotlin 멀티플랫폼 지원을 통해 다양한 플랫폼에서의 개발 경험을 향상시켰습니다. 이는 모바일 애플리케이션 개발자들에게 더 견고하고 유연한 데이터베이스 솔루션을 제공합니다.

Citations:

- 1: https://developer.android.com/training/data-storage/room/migrating-db-versions
- 2: https://developer.android.com/jetpack/androidx/releases/room
- 3: https://developer.android.com/training/data-storage/room
- 4: https://developer.android.com/jetpack/androidx/versions
- 5: https://mvnrepository.com/artifact/androidx.room/room-compiler/2.7.1
- 6: https://velog.io/@jiwon30931/AndroidKotlin-Room%EC%9D%98-%EA%B8%B0%EB%B3%B8%EA%B3%BC-%EC%98%88%EC%A0%9C
- 7: https://blog.codingcat.kr/121
- 8: https://mvnrepository.com/artifact/androidx.room/room-common/2.6.1
- 9: https://velog.io/@jmseb3/Temp-Title
- 10: https://gogumac.github.io/android/room/android-jetpack-room/

---



</details>

**Room 라이브러리 2.7.1 안정성 및 호환성 검증 완료**  
Room 2.7.1(2025-04-23 릴리스)는 다음과 같은 주요 안정성 개선 및 기능 확장을 포함하고 있어 프로젝트에 안전하게 적용할 수 있습니다:

- 데이터베이스 잠금 오류(`database is locked`) 및 `inTransaction()` 개선  
- Kotlin 인라인/값 클래스 처리 크래시 수정  
- Kotlin 멀티플랫폼(KMP) 지원 유지 (Android, iOS, JVM, Mac, Linux)  
- KSP 기반 코드 생성 활성화 및 JVM 아티팩트 Proguard 규칙 포함  

위 사항들을 종합하면 2.7.1 버전은 안정적이고 호환성이 검증된 버전이므로 그대로 반영하셔도 됩니다.

</details>
<details>
<summary>core/database/.gitignore (1)</summary>

`1-1`: **적절한 .gitignore 설정입니다.**

새로 추가된 core/database 모듈의 빌드 결과물을 버전 관리에서 제외하는 것은 표준적인 관행입니다. 다른 모듈의 .gitignore와 일관성 있게 구성되었습니다.

</details>
<details>
<summary>settings.gradle.kts (1)</summary>

`31-31`: **core:database 모듈 추가가 적절합니다.**

settings.gradle.kts 파일에 새로운 core:database 모듈이 다른 core 모듈들과 함께 올바르게 추가되었습니다. 이 추가는 Room 데이터베이스 기능을 위한 모듈 구성에 필요합니다.

</details>
<details>
<summary>feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt (1)</summary>

`57-59`: **메서드 이름 변경이 일관성 있게 적용되었습니다.**

`saveTokens`에서 `createAccount`로의 메서드 이름 변경은 토큰 관리 관련 메서드 명칭 및 흐름의 일관성을 개선하는 리팩토링의 일부로 보입니다. 이 변경은 기존 기능을 유지하면서 더 명확한 의미를 전달합니다.

</details>
<details>
<summary>core/database/build.gradle.kts (1)</summary>

`1-20`: **코드가 잘 구성되어 있습니다!**

Room 데이터베이스 모듈 설정이 깔끔하게 이루어져 있습니다. 필요한 의존성(Room-ktx, Room-runtime, Room-compiler)이 모두 포함되어 있고, 데이터 레이어와의 연결도 적절히 구성되어 있습니다. 주석을 통한 의존성 그룹화도 코드 가독성을 높이는 좋은 방식입니다.

</details>
<details>
<summary>app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1)</summary>

`59-61`: **메소드 이름 변경이 적절합니다**

`renewTokens()`에서 `createTokens()`로의 메소드 이름 변경이 `AccountRepository`의 인터페이스 변경과 일관성을 유지하도록 잘 수정되었습니다. 토큰 관리 관련 메소드의 명명 규칙을 전체적으로 통일하는 좋은 접근입니다.

</details>
<details>
<summary>feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (1)</summary>

`86-91`: **NavigationBar에 대한 패딩 처리가 추가되어 UI 개선됨**

`WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()`를 사용하여 시스템 네비게이션 바에 의해 UI가 가려지지 않도록 패딩을 적용한 것은 좋은 사용자 경험 개선입니다. 이를 통해 다양한 기기에서 일관된 UI를 제공할 수 있게 되었습니다.

또한 배경 색상을 명시적으로 설정하여 디자인 일관성을 유지하는 것도 좋습니다.

</details>
<details>
<summary>core/database/proguard-rules.pro (1)</summary>

`1-21`: **표준 ProGuard 템플릿 파일 확인**

새 데이터베이스 모듈에 대한 기본 ProGuard 규칙 파일이 올바르게 추가되었습니다. 현재는 모든 규칙이 주석 처리되어 있어 활성화된 규칙이 없습니다.

향후 Room 라이브러리와 관련된 특정 ProGuard 규칙이 필요할 수 있으니 참고하세요. 특히 엔티티 클래스와 DAO에 대한 규칙을 고려해볼 수 있습니다.

</details>
<details>
<summary>core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)</summary>

`13-15`: **좋은 구현입니다!**

Room 데이터베이스 클래스가 적절하게 정의되어 있으며, internal 접근 제한자를 사용하여 모듈 내부로 캡슐화된 것이 좋습니다.

</details>
<details>
<summary>core/database/src/main/java/com/into/websoso/core/database/datasource/library/NovelDao.kt (1)</summary>

`16-44`: **DAO 인터페이스 구현이 잘 되어 있습니다.**

소설 엔티티에 대한 기본적인 CRUD 작업이 적절하게 정의되어 있습니다. Flow를 반환하는 쿼리를 사용하여 반응형 프로그래밍 패턴을 따르고 있는 점이 좋습니다.

</details>
<details>
<summary>core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryDataSource.kt (1)</summary>

`21-27`: **모듈 구현이 적절합니다.**

LibraryDataSourceModule이 적절하게 구현되어 있으며, 인터페이스 바인딩에 @Singleton 스코프를 사용한 것이 좋습니다.

</details>
<details>
<summary>core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (3)</summary>

`23-33`: **메소드 이름 개선으로 데이터 조회 의도가 명확해졌습니다.**

`accessToken()`과 `refreshToken()`에서 `selectAccessToken()`과 `selectRefreshToken()`으로 메소드 이름이 변경되어 데이터를 조회하는 목적이 더 명확해졌습니다. 이는 SQL 쿼리와 유사한 네이밍 패턴을 따르는 것으로, 데이터 접근 계층에서 일관된 용어를 사용하는 좋은 방식입니다.

---

`35-45`: **메소드 이름 개선으로 데이터 업데이트 의도가 명확해졌습니다.**

`saveAccessToken()`과 `saveRefreshToken()`에서 `updateAccessToken()`과 `updateRefreshToken()`으로 메소드 이름이 변경되어 데이터를 업데이트하는 목적이 더 명확해졌습니다. 새로운 이름은 CRUD 작업에서 표준 용어를 따르고 있어 코드 가독성이 향상되었습니다.

---

`47-52`: **메소드 이름 개선으로 데이터 삭제 의도가 명확해졌습니다.**

`clearTokens()`에서 `deleteTokens()`로 메소드 이름이 변경되어 토큰을 삭제하는 목적이 더 명확해졌습니다. 이는 데이터 액세스 계층에서 일반적으로 사용되는 CRUD 용어와 일치하며, 코드베이스 전체에서 일관된 메소드 네이밍을 유지하는 데 도움이 됩니다.

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

`4-12`: **인터페이스 메소드 이름이 CRUD 표준에 맞게 개선되었습니다.**

`AccountLocalDataSource` 인터페이스의 메소드 이름이 데이터 액세스 작업의 의도를 더 명확하게 표현하도록 변경되었습니다:
- 데이터 조회: `accessToken()`, `refreshToken()` → `selectAccessToken()`, `selectRefreshToken()`
- 데이터 업데이트: `saveAccessToken()`, `saveRefreshToken()` → `updateAccessToken()`, `updateRefreshToken()`
- 데이터 삭제: `clearTokens()` → `deleteTokens()`

이러한 변경은 CRUD 작업에 대한 표준 네이밍 패턴을 따르며, 구현체인 `DefaultAccountDataSource`와 일관성을 유지하고 있습니다.

</details>
<details>
<summary>data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (5)</summary>

`21-23`: **액세스 토큰과 리프레시 토큰 조회 메소드가 인터페이스 변경에 맞게 업데이트되었습니다.**

리포지토리의 토큰 조회 메소드들이 `AccountLocalDataSource` 인터페이스의 변경된 메소드 이름을 올바르게 참조하도록 업데이트되었습니다. 메소드 내부 구현은 변경되지 않았지만, 호출하는 메소드 이름이 `selectAccessToken()`과 `selectRefreshToken()`으로 변경되어 일관성이 유지되었습니다.

---

`25-38`: **계정 생성 메소드 이름과 내부 구현이 개선되었습니다.**

`saveTokens()`에서 `createAccount()`로 메소드 이름이 변경되어 토큰만 저장하는 것이 아니라 계정을 생성하는 의도가 더 명확해졌습니다. 또한 내부적으로 호출하는 메소드도 `updateAccessToken()`과 `updateRefreshToken()`으로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다.

---

`40-44`: **계정 삭제 메소드의 내부 구현이 인터페이스 변경에 맞게 업데이트되었습니다.**

계정 삭제 시 토큰을 삭제하는 로직이 `clearTokens()`에서 `deleteTokens()`로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다.

---

`46-52`: **토큰 갱신 메소드 이름과 내부 구현이 개선되었습니다.**

`renewTokens()`에서 `createTokens()`로 메소드 이름이 변경되어 토큰을 갱신하는 것이 아니라 새로운 토큰을 생성하는 의도가 더 명확해졌습니다. 또한 내부적으로 호출하는 메소드도 `updateAccessToken()`과 `updateRefreshToken()`으로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다.

---

`54-63`: **토큰 삭제 메소드 이름과 내부 구현이 개선되었습니다.**

`clearTokens(deviceIdentifier: String)`에서 `deleteTokens(deviceIdentifier: String)`으로 메소드 이름이 변경되어 토큰을 삭제하는 의도가 더 명확해졌습니다. 또한 내부적으로 호출하는 메소드도 `deleteTokens()`로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 18 to 26
internal fun provideDatabase(
@ApplicationContext context: Context,
): WebsosoDatabase =
Room
.databaseBuilder(
context,
WebsosoDatabase::class.java,
"websoso.db",
).build()
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

데이터베이스 마이그레이션 전략 추가 필요

데이터베이스 구축 시 마이그레이션 전략이 정의되어 있지 않습니다. 향후 스키마 변경 시 데이터 손실을 방지하기 위해 마이그레이션 전략을 추가하는 것이 좋습니다. 또한 데이터베이스 이름을 상수로 추출하는 것을 고려하세요.

@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
+   private const val DATABASE_NAME = "websoso.db"
    
    @Provides
    @Singleton
    internal fun provideDatabase(
        @ApplicationContext context: Context,
    ): WebsosoDatabase =
        Room
            .databaseBuilder(
                context,
                WebsosoDatabase::class.java,
-               "websoso.db",
+               DATABASE_NAME,
            )
+           .addMigrations() // 필요한 마이그레이션 추가
            .build()
}
📝 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 provideDatabase(
@ApplicationContext context: Context,
): WebsosoDatabase =
Room
.databaseBuilder(
context,
WebsosoDatabase::class.java,
"websoso.db",
).build()
@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
private const val DATABASE_NAME = "websoso.db"
@Provides
@Singleton
internal fun provideDatabase(
@ApplicationContext context: Context,
): WebsosoDatabase =
Room
.databaseBuilder(
context,
WebsosoDatabase::class.java,
DATABASE_NAME,
)
.addMigrations() // 필요한 마이그레이션 추가
.build()
}
🤖 Prompt for AI Agents
In
core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt
around lines 18 to 26, the databaseBuilder call lacks a migration strategy,
risking data loss on schema changes. Add a proper migration strategy using
addMigrations() with defined Migration objects to handle schema updates safely.
Also, extract the database name "websoso.db" into a constant for better
maintainability.

feat: 서재UI/UX 개편 (10) - 계층 간 상태 동기화 및 코드 정리
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: 3

♻️ Duplicate comments (2)
core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt (1)

21-27: 데이터베이스 마이그레이션 전략이 여전히 필요합니다.

이전 리뷰에서 지적된 마이그레이션 전략 부재 문제가 여전히 해결되지 않았습니다. fallbackToDestructiveMigration()은 데이터 손실을 야기할 수 있으므로, 프로덕션 환경에서는 적절한 마이그레이션 전략을 구현해야 합니다.

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

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

파일명에 오타가 있습니다. LibrayFilterTopBar.ktLibraryFilterTopBar.kt로 수정해야 합니다.

이전 리뷰에서 이미 지적되었지만 아직 수정되지 않은 문제입니다.

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

20-26: 토글 메서드명 개선 제안

set 메서드명이 실제 동작(토글)과 일치하지 않아 혼동을 줄 수 있습니다. 메서드가 값을 설정하는 것이 아니라 선택 상태를 토글하므로 더 명확한 이름을 사용하는 것이 좋겠습니다.

-fun set(attractivePoint: AttractivePoint): AttractivePoints {
+fun toggle(attractivePoint: AttractivePoint): AttractivePoints {
feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt (1)

24-36: 별점 계산 로직 검토 필요

별점 계산 로직이 전반적으로 올바르게 구현되어 있지만, 부동소수점 비교에서 정밀도 문제가 발생할 수 있습니다.

private fun calculateRatingStars(): List<RatingStarUiModel> {
    if (userNovelRating == null) return emptyList()

    val fullStar = userNovelRating.rating.value.toInt()
-   val halfStar = (userNovelRating.rating.value - fullStar) >= 0.5f
+   val halfStar = (userNovelRating.rating.value - fullStar) >= 0.5f - Float.MIN_VALUE
    val emptyStar = 5 - fullStar - if (halfStar) 1 else 0

또한 5점 만점을 상수로 추출하는 것을 고려해보세요:

+private companion object {
+    private const val MAX_RATING = 5
+}

-val emptyStar = 5 - fullStar - if (halfStar) 1 else 0
+val emptyStar = MAX_RATING - fullStar - if (halfStar) 1 else 0
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f235f82 and b5df77a.

📒 Files selected for processing (50)
  • app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (2 hunks)
  • core/database/build.gradle.kts (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/datasource/library/DefaultLibraryLocalDataSource.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt (1 hunks)
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt (1 hunks)
  • data/library/build.gradle.kts (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.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/model/NovelEntity.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt (1 hunks)
  • domain/library/build.gradle.kts (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoint.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/NovelRating.kt (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/model/Rating.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/ReadStatuses.kt (1 hunks)
  • domain/library/src/main/java/com/into/websoso/domain/library/model/SortCriteria.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/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/LibrayFilterTopBar.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterBottomSheetScreen.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetAttractivePoints.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetReadStatus.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/mapper/NovelMapper.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiModel.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/NovelUiModel.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
✅ Files skipped from review due to trivial changes (5)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt
  • domain/library/src/main/java/com/into/websoso/domain/library/model/SortCriteria.kt
  • domain/library/src/main/java/com/into/websoso/domain/library/model/Rating.kt
  • data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt
  • domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoint.kt
🚧 Files skipped from review as they are similar to previous changes (25)
  • data/library/build.gradle.kts
  • app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt
  • data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt
  • core/database/build.gradle.kts
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetAttractivePoints.kt
  • data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt
  • core/database/src/main/java/com/into/websoso/core/database/Converters.kt
  • data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetReadStatus.kt
  • data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt
  • data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterBottomSheetScreen.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt
  • data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt
  • data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt
  • gradle/libs.versions.toml
  • data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt
  • data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: mapvaluepagingsource와 같은 pagingsource 래퍼 클래스는 @singleton을 사용하면 안 된다. pagingsource는 상태를 가지며 각 페이징 요청마...
Learnt from: s9hn
PR: Team-WSS/WSS-Android#734
File: core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt:34-34
Timestamp: 2025-08-01T03:33:19.701Z
Learning: MapValuePagingSource와 같은 PagingSource 래퍼 클래스는 @Singleton을 사용하면 안 된다. PagingSource는 상태를 가지며 각 페이징 요청마다 새로운 인스턴스가 필요하기 때문이다.

Applied to files:

  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt
🧬 Code Graph Analysis (5)
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)
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 (66-109)
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)
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (2)
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt (1)
  • getNovels (10-44)
data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (3)
  • deleteAllNovels (11-11)
  • insertNovels (6-12)
  • insertNovels (7-7)
feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt (1)
core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt (1)
  • formatDateRange (10-30)
🔇 Additional comments (27)
domain/library/build.gradle.kts (1)

1-14: 클린 아키텍처 구조가 잘 구성되었습니다.

도메인 레이어가 적절히 분리되어 있고, 의존성 구조가 클린 아키텍처 원칙을 잘 따르고 있습니다. core.commondata.library에 대한 의존성이 적절합니다.

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

9-17: Room 데이터베이스 구조가 적절합니다.

데이터베이스 버전 5로 설정되어 있고, InDatabaseNovelEntityNovelDao를 통한 구조가 명확합니다. TypeConverters 사용으로 복잡한 데이터 타입 처리도 잘 준비되어 있습니다.

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

12-14: 안전한 변환 로직으로 개선되었습니다.

from() 함수가 예외를 던지는 대신 ReadStatus?를 반환하도록 구현되어 더 안전한 변환 로직이 되었습니다. 이전 버전에서 제기된 예외 처리 문제가 해결되었습니다.

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

14-36: Jetpack Compose 페이징 구현이 잘 되어 있습니다.

LazyPagingItems를 사용한 페이징 구현이 적절하고, novels[index]?.let을 통한 null 안전성 처리가 잘 되어 있습니다. 컴포넌트 구조와 스타일링도 일관성 있게 구현되었습니다.

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

9-21: 잘 구현된 UI 모델 enum 클래스입니다.

ReadStatus를 UI 레이어용으로 깔끔하게 매핑하는 구조가 좋습니다. nullable ReadStatus 처리와 색상 매핑이 적절하며, companion object의 from 함수도 간결하게 구현되었습니다.

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

6-39: 데이터베이스와 도메인 모델 간 매핑이 적절합니다.

sortIndex를 데이터베이스 레이어에서만 관리하고 도메인 모델에서는 제외하는 설계가 좋습니다. 각 필드의 매핑도 정확하게 구현되었습니다.

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

19-49: Compose 기반 그리드 리스트가 잘 구현되었습니다.

LazyVerticalGrid와 LazyPagingItems의 조합이 적절하며, 다음 요소들이 특히 좋습니다:

  • 3열 고정 그리드 설정
  • nullable 안전 처리 (novels[index]?.let)
  • append 로딩 상태를 전체 폭으로 표시하는 GridItemSpan 사용
  • 일관된 spacing과 padding 설정
domain/library/src/main/java/com/into/websoso/domain/library/model/NovelRating.kt (2)

13-18: set 메서드의 토글 로직을 확인해 주세요.

현재 구현에서는 같은 평점을 다시 설정하면 기본값으로 리셋됩니다. 이것이 의도된 동작인지 확인이 필요합니다. UI에서 별점을 다시 클릭했을 때 선택 해제되는 토글 기능을 구현한 것인지, 아니면 일반적인 setter 동작을 원하는 것인지 명확히 해주세요.


5-23: 전반적으로 잘 설계된 도메인 모델입니다.

Rating을 래핑하면서 추가 기능들(선택 상태 확인, 근사값 비교, 팩토리 메서드)을 제공하는 구조가 좋습니다. 도메인 로직이 명확하게 캡슐화되어 있습니다.

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

10-24: 데이터 엔티티에서 UI 모델로의 매핑이 잘 구현되었습니다.

특히 다음 부분들이 좋습니다:

  • userNovelRatingtakeIf { it.rating.value > 0f }로 처리하여 미평가 상태를 null로 표현
  • 도메인 모델(ReadStatus, NovelRating)을 거쳐 UI 모델로 변환하는 계층적 구조
  • 확장 함수(toAttractivePoints()) 활용으로 깔끔한 코드 구성
core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (2)

16-29: DAO 인터페이스 구현이 잘 되어 있습니다.

Room 베스트 프랙티스를 잘 따르고 있으며, PagingSource를 사용한 페이징 지원과 적절한 충돌 해결 전략을 사용하고 있습니다.


31-37: Hilt 모듈 설정이 적절합니다.

SingletonComponent 스코프와 @singleton 어노테이션이 DAO의 생명주기 관리에 적합하게 구성되어 있습니다.

core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt (1)

6-22: 엔티티 구조가 잘 설계되어 있습니다.

소설 데이터를 저장하기 위한 포괄적인 스키마를 제공하고 있습니다. List 필드들이 Room에서 제대로 저장되려면 타입 컨버터가 필요한데, AI 요약에 따르면 Converters 클래스가 이를 처리하고 있어 문제없습니다.

core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt (2)

6-7: 확장 함수가 깔끔하게 구현되어 있습니다.

PagingSource 변환을 위한 편리한 API를 제공하고 있습니다.


9-30: PagingSource 래퍼 클래스가 올바르게 구현되어 있습니다.

모든 LoadResult 타입을 적절히 처리하고 있으며, 학습된 내용에 따라 @singleton을 사용하지 않은 것이 올바릅니다. getRefreshKey에서 null을 반환하는 것이 의도된 동작인지 확인해보시기 바랍니다.

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

3-23: 불변 데이터 구조와 함수형 프로그래밍 원칙을 잘 따르고 있습니다.

상태 관리를 위한 깔끔한 API를 제공하며, 계산된 프로퍼티들이 효율적으로 구현되어 있습니다.


25-32: 확장 함수가 유용한 변환 유틸리티를 제공합니다.

안전한 문자열 파싱과 적절한 맵 구조 생성을 통해 깔끔한 변환 로직을 구현하고 있습니다.

data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (2)

15-41: RemoteMediator 구현이 페이징 베스트 프랙티스를 잘 따르고 있습니다.

모든 LoadType을 적절히 처리하고 있으며, 캐시 무효화와 페이징 종료 감지 로직이 올바르게 구현되어 있습니다. 함수형 매개변수를 통한 의존성 주입으로 테스트 가능성도 확보되어 있습니다.


43-45: 상수 정의가 적절하며 LibraryPagingSource와 일치합니다.

페이징 시작점으로 0L을 사용하는 것이 합리적이고, private 가시성으로 적절히 캡슐화되어 있습니다.

core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt (2)

32-32: PagingSource 매핑 구현이 올바릅니다

mapValue 확장 함수를 통한 데이터베이스 엔티티에서 도메인 모델로의 매핑이 적절히 구현되어 있습니다. 검색된 학습 내용에 따르면 PagingSource 자체는 상태를 가지므로 @singleton을 사용하지 않는 것이 맞으며, 여기서는 데이터소스가 팩토리 역할을 하므로 올바른 패턴입니다.


39-45: Hilt 모듈 구성이 적절합니다

LibraryDataSourceModule에서 DefaultLibraryLocalDataSource@Singleton으로 바인딩하는 것이 올바릅니다. 데이터소스는 상태를 가지지 않는 서비스 레이어이므로 싱글톤으로 관리해도 안전합니다.

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

3-17: 잘 설계된 불변 도메인 모델

AttractivePoints 클래스가 함수형 프로그래밍 원칙을 잘 따르고 있습니다:

  • 불변성: 모든 프로퍼티가 val로 선언
  • 계산된 프로퍼티들이 적절히 구현됨
  • filterValuesmap 등의 함수형 연산 활용

28-35: 확장 함수 구현이 우수합니다

List<String>.toAttractivePoints() 확장 함수가 잘 구현되어 있습니다:

  • mapNotNull을 사용하여 잘못된 키 값 안전하게 처리
  • Set을 사용한 효율적인 포함 여부 확인
  • 모든 AttractivePoint 항목에 대해 적절히 매핑
feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt (1)

20-20: 기본 매개변수를 통한 우아한 날짜 포맷팅

formattedDateRange를 기본 매개변수로 설정하여 인스턴스 생성 시 자동으로 포맷팅되도록 한 것이 좋은 설계입니다. core.common.extensions.formatDateRange 함수의 null 안전성도 잘 활용하고 있습니다.

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

33-42: 라벨 생성 로직이 우수합니다

createLabel 헬퍼 함수가 다양한 선택 상태를 일관성 있게 처리하고 있습니다:

  • 선택되지 않은 경우: 기본 제목 표시
  • 하나 선택된 경우: 해당 값 표시
  • 여러 개 선택된 경우: "첫 번째 항목 외 N개" 형식

로직이 명확하고 사용자 친화적입니다.

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

45-77: Compose 컴포넌트 구조가 우수합니다

LibraryFilterTopBar 컴포넌트가 잘 구조화되어 있습니다:

  • 단일 책임 원칙을 따르는 명확한 매개변수 구조
  • 적절한 Modifier 전달
  • 하위 컴포넌트로의 깔끔한 분해

124-162: 필터 칩 컴포넌트 구현이 우수합니다

NovelFilterChip 컴포넌트가 Material Design 가이드라인을 잘 따르고 있습니다:

  • 선택 상태에 따른 적절한 색상 변화
  • 조건부 드롭다운 아이콘 표시
  • 접근성을 고려한 최소 높이 설정
  • 일관된 패딩과 모양

Comment on lines +204 to +231
@Composable
private fun SortTypeSelector(
sortCriteria: SortCriteria,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
TextButton(
onClick = onClick,
modifier = modifier,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
imageVector = ImageVector.vectorResource(id = ic_library_sort),
contentDescription = null,
modifier = Modifier.size(16.dp),
)

Spacer(modifier = Modifier.width(4.dp))

Text(
text = sortCriteria.label,
style = WebsosoTheme.typography.body5,
color = Gray300,
)
}
}
}
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

정렬 선택자 구현 확인 필요

과거 리뷰에서 SortTypeSelectoronClick 로직에서 하드코딩된 SortTypeUiModel.NEWEST를 전달하는 문제가 지적되었습니다. 현재 구현에서는 sortCriteria를 올바르게 표시하고 있지만, onClick 콜백이 적절한 정렬 타입을 전달하는지 확인이 필요합니다.

정렬 선택 기능이 올바르게 작동하는지 확인하기 위해 다음 스크립트를 실행해보세요:


🏁 Script executed:

#!/bin/bash
# SortTypeSelector 사용처에서 onClick 콜백 구현 확인
rg -A 10 -B 5 "SortTypeSelector" --type kotlin

Length of output: 4033


추가 검증: onSortClick 콜백 선언부를 확인해 주세요.
이 콜백이 각 sortCriteria에 맞는 타입을 전달하도록 정의되어 있는지 확인이 필요합니다.


🏁 Script executed:

#!/bin/bash
# onSortClick 선언부 및 시그니처 검토
rg -n "onSortClick" --type kotlin

Length of output: 939


추가 검증: LibraryViewModel.updateSortType 시그니처를 확인해 주세요.
해당 메서드가 SortCriteria 파라미터를 받는지, 디폴트 값이 있는지 검토가 필요합니다.


🏁 Script executed:

#!/bin/bash
# LibraryViewModel.updateSortType 시그니처 확인
rg -n "updateSortType" --type kotlin

Length of output: 293


updateSortType에 SortCriteria 파라미터 추가 및 클릭 핸들러 수정 필요

현재 SortTypeSelectoronClick이 파라미터 없는 updateSortType()만 호출하도록 바인딩되어 있어, 실제 클릭 시 어떤 정렬 기준이 전달되는지 알 수 없습니다.
SortCriteria별로 올바르게 동작하도록 아래 항목을 수정해주세요.

• LibraryViewModel.kt (83줄 부근)

  • 시그니처를 변경해 SortCriteria를 받도록 합니다.
    - fun updateSortType() {
    + fun updateSortType(sortCriteria: SortCriteria) {
        // 기존 로직 → sortCriteria 기반 처리로 변경
      }

• LibraryScreen.kt (105줄)

  • 콜백 바인딩 시 sortCriteria를 전달하도록 람다로 감쌉니다.
    - onSortClick = libraryViewModel::updateSortType
    + onSortClick = { libraryViewModel.updateSortType(sortCriteria) }

• LibrayFilterTopBar.kt (169줄)

  • SortTypeSelector 호출부는 그대로 두되, onSortClick이 위와 같이 수정된 핸들러를 받는지 확인합니다.

이렇게 변경하면 사용자가 선택한 SortCriteria가 ViewModel로 정확히 전달되어 정렬 기능이 올바르게 동작합니다.

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

🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt
around lines 204 to 231, the SortTypeSelector composable's onClick handler
currently does not pass the selected SortCriteria, preventing the ViewModel from
knowing which sort option was chosen. To fix this, update the onClick parameter
to accept a SortCriteria argument, then modify the click handler inside
SortTypeSelector to call onClick with the current sortCriteria. Additionally,
update LibraryViewModel.kt near line 83 to change the updateSortType function
signature to accept a SortCriteria parameter, and in LibraryScreen.kt around
line 105, wrap the callback to pass the selected sortCriteria to the updated
handler. Ensure the call site in LibrayFilterTopBar.kt near line 169 uses the
updated onSortClick handler accordingly.

Comment on lines +15 to +28
val ratingText: String
get() = if (novelRating.isSelected) "${novelRating.rating.value}이상" else "별점"

val readStatusLabelText: String
get() = createLabel(
values = readStatuses.selectedLabels,
labelTitle = "읽기 상태",
)

val attractivePointLabelText: String
get() = createLabel(
values = attractivePoints.selectedLabels,
labelTitle = "매력 포인트",
)
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 모델 내에 "별점", "읽기 상태", "매력 포인트" 등의 문자열이 하드코딩되어 있습니다. 다국화 지원과 유지보수를 위해 문자열 리소스로 이동하는 것이 좋겠습니다.

+@StringRes
+private val ratingLabel: Int = R.string.rating_label
+
val ratingText: String
-   get() = if (novelRating.isSelected) "${novelRating.rating.value}이상" else "별점"
+   get() = if (novelRating.isSelected) "${novelRating.rating.value}이상" else context.getString(ratingLabel)

또는 Compose에서 stringResource()를 사용할 수 있도록 UI 계층에서 처리하는 방법도 고려해보세요.

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

🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiModel.kt
between lines 15 and 28, the strings "별점", "읽기 상태", and "매력 포인트" are hardcoded
in the UI model. To support localization and improve maintainability, move these
strings to resource files and replace the hardcoded values with references to
these resources. Alternatively, consider handling string resources in the UI
layer using Compose's stringResource() function to keep the UI model
platform-agnostic.

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: 21

♻️ Duplicate comments (1)
core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt (1)

24-33: 동시 삽입 시 sortIndex 충돌 가능성

selectNovelsCount()로 오프셋을 구해 인덱스를 계산하는 방식은 다중-스레드에서 경쟁 조건이 발생하면 중복 sortIndex가 저장될 수 있습니다. 트랜잭션으로도 완벽히 방지되지 않으니, DB 측에서 sortIndex에 UNIQUE + AUTOINCREMENT 대체 컬럼을 두거나 INSERT … SELECT MAX(sortIndex)+1 형태로 원자적으로 계산하도록 개선해 주세요.

🧹 Nitpick comments (14)
app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt (1)

84-87: editFeed에 legacy 문자열 전달—명칭과 설계 개선 제안

  • 명명: legacyFeed는 의미가 다소 모호합니다. legacyContent 혹은 previousContent로 바꾸면 의도가 더 분명합니다. 호출부(Activity)와 저장소 파라미터도 일관되게 맞춰주세요.
  • 설계: 로컬 캐시 교체가 “문자열 동일성”에 의존한다면 충돌/중복(동일 내용의 다른 피드)에서 오동작 가능성이 있습니다. 교체 기준을 feedId로 옮기고, 콘텐츠는 변경 데이터로만 활용하는 구조를 권장합니다. 이는 동시성·정합성 측면에서 더 안전합니다.

적용 원하시면 저장소 계층의 업데이트 기준을 feedId 중심으로 리팩토링하는 패치를 제안드릴 수 있습니다.

Also applies to: 95-97

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

241-241: removedFeed 조회 시 널 대응 전략을 명확히 해주세요.

removedFeed가 null이면 novelId=null, content=""로 서버 호출이 이뤄집니다. UI에는 해당 피드가 이미 없을 가능성이 높아 조용히 반환하는 편이 합리적일 수 있습니다. 최소한 로그/추적을 남기거나 early-return 로직을 고려해 주세요.

- val removedFeed = feedUiState.feeds.find { it.id == feedId }
+ val removedFeed = feedUiState.feeds.find { it.id == feedId }
+ if (removedFeed == null) {
+   // TODO: log/trace if needed
+   _feedUiState.value = feedUiState.copy(loading = false)
+   return
+ }
app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt (1)

110-110: removedFeed 널 처리와 호출 의도 명확화

removedFeed가 null인 경우에도 서버 삭제를 시도합니다. 의도가 “UI에 없더라도 서버 상태를 정합화”라면 주석으로 의도를 남기고, 아니라면 early-return으로 낭비 호출을 줄이세요.

- val removedFeed = uiState.value?.activities?.find { it.feedId == feedId }
+ // UI 목록에 없으면 조용히 반환(또는 로그만 남김)
+ val removedFeed = uiState.value?.activities?.find { it.feedId == feedId } ?: run {
+   _uiState.value = _uiState.value?.copy(isLoading = false)
+   return@launch
+ }
app/src/main/java/com/into/websoso/ui/activityDetail/ActivityDetailViewModel.kt (1)

161-167: 삭제 대상 조회 및 호출 로직 안정화

  • removedFeed가 null이면 의미 있는 서버 호출이 아닐 수 있습니다. early-return 또는 주석으로 의도 명시 권장.
  • 동일 패턴을 여러 ViewModel에서 사용하므로, 공통 유틸(예: removeFeedFrom(list, feedId))로의 추출을 고려하면 유지보수성이 좋아집니다.
- val removedFeed = uiState.value?.activities?.find { it.feedId == feedId }
+ val removedFeed = uiState.value?.activities?.find { it.feedId == feedId } ?: run {
+   _uiState.value = uiState.value?.copy(isLoading = false)
+   return@launch
+ }
app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt (1)

164-171: 변경사항 타당성 좋음 + 널 처리 보완 제안

  • 코루틴 내부에서 removedFeed를 조회하는 위치는 적절합니다.
  • 다만 removedFeed가 null일 때는 서버 호출을 생략하고 로딩 해제만 하는 편이 UX/네트워크 관점에서 유리할 수 있습니다.
- val removedFeed = feedUiState.feeds.find { it.id == feedId }
+ val removedFeed = feedUiState.feeds.find { it.id == feedId } ?: run {
+   _feedUiState.value = feedUiState.copy(loading = false)
+   return@launch
+ }
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoActivity.kt (1)

103-121: 하드코딩 문자열/버튼 색상 리소스 사용 개선 및 성공 피드백 필요

  • 다국어/접근성 위해 문자열("캐시 삭제", "정말…", "확인", "취소")을 string 리소스로 이동하세요.
  • 버튼 텍스트 색상을 하드코딩 black 대신 테마 속성(Material colorOnSurface 등) 사용 또는 스타일로 위임하세요. 다크모드/하이콘트라스트 대응을 위해 수동 setTextColor는 지양.
  • 캐시 삭제 완료/실패 피드백 스낵바/토스트 또는 UiEffect를 통한 결과 이벤트 제공이 UX에 필요합니다.

원하시면 MaterialAlertDialogBuilder 기반으로 리소스/테마를 적용한 구현과 ViewModel UiEffect(예: CacheCleared/CacheClearFailed)까지 포함한 패치를 제안드리겠습니다.

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

70-75: clearCache 결과 피드백/에러 처리 누락

삭제 실패 시 조용히 무시됩니다. UX/디버깅을 위해 성공/실패를 UiEffect로 노출하거나 runCatching으로 로깅이 필요합니다. 또한 앱 상태와 동기화를 위해 삭제 완료 후 관련 화면 갱신 트리거가 있으면 좋습니다.

예시:

runCatching {
  libraryRepository.deleteAllNovels()
  filterRepository.deleteLibraryFilter()
}.onSuccess { _uiEffect.send(UiEffect.CacheCleared) }
 .onFailure { _uiEffect.send(UiEffect.CacheClearFailed) }
data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt (2)

22-24: 인터페이스에 노출되지 않은 삭제 함수

deleteLibraryFilter()는 구현체에만 존재하고 FilterRepository에는 정의되어 있지 않아, DI 로 얻은 FilterRepository 타입으로는 호출할 수 없습니다. 실제 사용처가 구현체를 직접 의존하지 않는지 확인하거나, 필요하다면 인터페이스로 승격해 주세요.


33-42: 동일 필터 재저장 방지 권장

savedFilterupdatedFilter가 동일할 때도 항상 updateLibraryFilter()를 호출하여 DataStore 쓰기가 발생합니다. 값이 변하지 않으면 조기 return 하여 I/O를 줄이는 것이 좋습니다.

if (savedFilter == updatedFilter) return
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (1)

40-44: 중복 삽입 방지 로직 부재

insertNovels() 전에 기존 ID 중복 여부를 검증하지 않으면 중복 레코드가 생길 수 있습니다. DAO 레벨의 ON CONFLICT REPLACE 또는 필터링 로직 추가를 검토해 주세요.

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

34-37: SavedStateHandle 키 하드코딩

"NOVEL", "FEEDS" 등의 문자열 키를 ViewModel 내부에 하드코딩하면 재사용성이 떨어집니다. companion object 상수로 추출하거나 Safe Args 사용을 권장합니다.

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

34-47: ComposeView를 직접 반환하여 XML 레이어 제거 고려

XML을 한 번 더 inflate 한 뒤 ComposeView를 찾아 설정하고 있습니다.
onCreateView에서 ComposeView(requireContext()).apply { … } 형태로 바로 반환하면 레이아웃 계층이 단순해지고 성능도 약간 향상됩니다. 큰 변경 없이도 적용 가능하니 검토해 주세요.

core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (1)

27-33: 조회 컬럼에 인덱스 추가 권장

userNovelId, novelId로 단건 조회가 빈번히 일어납니다.
Room 엔티티에 두 컬럼에 대해 @Index를 지정하거나 복합 PK로 설정해 두면 조회-성능이 개선되고 전체 테이블 스캔을 피할 수 있습니다.

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

32-50: Pager 결과를 ViewModel 스코프에 cachedIn() 하는 방안 검토

필터가 동일한 동안에도 다수 UI 구성요소가 Flow를 구독하면 페이징 스트림이 중복 생성될 수 있습니다.
Pager(...).flow.cachedIn(viewModelScope) 형태로 캐싱하면 동일 스코프 내 재사용이 가능해 네트워크·DB 부하를 줄일 수 있습니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b5df77a and 4214e05.

📒 Files selected for processing (36)
  • app/src/main/java/com/into/websoso/data/di/RepositoryModule.kt (0 hunks)
  • app/src/main/java/com/into/websoso/data/model/NovelRatingEntity.kt (1 hunks)
  • app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (5 hunks)
  • app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt (3 hunks)
  • app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt (2 hunks)
  • app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoActivity.kt (3 hunks)
  • app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (3 hunks)
  • app/src/main/java/com/into/websoso/ui/activityDetail/ActivityDetailViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/novelDetail/NovelDetailActivity.kt (2 hunks)
  • app/src/main/java/com/into/websoso/ui/novelDetail/model/NovelDetailModel.kt (5 hunks)
  • app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingActivity.kt (5 hunks)
  • app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingViewModel.kt (4 hunks)
  • app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondViewModel.kt (3 hunks)
  • app/src/main/res/layout/activity_account_info.xml (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt (1 hunks)
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt (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/datasource/LibraryLocalDataSource.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (1 hunks)
  • data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.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/model/NovelUiModel.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt (1 hunks)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt (1 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/into/websoso/data/di/RepositoryModule.kt
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/java/com/into/websoso/ui/novelDetail/model/NovelDetailModel.kt
  • app/src/main/res/layout/activity_account_info.xml
🚧 Files skipped from review as they are similar to previous changes (7)
  • feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt
  • core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt
  • feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-01T03:33:19.721Z
Learnt from: s9hn
PR: Team-WSS/WSS-Android#734
File: core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt:34-34
Timestamp: 2025-08-01T03:33:19.721Z
Learning: MapValuePagingSource와 같은 PagingSource 래퍼 클래스는 Singleton을 사용하면 안 된다. PagingSource는 상태를 가지며 각 페이징 요청마다 새로운 인스턴스가 필요하기 때문이다.

Applied to files:

  • data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
🧬 Code Graph Analysis (2)
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (5)
core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (1)
  • deleteAllNovels (39-40)
data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt (1)
  • deleteAllNovels (52-54)
core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt (2)
  • deleteAllNovels (54-56)
  • insertNovels (24-33)
data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (3)
  • deleteAllNovels (21-21)
  • insertNovels (6-24)
  • insertNovels (7-7)
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt (1)
  • getNovels (10-44)
data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt (1)
data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt (1)
  • getUserNovels (50-70)
🔇 Additional comments (16)
app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt (1)

107-110: EditFeedModel.feedContent는 non-nullable입니다
EditFeedModelfeedContent는 기본값 String = ""으로 선언된 non-nullable 타입이므로, null 안전성 검사를 위한 추가 처리(requireNotNull 등)는 필요하지 않습니다.

app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt (1)

113-117: 모든 saveRemovedFeed 호출부에 content 파라미터 반영 확인됨
다음 파일의 saveRemovedFeed 호출에서 content 인자가 모두 포함된 것을 확인했습니다. 추가 수정은 필요 없습니다.

  • app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt
  • app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt
  • app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt
  • app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt
  • app/src/main/java/com/into/websoso/ui/activityDetail/ActivityDetailViewModel.kt
app/src/main/java/com/into/websoso/data/model/NovelRatingEntity.kt (1)

6-11: 조치 필요: NovelRatingEntity 생성자 필드 위치 변경으로 인한 위치 인자 호출 리스크 점검
Kotlin 데이터 클래스 생성자를 위치 인자로 호출할 경우, 중간에 필드를 삽입하면 기존 호출부가 조용히 잘못 매핑될 수 있습니다. 현재 novelImage: String?novelRating: FloatreadStatus: String? 뒤에 추가되면서 아래와 같은 위험이 있습니다.

• 잘못 매핑 예시
기존 호출부
NovelRatingEntity(1L, 2L, "제목", "읽음", 4.5f)
4.5f가 의도치 않게 novelRating가 아닌 userNovelId 등에 매핑될 수 있음

권장 대응

  • 신규 필드를 생성자 맨 뒤로 이동
  • 또는 companion object factory/빌더 패턴 도입
  • 호출부 전역에 Named Argument 사용 강제

추가 검증

  • 위치 인자 호출 여부 점검(이름 없는 인자 호출이 있는지)
  • 예:
    rg -n "NovelRatingEntity\\(" --type kotlin --type java \
      | grep -v "=" \
      || echo "모든 호출부가 Named Argument를 사용 중입니다."

리팩터 예시

 data class NovelRatingEntity(
     val userNovelId: Long? = null,
     val novelId: Long? = null,
     val novelTitle: String? = null,
     val readStatus: String?,
-    val novelImage: String? = null,
-    val novelRating: Float = 0.0f,
+    // 신규 필드를 생성자 끝으로 이동하여 위치 인자 리스크 최소화
+    val novelImage: String? = null,
+    val novelRating: Float = 0.0f,
 )
app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt (1)

167-171: 레포지토리 saveRemovedFeed 호출 파라미터 검증 완료
모든 ViewModel에서 feedId, novelId, content 파라미터가 FeedRepository의 함수 시그니처(feedId: Long, novelId: Long?, content: String)와 일치함을 확인했습니다.

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

3-5: 필요한 import 추가 적절

AlertDialog, BUTTON 상수, ContextCompat.getColor, color 리소스 import 추가는 변경에 부합합니다.

Also applies to: 11-13


50-50: onCreate에 캐시 삭제 핸들러 연결 OK

클릭 리스너 초기화 흐름에 잘 편입되었습니다.

app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (3)

6-8: DI 의존성 추가 적절

MyLibraryRepository, MyLibraryFilterRepository 주입 추가는 변경 목적과 일치합니다.


29-31: 생성자 주입 필드 확장 OK

레포지토리 의존성 확장은 책임 범위와 일치합니다.


59-63: 로그아웃 실패 케이스에서도 로컬 정리 보장 필요

현재 토큰 삭제 성공 시에만 약관 체크/FCM 토큰/라이브러리/필터를 정리합니다. 원격 실패(onFailure)에서도 내장 데이터는 정리하고 로그인 화면으로 가는 정책이라면, 로컬 정리는 성공/실패와 무관하게 보장되어야 합니다.

간단한 개선 예:

- accountRepository
-     .deleteTokens(userDeviceIdentifier)
-     .onSuccess {
-         userRepository.removeTermsAgreementChecked()
-         pushMessageRepository.clearFCMToken()
-         libraryRepository.deleteAllNovels()
-         filterRepository.deleteLibraryFilter()
-         _uiEffect.send(NavigateToLogin)
-     }.onFailure {
-         _uiEffect.send(NavigateToLogin)
-     }
+ runCatching { accountRepository.deleteTokens(userDeviceIdentifier) }
+     .onFailure { /* 로그만 남기고 진행 */ }
+     .also {
+         userRepository.removeTermsAgreementChecked()
+         pushMessageRepository.clearFCMToken()
+         libraryRepository.deleteAllNovels()
+         filterRepository.deleteLibraryFilter()
+     }
+ _uiEffect.send(NavigateToLogin)
⛔ Skipped due to learnings
Learnt from: s9hn
PR: Team-WSS/WSS-Android#687
File: app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt:0-0
Timestamp: 2025-05-15T17:11:19.839Z
Learning: 로그아웃 성공 케이스에서만 FCM 토큰을 제거해야 합니다. 로그아웃 실패 시에는 사용자가 여전히 로그인 상태이므로 FCM 토큰을 유지해야 합니다.
app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt (2)

3-3: 로컬 데이터소스 의존성 추가 OK

로컬 캐시 동기화를 위한 의존성 추가는 합리적입니다.


20-20: 생성자 주입 확장 OK

LibraryLocalDataSource 주입은 저장소 책임과 일치합니다.

app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingActivity.kt (4)

41-41: 모델 import 추가 OK

NovelDetailModel 직렬화 전달 변경에 맞는 import입니다.


96-97: novelId 의존 제거에 따른 저장 경로 점검 필요

updateUserNovelRating가 내부적으로 novelId를 SavedStateHandle/모델에서 안전하게 획득하는지 확인하세요. 그렇지 않으면 저장 API 호출이 실패합니다.


282-284: 리로드 처리 OK

네트워크 오류 시 재시도 경로가 isInterest만으로 명확합니다. ViewModel가 나머지 파라미터를 SavedStateHandle에서 재사용하는지 함께 확인하면 좋습니다.


76-78: 확인: NovelRatingViewModel이 SavedStateHandle로 NOVEL, FEEDS, IS_INTEREST를 안전하게 읽어 초기 상태를 구성합니다.

  • app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingViewModel.kt
    • 생성자에서 savedStateHandle["NOVEL"], savedStateHandle["FEEDS"], savedStateHandle["IS_INTEREST"]를 통해 값 소비 확인
data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (1)

7-23: 확장된 CRUD 인터페이스 확인

메서드 시그니처와 반환 타입이 명확하게 정의되어 있어 기존 리뷰에서 지적된 문제점이 해결되었습니다. 별다른 이슈 없이 사용 가능해 보입니다.

Comment on lines 29 to 44
val updatedNovel = NovelEntity(
userNovelId = novelRatingEntity.userNovelId
?: libraryLocalDataSource.selectAllNovelsCount().toLong(),
novelId = novelRatingEntity.novelId!!,
title = novelRatingEntity.novelTitle.orEmpty(),
novelImage = novelRatingEntity.novelImage.orEmpty(),
novelRating = novelRatingEntity.novelRating,
readStatus = novelRatingEntity.readStatus.orEmpty(),
isInterest = isInterested ?: false,
userNovelRating = novelRatingEntity.userNovelRating,
attractivePoints = novelRatingEntity.charmPoints,
startDate = novelRatingEntity.startDate.orEmpty(),
endDate = novelRatingEntity.endDate.orEmpty(),
keywords = novelRatingEntity.userKeywords.map { it.keywordName },
myFeeds = feeds,
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

userNovelId 계산 방식의 충돌 가능성

selectAllNovelsCount() 기반 오프셋으로 userNovelId를 생성하면, 삭제 이력이 있거나 동시 삽입 시 중복될 수 있습니다. 서버에서 내려준 ID를 사용하거나, Room @PrimaryKey(autoGenerate = true)로 위임하는 편이 안전합니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt
between lines 29 and 44, the userNovelId is assigned using
selectAllNovelsCount() which can cause conflicts due to deletions or concurrent
inserts. To fix this, avoid manually generating userNovelId this way; instead,
use the ID provided by the server if available or delegate ID generation to Room
by marking userNovelId as @PrimaryKey(autoGenerate = true) and letting Room
handle it automatically.

Comment on lines +36 to +40
override suspend fun insertNovel(novel: NovelEntity) {
novelDao.apply {
val novelIndex = selectNovelByUserNovelId(novel.userNovelId)?.sortIndex ?: 0
insertNovel(novel.toNovelDatabase(novelIndex))
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

새 Novel 삽입 시 인덱스 계산 로직 확인 필요

존재하지 않는 경우 sortIndex = 0으로 삽입되면 리스트 맨 앞에 중복 인덱스가 생길 위험이 있습니다.
마지막 인덱스를 조회해 lastIndex + 1을 부여하거나 UNIQUE 제약을 두어 충돌을 방지해 주세요.

🤖 Prompt for AI Agents
In
core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt
around lines 36 to 40, the current logic assigns sortIndex as 0 if no existing
novel is found, which risks duplicate indices at the front of the list. Modify
the code to query the highest existing sortIndex and assign the new novel a
sortIndex of lastIndex + 1 to ensure uniqueness. Alternatively, add a UNIQUE
constraint on sortIndex in the database schema to prevent collisions.

Comment on lines +7 to +13
interface LibraryRepository {
val libraryFlow: Flow<PagingData<NovelEntity>>

companion object {
const val PAGE_SIZE = 20
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

인터페이스 전환은 타당. 다만 PAGE_SIZE 상수의 위치 재검토 권장

PAGE_SIZE를 인터페이스 companion에 두면 구현별로 다른 페이지 크기를 쓰기 어렵고, 상수 결합도가 커집니다. PagingConfig 전용 오브젝트/모듈(예: LibraryPagingConfig)로 분리하거나 DI로 주입받아 구현체가 자율적으로 설정 가능하도록 하는 것을 권장합니다. 테스트에서도 주입이 수월해집니다.

아래 스크립트로 PagingSource/RemoteMediator가 싱글톤(객체 선언)으로 구현되지 않았는지 확인해 주세요. 과거 이슈에 따라 싱글톤 사용은 지양되어야 합니다.


🏁 Script executed:

#!/bin/bash
# PagingSource/RemoteMediator가 object로 선언되었는지 점검
rg -n "object\\s+\\w*PagingSource"
rg -n "object\\s+\\w*RemoteMediator"

# PagingSource 래퍼의 싱글톤 키워드/주석 탐색
rg -n "Singleton|@Singleton" -A 2 | rg -n "PagingSource|RemoteMediator|Paging"

Length of output: 152


PAGE_SIZE 상수 위치 재검토 필요

LibraryRepository 인터페이스의 companion object에 정의된 PAGE_SIZE 상수는 구현체별로 다른 페이지 크기 설정을 어렵게 하고, 테스트 시에도 주입이 불편합니다. 아래와 같이 PagingConfig 전용 객체(예: LibraryPagingConfig)로 분리하거나 DI로 주입받아 구현체가 자율적으로 설정할 수 있도록 변경을 권장합니다.

  • 대상 파일 및 위치
    • data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
      • companion object 내 const val PAGE_SIZE = 20
  • 참고: PagingSource/RemoteMediator가 object 선언이나 @Singleton 어노테이션으로 싱글톤화된 부분은 확인되지 않아 해당 이슈는 없습니다.
🤖 Prompt for AI Agents
In data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
around lines 7 to 13, the PAGE_SIZE constant is defined inside the companion
object of the LibraryRepository interface, which limits flexibility for
different implementations and testing. To fix this, remove the PAGE_SIZE
constant from the companion object and instead create a separate PagingConfig
object or class (e.g., LibraryPagingConfig) that holds the page size
configuration. Then, inject this configuration into the implementations of
LibraryRepository so each can set its own page size independently.

Comment on lines +26 to +31
val lastUserNovelId = when (loadType) {
REFRESH -> {
deleteAllNovels()
DEFAULT_LAST_USER_NOVEL_ID
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

REFRESH 시 캐시 선삭제로 인한 빈 화면 위험

네트워크 오류가 발생하면 캐시를 이미 지운 상태로 남아 사용자에게 빈 목록이 노출될 수 있습니다.
성공적으로 새 데이터를 받아온 뒤에 삭제·삽입을 하나의 트랜잭션으로 처리하거나, 실패 시 이전 캐시를 보존하는 방법을 고려해 주세요.

🤖 Prompt for AI Agents
In
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt
around lines 26 to 31, the current code deletes all cached novels before
fetching new data during a REFRESH load, which risks showing an empty screen if
the network request fails. To fix this, modify the logic to perform cache
deletion and insertion within a single transaction after successfully fetching
new data, or implement a rollback mechanism to preserve the previous cache if
the network request fails, ensuring the user does not see an empty list.

Comment on lines +65 to +68
val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { false },
)
Copy link

@coderabbitai coderabbitai bot Aug 9, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

BottomSheet가 절대 열리지 않는 설정입니다

confirmValueChange = { false } 로 두면 모든 상태 전이가 거부되어 show()/hide() 호출도 실패합니다. 필터 시트가 나타나지 않아 UX가 완전히 막히므로 아래처럼 허용 조건을 지정하세요.

- confirmValueChange = { false }
+ confirmValueChange = { it != SheetValue.Hidden } // Hidden 제외 전이 허용
📝 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 bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { false },
)
val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { it != SheetValue.Hidden }, // Hidden 제외 전이 허용
)
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt
around lines 65 to 68, the bottom sheet state is configured with
confirmValueChange always returning false, which blocks all state transitions
and prevents the bottom sheet from opening or closing. Modify confirmValueChange
to allow state changes by returning true or by specifying conditions that permit
opening and closing, so the filter sheet can appear and improve UX.

Copy link
Member Author

Choose a reason for hiding this comment

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

정상동작해

Copy link

Choose a reason for hiding this comment

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

@s9hn 피드백 감사합니다! 제가 잘못 이해했네요. confirmValueChange = { false }로 설정해도 정상 동작한다는 점 기억하겠습니다.


✏️ Learnings added
Learnt from: s9hn
PR: Team-WSS/WSS-Android#705
File: feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt:65-68
Timestamp: 2025-08-09T09:44:15.246Z
Learning: In the LibraryScreen.kt file, the bottom sheet configuration with `rememberModalBottomSheetState(skipPartiallyExpanded = true, confirmValueChange = { false })` works correctly and does not prevent the bottom sheet from opening or closing, despite `confirmValueChange` returning false.

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.


🧠 Learnings used
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:01.000Z
Learning: `bottomSheetState.show()`는 바텀시트 애니메이션이 완료될 때까지 suspend하는 함수이므로, `invokeOnCompletion`에서 실행되는 코드는 바텀시트가 완전히 표시된 후에 실행된다.

Comment on lines +70 to +76
latestEffect.collectAsEventWithLifecycle {
if (uiState.isGrid) {
gridState.scrollToItem(SCROLL_POSITION_TOP)
} else {
listState.scrollToItem(SCROLL_POSITION_TOP)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

suspend 함수를 일반 컨텍스트에서 호출하여 컴파일 오류 발생

LazyListState.scrollToItem() / LazyGridState.scrollToItem() 는 suspend 함수입니다. 현재 collectAsEventWithLifecycle의 람다는 suspend 컨텍스트가 아니므로 컴파일되지 않습니다.

latestEffect.collectAsEventWithLifecycle {
-    if (uiState.isGrid) {
-        gridState.scrollToItem(SCROLL_POSITION_TOP)
-    } else {
-        listState.scrollToItem(SCROLL_POSITION_TOP)
-    }
+    scope.launch {
+        if (uiState.isGrid) {
+            gridState.animateScrollToItem(SCROLL_POSITION_TOP)
+        } else {
+            listState.animateScrollToItem(SCROLL_POSITION_TOP)
+        }
+    }
}

코루틴으로 래핑하고 animateScrollToItem을 사용하면 UX도 부드럽게 개선됩니다.

📝 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
latestEffect.collectAsEventWithLifecycle {
if (uiState.isGrid) {
gridState.scrollToItem(SCROLL_POSITION_TOP)
} else {
listState.scrollToItem(SCROLL_POSITION_TOP)
}
}
latestEffect.collectAsEventWithLifecycle {
scope.launch {
if (uiState.isGrid) {
gridState.animateScrollToItem(SCROLL_POSITION_TOP)
} else {
listState.animateScrollToItem(SCROLL_POSITION_TOP)
}
}
}
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt
around lines 70 to 76, the suspend functions scrollToItem are called inside a
non-suspend lambda passed to collectAsEventWithLifecycle, causing a compile
error. To fix this, wrap the scroll calls inside a coroutine scope launch block
and replace scrollToItem with animateScrollToItem to enable smooth scrolling and
proper suspend function usage.

@s9hn s9hn removed ♻️ [REFACTOR] 기존 코드를 리팩토링합니다. ❌[DO NOT MERGE] 머지를 하면 안됩니다. labels Aug 9, 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: 3

🧹 Nitpick comments (3)
core/common/src/main/AndroidManifest.xml (1)

2-4: 의도적으로 빈 Manifest라면 주석으로 의도를 문서화 권장

빈 Manifest(merge 전용)임을 간단 주석으로 남기면 추후 정리 과정에서의 오해(불필요한 추가/삭제)를 줄일 수 있습니다.

 <?xml version="1.0" encoding="utf-8"?>
-<manifest>
+<manifest>
+  <!-- Intentionally empty: this library doesn't contribute manifest entries; merged at build time -->
 
 </manifest>
app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (2)

134-152: 멀티스레드 환경에서 _cachedFeeds 동시성 문제 가능성

_cachedFeedsMutableList 로, 여러 코루틴에서 fetchFeeds / saveRemovedFeed 등을 동시에 호출하면 경합 조건이 발생할 수 있습니다.
Mutex 나 스레드-세이프 컬렉션으로 보호하거나, UI 전용 캐시라면 단일 스레드(메인 디스패처)에서만 접근하도록 보장해주세요.


215-218: FILE_NAME_FEED 상수가 사용되지 않습니다

현재 모든 호출부에서 "feed.json" 리터럴을 직접 넣고 있어 상수가 dead code 상태입니다.
상수를 실제로 사용하거나 제거해 주세요.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4214e05 and e38d94b.

📒 Files selected for processing (15)
  • app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (4 hunks)
  • app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt (3 hunks)
  • app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt (2 hunks)
  • app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt (3 hunks)
  • app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt (2 hunks)
  • app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/main/feed/FeedFragment.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt (1 hunks)
  • app/src/main/java/com/into/websoso/ui/novelDetail/NovelDetailActivity.kt (2 hunks)
  • core/auth-kakao/src/main/AndroidManifest.xml (1 hunks)
  • core/common/src/main/AndroidManifest.xml (1 hunks)
  • core/datastore/src/main/AndroidManifest.xml (1 hunks)
  • core/network/src/main/AndroidManifest.xml (1 hunks)
  • data/account/src/main/AndroidManifest.xml (1 hunks)
  • feature/signin/src/main/AndroidManifest.xml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/com/into/websoso/ui/main/feed/FeedFragment.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt
  • app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt
  • app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt
  • app/src/main/java/com/into/websoso/ui/novelDetail/NovelDetailActivity.kt
  • app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt
  • app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt
  • app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt
🔇 Additional comments (8)
core/datastore/src/main/AndroidManifest.xml (1)

2-2: 일관성 있는 네임스페이스 정리 좋습니다 — 빌드 설정만 검증해주세요

파일 내 android: 접두 속성이 없으므로 xmlns 생략은 문제 없습니다. 다만 AGP 8+로의 전환 및 각 모듈의 android { namespace } 설정 여부를 확인해 주세요. 향후 android: 속성을 추가하면 네임스페이스 선언이 필요합니다.

위 코멘트에 첨부한 스크립트로 namespace 선언과 android: 사용 여부를 점검해 주세요.

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

2-2: 네임스페이스 생략은 현재 구조에서 안전 — 향후 변경 시 주의

현재 문서에 android: 접두 속성이 없으므로 xmlns 생략은 유효합니다. 다만 manifest에 요소/속성이 추가될 경우(예: android:exported 등) 루트에 xmlns:android 재도입이 필요합니다. 또한 android { namespace } 구성이 완료되었는지 확인 부탁드립니다.

상단 스크립트로 빠르게 검증 가능합니다.

data/account/src/main/AndroidManifest.xml (1)

2-2: 정리된 빈 매니페스트 유지 OK — namespace/AGP 버전 점검 권장

현재 상태로는 문제가 없으나, 차후 이 파일에 android: 접두 속성 추가 시 루트에 xmlns:android 선언이 필요합니다. 모듈의 android { namespace } 선언 및 AGP 설정을 함께 확인해 주세요.

위 공통 스크립트로 검증해 주시면 됩니다.

feature/signin/src/main/AndroidManifest.xml (1)

2-2: xmlns 제거 방향성 동의 — 변경 파급만 최소 확인

파일 자체는 안전합니다. 빌드 설정(android { namespace })이 준비되어 있고, 해당 매니페스트 내부에 android: 접두 사용이 없는지 확인만 부탁드립니다. 향후 속성 추가 시에는 xmlns 재도입이 필요합니다.

공통 검증 스크립트로 확인해 주세요.

core/common/src/main/AndroidManifest.xml (2)

2-2: 모든 모듈 AndroidManifest.xml 점검 완료: android: 접두사를 사용하는 모든 Manifest 파일에 xmlns:android="http://schemas.android.com/apk/res/android" 선언이 존재함을 확인했습니다. 병합 혹은 빌드 실패 가능성은 없습니다.


2-2: 패키지 및 네임스페이스 검증 완료
스크립트 실행 결과 AndroidManifest.xml에 package 속성이 없는 모듈과 Gradle namespace가 누락된 모듈이 발견되지 않았습니다. 추가 조치가 필요 없습니다.

app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (2)

75-83: 로컬 캐시 갱신 시 트랜잭션 보장이 없습니다

insertNovel(updatedNovel) 이 실패하면(예: DB I/O 예외) 원격은 성공하고 로컬만 불일치 상태가 됩니다.
Room 트랜잭션 또는 libraryLocalDataSource 내부에서 예외를 래핑해 동일한 Result 체인으로 묶어주세요.


97-111: 상수 미사용 및 파트 이름 불일치

  1. partName = "feed" / fileName = "feed.json" — 이미 companion object 에 상수가 정의돼 있으므로 재사용하세요.
  2. saveFeed() 에서는 PART_NAME_FEED 를 쓰지만 saveEditedFeed() 는 리터럴 사용으로 불일치가 발생합니다.
    [ suggest_nitpick ]
-    feedRequestDto = multiPartMapper.formatToMultipart<FeedRequestDto>(
+    feedRequestDto = multiPartMapper.formatToMultipart<FeedRequestDto>(
         target = …,
-        partName = "feed",
-        fileName = "feed.json",
+        partName = PART_NAME_FEED,
+        fileName = FILE_NAME_FEED,
     )

Comment on lines +59 to +74
runCatching {
feedApi.postFeed(
feedRequestDto = multiPartMapper.formatToMultipart<FeedRequestDto>(
target = FeedRequestDto(
relevantCategories = relevantCategories,
feedContent = feedContent,
novelId = novelId,
isSpoiler = isSpoiler,
isPublic = isPublic,
),
partName = PART_NAME_FEED,
fileName = "feed.json",
),
partName = PART_NAME_FEED,
fileName = "feed.json",
),
images = images.map { multiPartMapper.formatToMultipart(it) },
)
images = images.map { multiPartMapper.formatToMultipart(it) },
)
}.onSuccess {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

예외가 호출자까지 전파되지 않아 오류 인지가 어렵습니다

runCatching { … }.onSuccess { … } 패턴만 사용하고 onFailure·getOrThrow 등을 호출하지 않아 API 호출 실패 시 아무런 로그·예외도 전달되지 않습니다.
UI 단에서 실패-성공 플로우를 구분하려면 Result 반환(또는 예외 전파)이 필요합니다.

-suspend fun saveFeed(...): Unit {
-    runCatching {
-        feedApi.postFeed(...)
-    }.onSuccess { … }
+/** Result<Unit> 등을 반환하여 호출자가 성공/실패를 알 수 있게 합니다. */
+suspend fun saveFeed(...): Result<Unit> =
+    runCatching {
+        feedApi.postFeed(...)
+        /* 성공 후 로컬 갱신 */
+        ...
+    }

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

🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt around
lines 59 to 74, the runCatching block handles API calls but does not propagate
exceptions or return a Result, causing failures to be silent. Modify the code to
either return the Result from runCatching to the caller or explicitly handle
onFailure by logging or rethrowing the exception, ensuring that errors are
visible and can be handled properly in the UI layer.

Comment on lines +90 to 91
legacyFeed: String,
novelId: Long?,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

동일 콘텐츠 중복 시 모든 항목이 교체됩니다

myFeeds.map { if (it == legacyFeed) editedFeed else it } 는 같은 내용의 피드가 여러 개일 때 전부 수정됩니다.
feedId 를 키로 비교하거나 indexOfFirst 로 특정 위치만 교체하도록 수정이 필요합니다.

Also applies to: 119-124

🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt around
lines 90-91 and also 119-124, the code replaces all entries matching legacyFeed
in myFeeds, causing multiple identical feeds to be replaced. Modify the logic to
identify the specific feed to replace by comparing feedId or using indexOfFirst
to find the exact position, then replace only that single item instead of all
matching ones.

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest>
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

xmlns:android 제거 OK — AGP/namespace 구성만 확인 부탁

빈 매니페스트에서 android: 접두 속성을 사용하지 않는 한 xmlns 생략은 유효합니다. 다만, 모듈의 build.gradle(.kts)에서 android { namespace }가 설정되어 있고, 이 파일 내에 android: 접두 속성이 없는지 확인해 주세요. 향후 android: 속성을 추가할 경우 네임스페이스를 다시 선언해야 합니다.

아래 스크립트로 빠르게 점검할 수 있습니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] 각 모듈의 android { namespace } 선언 확인"
fd -a --extension kts build.gradle | xargs -I{} rg -n "android\s*\{\s*namespace\s*=" "{}" || true

echo
echo "[2] 대상 매니페스트에서 android: 접두 사용 여부 점검 (있다면 xmlns 필요)"
for f in \
  core/auth-kakao/src/main/AndroidManifest.xml \
  core/datastore/src/main/AndroidManifest.xml \
  core/network/src/main/AndroidManifest.xml \
  data/account/src/main/AndroidManifest.xml \
  feature/signin/src/main/AndroidManifest.xml
do
  echo ">> $f"
  rg -n "android:" "$f" || echo "  (android: 접두 사용 없음)"
done

Length of output: 2540


android:namespace 미설정으로 xmlns:android 제거 불가 — build.gradle(.kts)에 namespace 선언 추가 필요

현재 아래 모든 모듈에서 android { namespace = "..." } 설정이 누락되어 있어, Manifest에서 xmlns:android를 제거하면 컴파일 오류가 발생합니다.
각 모듈의 build.gradle 또는 build.gradle.kts에 올바른 namespace를 선언하거나, 선언 전까지는 xmlns:android 속성을 유지해 주세요.

· core/auth-kakao/build.gradle(.kts)
· core/datastore/build.gradle(.kts)
· core/network/build.gradle(.kts)
· data/account/build.gradle(.kts)
· feature/signin/build.gradle(.kts)

🤖 Prompt for AI Agents
In core/auth-kakao/src/main/AndroidManifest.xml at line 2, the xmlns:android
attribute cannot be removed because the android namespace is not set in the
module's build.gradle or build.gradle.kts file. To fix this, add the android
namespace declaration (e.g., android { namespace = "your.namespace.here" }) in
core/auth-kakao/build.gradle or build.gradle.kts. Until this is done, keep the
xmlns:android attribute in the manifest to avoid build errors.

@s9hn s9hn merged commit 8d55650 into develop Aug 9, 2025
1 of 2 checks passed
@m6z1 m6z1 deleted the feat/704 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.

feat: 서재 사일로 - UI/UX 개편

5 participants