-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 서재UI/UX 개편 (11) - 탭 변경 시에도 스크롤 유지되도록 기능 구현 #726
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Walkthrough이 PR은 서재(라이브러리) 기능의 대규모 리팩터링과 확장, Jetpack Compose 기반 UI 도입, 페이징 및 필터링 기능 추가, 데이터/도메인/네트워크 계층의 구조 개선, 네비게이션 시스템 확장, Room 기반 로컬 DB 구축, 관련 레이아웃 및 어댑터 제거, 그리고 빌드 스크립트 및 의존성 추가를 포함합니다. Changes
Changes Table
Sequence Diagram(s)sequenceDiagram
participant User
participant LibraryScreen
participant LibraryViewModel
participant LibraryRepository
participant LibraryRemoteDataSource
participant LibraryLocalDataSource
participant RoomDB
User->>LibraryScreen: 화면 진입/필터/정렬/스크롤 등 UI 인터랙션
LibraryScreen->>LibraryViewModel: 상태 업데이트/필터/정렬/뷰타입 변경
LibraryViewModel->>LibraryRepository: getUserLibrary(필터/정렬/페이징)
LibraryRepository->>NovelRemoteMediator: 페이징 요청
NovelRemoteMediator->>LibraryRemoteDataSource: getUserNovels(필터/정렬/페이징)
LibraryRemoteDataSource->>LibraryRemoteDataSource: API 호출, 결과 파싱
LibraryRemoteDataSource-->>NovelRemoteMediator: UserNovelsEntity 반환
NovelRemoteMediator->>LibraryLocalDataSource: insertNovels()
LibraryLocalDataSource->>RoomDB: DB 저장
LibraryLocalDataSource-->>NovelRemoteMediator: 저장 완료
NovelRemoteMediator-->>LibraryRepository: MediatorResult 반환
LibraryRepository-->>LibraryViewModel: PagingData<NovelEntity> Flow 전달
LibraryViewModel-->>LibraryScreen: UI 상태 및 PagingData 전달
LibraryScreen-->>User: UI 렌더링 및 피드백
Estimated code review effort4 (60–120분)
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
| kapt(libs.room.compiler) | ||
|
|
||
| // 페이징3 | ||
| val paging_version = "3.3.6" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:property-naming reported by reviewdog 🐶
Property name should start with a lowercase letter and use camel case
|
|
||
| // 페이징3 | ||
| val paging_version = "3.3.6" | ||
| implementation("androidx.room:room-paging:2.5.1") // Room 최신 버전에 맞게 버전 확인 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:no-multi-spaces reported by reviewdog 🐶
Unnecessary long whitespace
| val paging_version = "3.3.6" | ||
| implementation("androidx.room:room-paging:2.5.1") // Room 최신 버전에 맞게 버전 확인 | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:no-consecutive-blank-lines reported by reviewdog 🐶
Needless blank line(s)
| dependencies { | ||
| implementation(projects.core.database) | ||
| // 페이징3 | ||
| val paging_version = "3.3.6" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [ktlint] standard:property-naming reported by reviewdog 🐶
Property name should start with a lowercase letter and use camel case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 18
🧹 Nitpick comments (38)
domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt (1)
3-6: 도메인 레이어로의 SortType 이동이 적절합니다.UI 레이어에서 도메인 레이어로 SortType을 이동시킨 것은 좋은 아키텍처 개선입니다. RECENT와 OLD라는 명명도 직관적입니다.
가독성 향상을 위해 각 enum 값에 대한 문서 주석 추가를 고려해보세요:
enum class SortType { + /** 최신순 정렬 */ RECENT, + /** 과거순 정렬 */ OLD, }core/resource/src/main/res/drawable/ic_library_vibe.xml (1)
6-8: 하드코드된 색상 리소스 사용 자제 권장
android:fillColor="#6A5DFD"처럼 직접 색상 값을 하드코딩하면 다크모드·테마 전환 시 유연성이 떨어집니다. 가능하면@color/library_vibe같은 색상 리소스를 정의해서 참조하도록 변경해 주세요.core/resource/src/main/res/drawable/ic_library_finished.xml (1)
7-9: 색상 값 하드코딩 대신 색상 리소스 사용 권장
android:fillColor="#C7C7D0"가 하드코딩되어 있습니다. 테마 대응을 위해@color/library_finished등으로 분리하는 것이 좋습니다.core/resource/src/main/res/drawable/ic_cancel_modal.xml (1)
6-17: Stroke 색상 하드코딩 개선 제안두 path 모두
android:strokeColor="#52515F"가 고정되어 있습니다. 다크모드 및 테마 변경 대응을 위해 색상 리소스로 분리하는 것을 권장합니다.core/resource/src/main/res/drawable/ic_library_sort.xml (1)
10-11: 색상 값 하드코딩 → 테마 속성으로 치환 제안
fillColor와strokeColor에#C7C7D0를 직접 넣으면 다크모드·테마별 색상 변경이 불가능합니다.
?attr/colorOnSurfaceVariant같은 테마 속성을 사용해 재사용성과 접근성을 높여주세요.- android:fillColor="#00000000" - android:strokeColor="#C7C7D0" + android:fillColor="@android:color/transparent" + android:strokeColor="?attr/colorOnSurfaceVariant"data/library/build.gradle.kts (1)
12-20: 페이징 버전 상수 중복 선언 제거이 모듈에서도
paging_version을 별도로 선언하고 있습니다. 중앙화된 버전 관리(버전 카탈로그 또는 Gradle extra)로 일원화하면 버전 변경 시 실수를 줄일 수 있습니다. 빌드 스크립트 단순화 차원에서 고려해주세요.core/resource/src/main/res/drawable/ic_library_null_star.xml (1)
7-8: 스타 아이콘 색상 테마 적용 미지원
fillColor="#C7C7D0"가 고정되어 있어 다크모드에서 시안성이 떨어질 수 있습니다.?attr/colorOnSurfaceVariant와 같이 테마 응용 색상을 사용해 주세요.core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml (1)
8-10: 하드-코드된 색상 → 테마 속성/리소스로 추출 제안
#DDDDE3색상이fillColor/strokeColor에 직접 하드-코딩되어 있습니다. 다크모드나 브랜드 컬러 변경 시 유지보수가 어려우므로 color resource 또는 theme attribute로 교체하는 것이 좋습니다.예시 diff:
- android:fillColor="#DDDDE3" - android:strokeColor="#DDDDE3"/> + android:fillColor="?attr/colorOnSurfaceVariant" + android:strokeColor="?attr/colorOnSurfaceVariant"/>core/resource/src/main/res/drawable/ic_library_character.xml (1)
8-11: 색상 하드-코딩 제거 권장
#6A5DFD보라색이 여러 path에 반복됩니다. 색상 리소스(@color/library_primary)로 추출하면 재사용·일관성 유지가 수월합니다.Also applies to: 13-14
core/resource/src/main/res/drawable/ic_library_grid.xml (1)
8-8: 다수 path의 동일 색상 → 공통 리소스 사용
#C7C7D0가 네 곳 모두 동일하게 쓰입니다. 향후 디자인 시스템 컬러 변경을 고려해 색상 리소스로 분리해 주세요.Also applies to: 11-11, 14-14, 17-17
core/resource/src/main/res/drawable/ic_library_reset.xml (1)
7-9: reset 아이콘 색상도 테마 기반으로
#52515F역시 다크모드 대응을 위해?attr/colorOnSurface등으로 교체하는 것이 바람직합니다.core/resource/src/main/res/drawable/ic_library_half_star.xml (1)
8-8: 별 아이콘 색상 분리 및 상태별 컬러 토큰 사용회색
#C7C7D0과 포인트 컬러#FF675D가 하드-코딩되어 있습니다. 평점 테마 변경 시를 위해@color/rating_inactive,@color/rating_active같은 토큰으로 분리해 주세요.Also applies to: 11-11
core/resource/src/main/res/drawable/ic_library_quote_started.xml (1)
6-8: 하드코딩된 색상 값 대신 색상 리소스를 사용해 다크모드·테마 대응 강화
android:fillColor="#949399"값이 직접 입력되어 있어 다크모드나 테마 변경 시 아이콘 색상이 어둡게 보일 수 있습니다.@color/…혹은?attr/colorOnSurfaceVariant등으로 추출해두면 유지보수성이 좋아집니다.core/resource/src/main/res/drawable/ic_library_reading.xml (1)
6-9: 아이콘 색상 하드코딩 제거 권장
#C7C7D0색상이 고정돼 있습니다. 공통 팔레트 리소스로 분리하면 다크모드 대응이 수월하고 디자인 팀이 색상을 일괄 교체하기도 편리합니다.core/resource/src/main/res/drawable/ic_library_stopped.xml (1)
6-8: 색상값 리소스화 필요
다른 신규 아이콘과 동일하게#C7C7D0이 하드코딩되어 있습니다.@color/neutral60등 공용 색상 리소스로 이동해 주세요.core/resource/src/main/res/drawable/ic_library_quote_finished.xml (1)
6-8: 테마 적용을 위한 색상 추상화
android:fillColor="#949399"→@color/library_icon_quote_finished와 같이 분리하면, 차후 색상 시스템 개편 시 아이콘을 재빌드할 필요가 없습니다.core/resource/src/main/res/drawable/ic_library_material.xml (1)
6-8: 메인 브랜드 색상도 리소스로 관리하세요
브랜드 퍼플#6A5DFD역시 하드코딩돼 있습니다. 다른 화면에서도 동일 색상을 사용하므로@color/brand_primary등으로 분리해 일관성을 유지하는 것이 좋습니다.core/resource/src/main/res/drawable/ic_library_world_view.xml (1)
1-9: 테마 컬러를 사용하도록 변경하면 재사용성이 높아집니다
android:fillColor="#6A5DFD"와 같이 고정 HEX 값을 쓰면 다크모드·테마 변경 시 일관성이 깨질 수 있습니다.?attr/colorOnSurface등 테마 속성으로 교체하거나@color/...리소스로 분리해 두면 유지보수가 용이합니다.- android:fillColor="#6A5DFD"/> + android:fillColor="?attr/colorOnSurface"/>app/src/main/res/layout/fragment_library.xml (1)
2-11: 계층을 한 단계 줄여 불필요한LinearLayout제거 제안
ComposeView 하나만 포함하고 orientation 속성도 의미가 없어 레이아웃 트리 깊이만 늘립니다. ComposeView를 루트로 사용하면 렌더링·측정 비용을 줄일 수 있습니다.<?xml version="1.0" encoding="utf-8"?> <androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/cv_library" android:layout_width="match_parent" android:layout_height="match_parent"/>app/src/main/res/layout/activity_storage.xml (1)
2-8: 빈 ConstraintLayout 대신 불필요한 XML 제거 고려
Activity 내부에서 바로setContent { ... }로 Compose UI를 띄운다면, XML 레이아웃 자체가 필요 없습니다. 파일을 삭제하고 Activity에서setContent를 호출하도록 변경하면 정의·로드 비용을 없앨 수 있습니다.core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)
17-25: 데이터베이스 마이그레이션 전략 고려 필요현재
exportSchema = false로 설정되어 있어 스키마 히스토리가 관리되지 않습니다. 향후 데이터베이스 스키마 변경 시 마이그레이션 전략을 고려해보세요.@Database( entities = [InDatabaseNovelEntity::class], version = 1, - exportSchema = false, + exportSchema = true, )또한 데이터베이스 빌드 시 fallback 전략도 고려해볼 수 있습니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt (1)
37-37: 하드코딩된 값을 테마로 추출 고려UI 간격값들이 하드코딩되어 있습니다. 일관성을 위해 디자인 시스템으로 추출을 고려해보세요.
.padding(top = 80.dp),현재는 문제없지만, 향후 디자인 시스템 확장 시
WebsosoTheme.spacing등을 활용할 수 있습니다.Also applies to: 46-46, 52-52
domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt (1)
14-19: valueOf 메서드명 충돌 가능성커스텀
valueOf메서드가 enum의 기본valueOf메서드를 숨길 수 있습니다. 혼란을 방지하기 위해 다른 이름 사용을 고려해보세요.companion object { - fun valueOf(name: String): AttractivePoints = + fun fromName(name: String): AttractivePoints = AttractivePoints.entries.find { attractivePoints -> attractivePoints.name == name } - ?: throw IllegalArgumentException() + ?: throw IllegalArgumentException("Unknown AttractivePoints: $name") }또한 예외 메시지에 구체적인 정보를 포함하면 디버깅에 도움이 됩니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1)
27-34: 로딩 인디케이터 추가를 고려해보세요.LibraryGridList와 달리 페이징 로딩 상태에 대한 인디케이터가 없습니다. 일관된 사용자 경험을 위해 추가를 고려해보세요.
items(novels.itemCount) { index -> novels[index]?.let { novel -> LibraryListItem( item = novel, onClick = { onItemClick(novel) }, ) } } + + if (novels.loadState.append is LoadState.Loading) { + item { + CircularProgressIndicator(modifier = Modifier.padding(16.dp)) + } + }feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1)
25-29: 리소스 타입 어노테이션 사용을 검토해주세요.
@SuppressLint("ResourceType")과@IntegerRes어노테이션이 함께 사용되고 있습니다.@IntegerRes는 정수 리소스 ID를 나타내지만, 실제로는 drawable 리소스 ID를 받고 있는 것으로 보입니다.올바른 어노테이션 사용을 위해 다음과 같이 수정하는 것을 권장합니다:
- @IntegerRes icon: Int, + @DrawableRes icon: Int,그리고
@SuppressLint("ResourceType")어노테이션을 제거하세요.data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryRemoteDataSource.kt (1)
6-17: 매개변수 객체 패턴 사용을 고려해보세요.메서드가 10개의 매개변수를 받고 있어 가독성과 유지보수성이 떨어질 수 있습니다. 필터링과 페이징 관련 매개변수들을 하나의 데이터 클래스로 그룹화하는 것을 권장합니다.
예를 들어 다음과 같이 리팩토링할 수 있습니다:
data class UserNovelsRequest( val userId: Long, val lastUserNovelId: Long, val size: Int, val sortCriteria: String, val isInterest: Boolean? = null, val readStatuses: List<String>? = null, val attractivePoints: List<String>? = null, val novelRating: Float? = null, val query: String? = null, val updatedSince: String? = null, ) suspend fun getUserNovels(request: UserNovelsRequest): UserNovelsEntitycore/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt (1)
47-48: 날짜 필드의 null 처리 방식을 검토해주세요.
startDate.orEmpty()와endDate.orEmpty()로 null을 빈 문자열로 변환하고 있습니다. 도메인 모델에서 날짜가 없는 상태를 빈 문자열로 표현하는 것이 적절한지 확인해보세요.다음 중 하나를 고려해보세요:
- 도메인 모델도 nullable String으로 유지하기
- 특별한 기본값 (예: "미정", "TBD") 사용하기
- 별도의 sealed class나 enum으로 날짜 상태 표현하기
data/library/src/main/java/com/into/websoso/data/library/NovelRemoteMediator.kt (1)
45-49: 디버그 로그 태그를 의미있는 이름으로 변경 필요"123123"과 "1231234" 같은 임시 태그 대신 적절한 로그 태그를 사용해야 합니다.
- Log.d("123123", response.toString()) + Log.d("NovelRemoteMediator", "Loaded novels: $response")- Log.d("1231234", e.toString()) + Log.d("NovelRemoteMediator", "Error loading novels", e)domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt (1)
15-36: 매개변수가 많아 가독성이 떨어집니다.9개의 매개변수를 가진 함수는 유지보수가 어려울 수 있습니다. 필터링 관련 매개변수들을 하나의 데이터 클래스로 묶는 것을 고려해보세요.
예시 리팩터링:
+data class LibraryFilter( + val isInterest: Boolean? = null, + val readStatuses: List<String>? = null, + val attractivePoints: List<String>? = null, + val novelRating: Float? = null, + val query: String? = null +) operator fun invoke( userId: Long, lastUserNovelId: Long = INITIAL_LAST_USER_NOVEL_ID, size: Int = 60, sortCriteria: SortType = SortType.RECENT, - isInterest: Boolean? = null, - readStatuses: List<String>? = null, - attractivePoints: List<String>? = null, - novelRating: Float? = null, - query: String? = null, + filter: LibraryFilter = LibraryFilter() ): Flow<PagingData<NovelEntity>>feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (2)
25-31: 메서드명이 실제 동작과 일치하지 않습니다.
updateMyLibraryFilter는 필터를 업데이트한다기보다는 전체 상태를 교체합니다.replaceLibraryFilter또는setLibraryFilterState같은 이름이 더 적절합니다.-fun updateMyLibraryFilter(libraryFilterUiState: LibraryFilterUiState) { +fun replaceLibraryFilterState(libraryFilterUiState: LibraryFilterUiState) {
67-79: 코루틴 에러 핸들링 추가를 고려해보세요.
viewModelScope.launch블록에서 repository 호출 시 발생할 수 있는 예외에 대한 처리가 없습니다. 사용자 경험을 위해 에러 핸들링을 추가하는 것이 좋습니다.fun searchFilteredNovels() { viewModelScope.launch { - libraryRepository.updateMyLibraryFilter( - readStatuses = libraryFilterUiState.value.readStatuses - .filterValues { it } - .map { it.key.name }, - attractivePoints = libraryFilterUiState.value.attractivePoints - .filterValues { it } - .map { it.key.name }, - novelRating = libraryFilterUiState.value.novelRating, - ) + try { + libraryRepository.updateMyLibraryFilter( + readStatuses = libraryFilterUiState.value.readStatuses + .filterValues { it } + .map { it.key.name }, + attractivePoints = libraryFilterUiState.value.attractivePoints + .filterValues { it } + .map { it.key.name }, + novelRating = libraryFilterUiState.value.novelRating, + ) + } catch (e: Exception) { + // 에러 로깅 또는 사용자에게 알림 + } } }core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1)
36-43: JSON 파싱 오류 처리 추가 고려JSON 디코딩 시 오류가 발생할 경우를 대비한 예외 처리를 추가하는 것이 좋을 것 같습니다.
override val myLibraryFilterFlow: Flow<LibraryFilterParams?> get() = myLibraryFilterDataStore.data .map { prefs -> prefs[LIBRARY_FILTER_PARAMS_KEY]?.let { jsonString -> withContext(dispatcher) { - Json.decodeFromString<LibraryFilterPreferences>(jsonString).toData() + try { + Json.decodeFromString<LibraryFilterPreferences>(jsonString).toData() + } catch (e: Exception) { + // 로그 기록 후 null 반환 또는 기본값 반환 + null + } } } }.distinctUntilChanged()feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt (1)
1-1: 파일명 오타 수정 필요파일명에 오타가 있습니다:
LibrayFilterTopBar.kt→LibraryFilterTopBar.ktfeature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
364-374: LazyRow items에 key 추가로 성능 개선
MyFeedCardGroup처럼NovelKeywordChipGroup의 items에도 key를 추가하여 리컴포지션 성능을 개선하세요.@Composable private fun NovelKeywordChipGroup(novelKeyword: List<String>) { LazyRow( contentPadding = PaddingValues(end = 20.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - items(novelKeyword) { + items(novelKeyword, key = { it }) { NovelKeywordChip(it) } } }data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt (1)
76-83: TODO 주석 정리 필요구현 계획을 설명하는 한글 주석이 남아있습니다. 이미 구현된 내용이라면 제거하거나, 아직 구현이 필요하다면 영문으로 작성하거나 이슈로 관리하세요.
-// 1. 클릭 리스너로 뷰모델 상태 업데이트 -// 2. 확인 누르면 datastore 업데이트 -// 3. 객체 직렬화 및 저장 -// 4. datastore를 읽고, 캐싱(가능하면), 널이 아니라면, 해당 쿼리문으로 룸 업데이트 -// 5. 룸에서 데이터 읽고 UI -// 6. 이미지 캐싱 -// 7. 다른 뷰에서 룸 동기화 -// 8. 증분 APIfeature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (3)
161-197: 읽기 상태 필터 섹션 개선 제안현재 하드코딩된 패딩 값들을 디자인 시스템의 표준 spacing 값으로 통일하는 것을 고려해보세요.
LibraryFilterBottomSheetClickableItem( icon = ic_library_reading, iconTitle = "보는 중", iconSize = 24.dp, - horizontalPadding = 36.dp, + horizontalPadding = WebsosoTheme.spacing.large, // 또는 표준화된 값 onClick = { onReadStatusClick(WATCHING) }, isSelected = readStatues[WATCHING] ?: false, )
199-251: 매력포인트 필터 섹션의 일관성 확인5개의 매력포인트 아이템이 한 줄에 배치되어 있어 작은 화면에서 UI가 압축될 수 있습니다. 반응형 레이아웃이나 그리드 구조를 고려해보세요.
또한 읽기 상태 섹션과 동일하게 패딩 값 표준화를 권장합니다.
// 반응형 레이아웃 고려 예시 LazyHorizontalGrid( rows = GridCells.Fixed(2), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { // 아이템들... }
366-398: NovelRatingItem의 조건부 스타일링 개선 제안
then()modifier를 사용한 조건부 스타일링보다는 더 가독성 좋은 패턴을 고려해보세요.Box( modifier = modifier .background( color = backgroundColor, shape = RoundedCornerShape(size = 8.dp), ) - .then( - if (isSelected) { - Modifier.border( - width = 1.dp, - color = Primary100, - shape = RoundedCornerShape(size = 8.dp), - ) - } else { - Modifier - }, - ) + .let { modifier -> + if (isSelected) { + modifier.border( + width = 1.dp, + color = Primary100, + shape = RoundedCornerShape(size = 8.dp), + ) + } else modifier + } .padding(vertical = 14.dp, horizontal = 24.dp),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (107)
app/build.gradle.kts(2 hunks)app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt(3 hunks)app/src/main/java/com/into/websoso/data/mapper/UserMapper.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/api/UserApi.kt(0 hunks)app/src/main/java/com/into/websoso/data/remote/response/UserStorageResponseDto.kt(0 hunks)app/src/main/java/com/into/websoso/data/repository/UserRepository.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/MainActivity.kt(5 hunks)app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt(1 hunks)app/src/main/java/com/into/websoso/ui/main/library/LibraryViewModel.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemAdapter.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemViewHolder.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerAdapter.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerViewHolder.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/library/model/LibraryUiState.kt(0 hunks)app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt(1 hunks)app/src/main/java/com/into/websoso/ui/mapper/NovelMapper.kt(0 hunks)app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt(3 hunks)app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/userStorage/SortMenuHandler.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/SortPopupHandler.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt(1 hunks)app/src/main/java/com/into/websoso/ui/userStorage/UserStorageViewModel.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemAdapter.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemViewHolder.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerAdapter.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerViewHolder.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/model/SortType.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/model/StatusTabs.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageModel.kt(0 hunks)app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageUiState.kt(0 hunks)app/src/main/res/layout/activity_storage.xml(1 hunks)app/src/main/res/layout/fragment_library.xml(1 hunks)app/src/main/res/layout/item_storage.xml(0 hunks)app/src/main/res/layout/item_storage_novel.xml(0 hunks)app/src/main/res/layout/menu_storage_popup.xml(0 hunks)core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt(2 hunks)core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt(1 hunks)core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt(1 hunks)core/database/.gitignore(1 hunks)core/database/build.gradle.kts(1 hunks)core/database/proguard-rules.pro(1 hunks)core/database/src/main/AndroidManifest.xml(1 hunks)core/database/src/main/java/com/into/websoso/core/database/Converters.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt(1 hunks)core/datastore/build.gradle.kts(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/library/DefaultLibraryDataSource.kt(2 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/library/LibraryApi.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt(2 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserLibraryResponseDto.kt(0 hunks)core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserNovelsResponseDto.kt(1 hunks)core/resource/src/main/res/drawable/ic_cancel_modal.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_character.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_finished.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_grid.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_half_star.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_interesting.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_list.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_material.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_null_star.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_quote_finished.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_quote_started.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_reading.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_relationship.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_reset.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_sort.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_stopped.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_vibe.xml(1 hunks)core/resource/src/main/res/drawable/ic_library_world_view.xml(1 hunks)data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt(2 hunks)data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt(1 hunks)data/library/build.gradle.kts(1 hunks)data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/NovelRemoteMediator.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryRemoteDataSource.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt(1 hunks)domain/library/.gitignore(1 hunks)domain/library/build.gradle.kts(1 hunks)domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt(1 hunks)domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt(1 hunks)domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt(1 hunks)domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt(1 hunks)feature/library/build.gradle.kts(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryTopBar.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt(1 hunks)
⛔ Files not processed due to max files limit (13)
- feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt
- feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt
- feature/library/src/main/res/values/string.xml
- feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt
- feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt
- gradle/libs.versions.toml
- settings.gradle.kts
💤 Files with no reviewable changes (26)
- app/src/main/java/com/into/websoso/ui/mapper/NovelMapper.kt
- app/src/main/res/layout/item_storage.xml
- app/src/main/java/com/into/websoso/ui/userStorage/model/SortType.kt
- app/src/main/java/com/into/websoso/ui/userStorage/model/StatusTabs.kt
- app/src/main/java/com/into/websoso/data/repository/UserRepository.kt
- app/src/main/java/com/into/websoso/data/remote/api/UserApi.kt
- app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemViewHolder.kt
- app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemViewHolder.kt
- app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerViewHolder.kt
- app/src/main/java/com/into/websoso/data/mapper/UserMapper.kt
- app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryItemAdapter.kt
- app/src/main/java/com/into/websoso/ui/userStorage/SortPopupHandler.kt
- app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageUiState.kt
- app/src/main/res/layout/menu_storage_popup.xml
- app/src/main/java/com/into/websoso/ui/userStorage/SortMenuHandler.kt
- app/src/main/java/com/into/websoso/ui/main/library/model/LibraryUiState.kt
- app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerAdapter.kt
- app/src/main/res/layout/item_storage_novel.xml
- app/src/main/java/com/into/websoso/data/remote/response/UserStorageResponseDto.kt
- app/src/main/java/com/into/websoso/ui/userStorage/model/UserStorageModel.kt
- app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageViewPagerAdapter.kt
- app/src/main/java/com/into/websoso/ui/main/library/adapter/LibraryViewPagerViewHolder.kt
- core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserLibraryResponseDto.kt
- app/src/main/java/com/into/websoso/ui/userStorage/adapter/UserStorageItemAdapter.kt
- app/src/main/java/com/into/websoso/ui/userStorage/UserStorageViewModel.kt
- app/src/main/java/com/into/websoso/ui/main/library/LibraryViewModel.kt
🧰 Additional context used
🧠 Learnings (2)
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)
Learnt from: s9hn
PR: #676
File: core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt:27-34
Timestamp: 2025-05-14T07:41:05.706Z
Learning: Accessing DataStore via runBlocking in OkHttp interceptors can block network threads and cause performance issues. Consider using in-memory caching with background updates instead to achieve thread safety without blocking OkHttp threads.
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1)
Learnt from: s9hn
PR: #676
File: core/network/src/main/java/com/into/websoso/core/network/interceptor/AuthorizationInterceptor.kt:27-34
Timestamp: 2025-05-14T07:41:05.706Z
Learning: Accessing DataStore via runBlocking in OkHttp interceptors can block network threads and cause performance issues. Consider using in-memory caching with background updates instead to achieve thread safety without blocking OkHttp threads.
🧬 Code Graph Analysis (13)
data/library/build.gradle.kts (1)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
domain/library/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
core/datastore/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
feature/library/build.gradle.kts (1)
build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (1)
implementation(70-76)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1)
NovelGridListItem(49-89)
app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (2)
LibraryScreen(42-105)LibraryScreen(107-187)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
LibraryListItem(70-113)
app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (2)
LibraryScreen(42-105)LibraryScreen(107-187)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1)
ReadStatusBadge(130-145)
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt (3)
app/src/main/java/com/into/websoso/ui/detailExploreResult/DetailExploreResultKeywordFragment.kt (1)
setupBackButtonListener(280-293)app/src/main/java/com/into/websoso/ui/detailExplore/keyword/DetailExploreKeywordFragment.kt (1)
setupBackButtonListener(303-316)app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingKeywordBottomSheetDialog.kt (1)
setupBackButtonListener(278-298)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (2)
feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (1)
updateMyLibraryFilter(25-31)data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt (1)
updateMyLibraryFilter(54-74)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
ReadStatusBadge(157-180)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (1)
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1)
LibraryFilterBottomSheetClickableItem(25-54)
🪛 detekt (1.23.8)
domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt
[warning] 12-12: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt
[warning] 231-231: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
[warning] 235-235: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
🔇 Additional comments (76)
app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1)
60-60: 메서드 이름 변경 승인
renewTokens()에서createTokens()로의 메서드 이름 변경이 의미적으로 더 명확합니다. 기능적 동작은 변경되지 않으며 전체 토큰 관리 리팩터링의 일부로 보입니다.core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (1)
71-71: 메서드 이름 변경 승인 및 성능 고려사항
renewTokens()에서createTokens()로의 변경은 일관성을 위해 적절합니다.다만, 이전 리뷰에서 OkHttp 인터셉터/인증자에서 DataStore 접근 시 네트워크 스레드 블로킹 이슈가 언급된 바 있습니다.
accountRepository.get().createTokens()호출이 내부적으로 DataStore를 사용한다면 성능 영향을 검토해볼 필요가 있습니다.core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (1)
23-52: 메서드 명명 규칙 개선 승인모든 메서드 이름 변경이 CRUD 명명 규칙을 따라 의미가 더욱 명확해졌습니다:
accessToken()→selectAccessToken()refreshToken()→selectRefreshToken()saveAccessToken()→updateAccessToken()saveRefreshToken()→updateRefreshToken()clearTokens()→deleteTokens()기능적 변경 없이 코드 가독성과 의도 전달이 향상되었습니다.
data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)
4-12: 인터페이스 메서드 명명 규칙 일관성 승인인터페이스 메서드들이 구현체와 일치하도록 CRUD 명명 규칙으로 업데이트되었습니다. 계약(contract)과 구현 간의 일관성이 유지되어 좋습니다.
data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (5)
21-23: 토큰 조회 메서드 업데이트 승인
selectAccessToken()과selectRefreshToken()호출로 변경되어 데이터 소스 레이어와 일관성을 유지합니다.
25-38: 계정 생성 메서드 리팩터링 승인
saveTokens에서createAccount로 이름 변경과updateAccessToken(),updateRefreshToken()호출 사용이 의미상 더 명확합니다. 계정 생성의 의도가 잘 드러납니다.
40-44: 계정 삭제 메서드 업데이트 승인
deleteTokens()호출로 변경되어 명명 규칙 일관성이 유지됩니다.
46-52: 토큰 생성 메서드 리팩터링 승인
renewTokens에서createTokens로의 이름 변경과update*메서드 사용이 적절합니다. 토큰 갱신보다는 생성의 의미가 더 명확하게 전달됩니다.
54-63: 토큰 삭제 메서드 위치 이동 및 업데이트 승인메서드가 클래스 끝으로 이동되었고
deleteTokens()호출로 업데이트되었습니다. 기능적으로는 동일하며 명명 규칙 일관성이 유지됩니다.core/database/.gitignore (1)
1-1: 표준적인 .gitignore 구성이 적절합니다.Android 모듈의 빌드 산출물을 올바르게 제외하고 있습니다.
core/common/src/main/java/com/into/websoso/core/common/dispatchers/DispatchersModule.kt (2)
18-20: DEFAULT 디스패처 추가가 적절합니다.CPU 집약적인 작업을 위한 DEFAULT 디스패처를 추가하는 것은 좋은 아키텍처 결정입니다. 기존 IO 디스패처와 일관된 패턴으로 구현되었습니다.
31-31: enum 확장이 적절합니다.WebsosoDispatchers enum에 DEFAULT 추가가 깔끔하게 처리되었습니다.
domain/library/.gitignore (1)
1-1: 표준적인 .gitignore 구성이 적절합니다.새로운 domain/library 모듈에 대한 적절한 빌드 산출물 제외 설정입니다.
core/database/src/main/AndroidManifest.xml (1)
1-4: 라이브러리 모듈에 적합한 최소 manifest 구성입니다.core/database 모듈에 필요한 기본적인 AndroidManifest.xml 구성이 올바릅니다.
core/datastore/build.gradle.kts (1)
13-19: 레이어드 아키텍처 의존성 방향 확인 필요
core.datastore모듈이projects.data.library에 의존하면서 core → data 방향 결합이 생겼습니다. 일반적으로core계층은 하위(공통) 레이어여야 하므로, 데이터 계층을 참조하면 순환 의존의 위험이 있습니다. 의존성을 domain 또는 별도 contract 모듈로 분리하는지 확인해 주세요.core/database/build.gradle.kts (1)
18-25: Paging 3 버전 관리 중복 및 Room-Paging 버전 불일치 위험
val paging_version = "3.3.6"선언이 여러 모듈에 중복됩니다.libs.versions.toml또는ext에 중앙 관리해 유지보수를 단일화하는 편이 좋습니다.androidx.room:room-paging:2.5.1은 상단libs.room.*가 사용 중인 Room 버전과 맞지 않을 가능성이 있습니다. 버전 불일치 시 컴파일 경고·런타임 오류가 발생할 수 있으니 동일 버전으로 맞춰주세요.- val paging_version = "3.3.6" - implementation("androidx.room:room-paging:2.5.1") + val paging_version = libs.versions.androidxPaging.get() + implementation("androidx.room:room-paging:${libs.versions.room.get()}")core/database/proguard-rules.pro (1)
1-21: 파일이 build.gradle에 포함돼 있는지 확인 필요
새 규칙 파일이 추가됐지만,proguardFiles("core/database/proguard-rules.pro")가 해당 모듈의 build script 에 선언되지 않으면 적용되지 않습니다. 누락 여부를 점검해 주세요.feature/library/build.gradle.kts (1)
11-18: 의존성 버전 충돌 가능성 확인 요청
libs.paging.compose추가로 paging-runtime 과 compose-bom 간 버전 불일치가 발생할 수 있습니다. Gradle > Dependency Graph 로 충돌 여부를 한번 확인해 주세요. 문제 없으면 그대로 진행하셔도 됩니다.core/resource/src/main/res/drawable/ic_library_relationship.xml (1)
1-13: 벡터 드로어블 구현이 올바르게 되어 있습니다.XML 구조, 네임스페이스, 색상 사용(#6A5DFD), 그리고 경로 데이터가 모두 적절하게 구성되어 있습니다. evenOdd 채우기 타입 사용도 복잡한 경로 형상에 적합합니다.
core/resource/src/main/res/drawable/ic_library_interesting.xml (1)
1-12: 하트 모양 아이콘이 잘 구현되었습니다.레이어드 접근 방식(채우기 + 윤곽선)으로 시각적 깊이를 만들고, 일관된 색상 체계를 사용하여 디자인 시스템과 잘 어울립니다.
app/build.gradle.kts (3)
84-84: 데이터베이스 모듈 의존성 추가가 적절합니다.Room 데이터베이스 통합을 위한
core.database모듈 추가가 새로운 라이브러리 아키텍처와 잘 맞습니다.
87-90: 새로운 라이브러리 피처 모듈 구조가 적절합니다.
data.library와feature.library모듈의 추가와 의존성 순서 조정이 새로운 아키텍처를 잘 반영하고 있습니다.
137-137: 페이징 라이브러리 버전이 최신입니다.Paging Runtime 3.3.2 버전 사용으로 새로운 페이징 기능을 적절히 지원합니다.
core/resource/src/main/res/drawable/ic_library_list.xml (1)
1-24: 리스트 뷰 아이콘이 잘 설계되었습니다.2x3 그리드 레이아웃으로 리스트 뷰를 효과적으로 표현하고, 두 가지 회색 톤(#C7C7D0, #949399)을 사용하여 시각적 계층구조를 만들었습니다. 모서리가 둥근 사각형들의 일관된 반지름(1.009) 사용도 적절합니다.
core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt (2)
16-26: 잘 구현된 DAO 인터페이스입니다.Paging 지원, 적절한 충돌 전략 사용, 캐시 무효화를 위한 clear 기능이 모두 잘 구현되어 있습니다.
28-34: Hilt 모듈이 올바르게 구성되었습니다.SingletonComponent 스코프에서 DAO를 적절히 제공하고 있으며, 의존성 주입 설정이 정확합니다.
app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt (2)
198-210: 네비게이션 로직이 잘 단순화되었습니다.두 클릭 리스너 모두 매개변수 없는
navigateToLibraryFragment()메서드를 호출하도록 변경되어 코드가 더 간결해졌습니다. SingleEventHandler를 통한 중복 클릭 방지도 적절히 유지되고 있습니다.
212-220: 매개변수 제거로 네비게이션이 더 단순해졌습니다.읽기 상태 매개변수를 제거한 것은 타겟 화면에서 상태를 내부적으로 관리하는 새로운 아키텍처와 일치하는 좋은 변경사항입니다.
core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt (2)
20-24: 새로운 DataStore 인스턴스가 일관된 패턴으로 추가되었습니다.기존 account DataStore와 동일한 패턴을 따르고 있으며, 명명 규칙도 일관성 있게 적용되었습니다.
32-37: 의존성 주입 설정이 올바르게 구성되었습니다.
@Provides,@Singleton,@MyLibraryFilterDataStore어노테이션이 적절히 적용되어 있으며, 메서드 시그니처가 기존 패턴과 일치합니다.core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt (1)
5-12: 직렬화 가능한 필터 설정 모델이 잘 설계되었습니다.
@Serializable어노테이션을 통한 JSON 직렬화 지원, 적절한 내부 가시성, 그리고 옵셔널 필터링을 위한 nullable 필드 설계가 모두 잘 구현되어 있습니다. DataStore 지속성 모델의 역할에 적합합니다.core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)
32-41: 데이터베이스 구성이 적절합니다Room 데이터베이스 설정과 Hilt 모듈 구성이 올바르게 구현되었습니다. 싱글톤 스코프와 애플리케이션 컨텍스트 사용이 적절합니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt (2)
32-58: 컴포넌트 구성이 우수합니다빈 상태 UI 컴포넌트가 잘 구성되었습니다. Material3와 커스텀 테마를 적절히 활용하고 있으며, 리소스 참조도 올바르게 처리되었습니다.
60-85: 버튼 컴포넌트 구현이 적절합니다private 컴포넌트로 분리되어 재사용성과 가독성이 좋습니다. 버튼 스타일링과 패딩 설정도 적절합니다.
domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt (1)
3-12: enum 클래스 구조가 적절합니다한글 라벨과 영문 키를 가진 enum 구조가 잘 설계되었습니다. entries 프로퍼티 사용도 현대적인 Kotlin 접근법입니다.
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt (1)
6-23: 매핑 함수 구현이 적절합니다양방향 매핑이 일관되게 구현되었으며, 모든 프로퍼티가 올바르게 복사되고 있습니다. 단순한 프로퍼티 매핑에 적합한 구현입니다.
core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/UserNovelsResponseDto.kt (1)
9-25: DTO 구조와 매핑 로직이 잘 구현되었습니다.네트워크 응답을 도메인 엔티티로 변환하는 표준적인 패턴을 잘 따르고 있으며, 직렬화 어노테이션과 프로퍼티 매핑이 적절합니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt (1)
19-49: 스크롤 상태 관리가 잘 구현된 그리드 리스트입니다.LazyGridState를 매개변수로 받아 탭 전환 시 스크롤 위치를 유지할 수 있도록 구현되었으며, 페이징과 로딩 상태 처리도 적절합니다. 3열 고정 그리드 레이아웃과 간격 설정도 UI/UX에 적합합니다.
data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt (1)
3-13: 필터링 매개변수 모델이 잘 설계되었습니다.페이징과 다양한 필터링 옵션을 지원하는 데이터 클래스가 적절히 구조화되어 있으며, nullable 필드를 통해 선택적 필터링을 잘 지원합니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt (1)
14-36: 스크롤 상태 관리가 잘 구현된 리스트입니다.LazyListState를 매개변수로 받아 탭 전환 시 스크롤 위치 유지 기능을 지원하며, 페이징 아이템과의 통합도 적절합니다.
core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt (1)
7-30: 날짜 범위 포맷팅 유틸리티가 잘 구현되었습니다.null 안전성과 예외 처리가 적절하며, Java 8 시간 API를 올바르게 사용하고 있습니다. 단일 날짜와 날짜 범위 모두를 지원하는 로직도 명확합니다.
data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt (1)
6-12: 인터페이스 설계가 우수합니다.반응형 프로그래밍을 위한 Flow 사용, 비동기 작업을 위한 suspend 함수, 그리고 명확한 책임 분리가 잘 되어 있습니다. 깔끔하고 집중된 인터페이스 설계입니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheetClickableItem.kt (1)
35-54: 컴포저블 구현이 잘 되어 있습니다.UI 구조가 명확하고, 선택 상태에 따른 색상 변화, 적절한 간격과 정렬이 잘 구현되어 있습니다. 재사용 가능한 컴포넌트로 잘 설계되었습니다.
core/network/src/main/java/com/into/websoso/core/network/datasource/library/model/response/NovelResponseDto.kt (1)
36-51: 데이터 매핑 함수가 잘 구현되어 있습니다.DTO에서 도메인 모델로의 변환 로직이 명확하고 모든 필드가 적절히 매핑되어 있습니다. 확장된 데이터 구조도 새로운 필터링 기능을 잘 지원합니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt (3)
43-48: 상수 정의가 적절합니다.그리드 레이아웃을 위한 상수들이 명확하게 정의되어 있고, 이미지 비율 상수도 적절합니다. 매직 넘버 사용을 피한 좋은 구현입니다.
148-162: 그리드 아이템 크기 계산 로직이 잘 구현되어 있습니다.화면 크기에 따라 동적으로 아이템 크기를 계산하는 로직이 정확하고,
remember를 사용해 불필요한 재계산을 방지하고 있습니다. 밀도 변환도 올바르게 처리되었습니다.
165-181: 별점 표시 컴포넌트가 잘 구현되어 있습니다.별점 타입에 따른 아이콘 선택과 적절한 간격 설정이 잘 되어 있습니다. 재사용 가능한 컴포넌트로 잘 설계되었습니다.
core/network/src/main/java/com/into/websoso/core/network/datasource/library/LibraryApi.kt (1)
16-27: API 인터페이스 설계가 잘 되어 있습니다페이징과 필터링을 위한 포괄적인 매개변수들이 잘 정의되어 있고, Retrofit 어노테이션도 적절히 사용되었습니다.
core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt (2)
6-21: Room 엔티티 구조가 잘 설계되었습니다소설 데이터를 저장하기 위한 포괄적인 필드들이 잘 정의되어 있고,
@Entity와@PrimaryKey어노테이션도 적절히 사용되었습니다.
16-20: TypeConverter 설정 확인 완료
Converters.kt에List<String>↔String변환 로직이 구현되어 있으며,WebsosoDatabase에@TypeConverters(Converters::class)로 등록되어 있어attractivePoints,keywords,myFeeds필드는 정상 저장됩니다. 문제 없습니다.app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt (1)
49-65: 새로운 네비게이션 메서드들이 잘 구현되었습니다일관된 패턴을 따르며 필요한 매개변수들을 적절히 처리하고 있습니다. 특히
navigateToNovelDetailActivity에서 novelId를 받아 처리하는 부분이 잘 설계되었습니다.data/library/src/main/java/com/into/websoso/data/library/NovelRemoteMediator.kt (1)
16-52: 페이징 로직이 올바르게 구현되었습니다
RemoteMediator의 다양한LoadType을 적절히 처리하고 있으며, 로컬 데이터베이스와의 동기화 로직도 잘 구현되어 있습니다. 예외 처리도 적절합니다.data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (3)
15-21: 인터페이스 설계가 깔끔하고 책임이 명확합니다.로컬 데이터 소스의 핵심 기능인 삽입, 조회, 삭제가 적절히 정의되었고, PagingSource 반환으로 페이징 통합도 잘 구현되었습니다.
23-37: 구현체가 올바르게 작성되었습니다.의존성 주입과 DAO 위임 패턴이 적절히 사용되었고, 엔티티 매핑도 정확합니다. NovelEntity::toDatabase 확장 함수 사용으로 코드가 간결합니다.
39-45: Dagger Hilt 모듈 설정이 적절합니다.싱글톤 스코프로 바인딩되어 앱 전체에서 일관된 데이터 소스 인스턴스를 사용할 수 있습니다.
domain/library/src/main/java/com/into/websoso/domain/library/GetUserNovelUseCase.kt (1)
16-16: 하드코딩된 사용자 ID를 검토해주세요.기본값으로
userId: Long = 184가 하드코딩되어 있습니다. 프로덕션 코드에서는 현재 로그인된 사용자 ID를 동적으로 가져오거나, 필수 매개변수로 만드는 것이 적절합니다.다음 스크립트로 이 use case의 사용 패턴을 확인해보겠습니다:
core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt (2)
11-14: 기존 메서드 확장이 적절합니다.
fragmentType매개변수를 선택적으로 추가하여 하위 호환성을 유지하면서 기능을 확장했습니다.
18-26: 새로운 네비게이션 메서드들이 일관된 패턴을 따릅니다.모든 메서드가 동일한
startActivity: (Intent) -> Unit패턴을 사용하여 일관성을 유지하고,navigateToNovelDetailActivity는 필요한novelId매개변수를 적절히 포함했습니다.feature/library/src/main/java/com/into/websoso/feature/library/LibraryFilterViewModel.kt (1)
53-59: 별점 토글 로직이 명확합니다.동일한 별점을 다시 선택하면 0f로 리셋되는 토글 동작이 직관적이고 잘 구현되었습니다.
app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt (2)
19-19: NavigatorProvider 의존성 주입이 적절합니다.중앙화된 네비게이션 관리를 위한 깔끔한 의존성 주입 구현입니다.
Also applies to: 35-36
210-226: 네비게이션 로직이 단순화되어 유지보수성이 향상되었습니다.이전의 복잡한 매개변수 전달 방식에서 NavigatorProvider로 위임하는 방식으로 변경되어 결합도가 낮아지고 코드가 더 깔끔해졌습니다.
data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt (1)
1-59: 데이터 모델 구현이 깔끔합니다!데이터 클래스와 변환 함수가 잘 구성되어 있습니다. 모든 필드가 올바르게 매핑되어 있고, 데이터베이스 레이어와 데이터 레이어 간의 변환이 명확하게 구현되어 있습니다.
app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt (1)
21-37: Compose 마이그레이션이 잘 구현되었습니다!Activity가 Compose 기반으로 깔끔하게 마이그레이션되었습니다. Edge-to-edge 설정과 테마 적용, 네비게이션 콜백 설정이 모두 올바르게 구현되어 있습니다.
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt (1)
35-43: Flow 구현이 효율적입니다!JSON 디코딩을 위해 dispatcher를 사용하고
distinctUntilChanged()로 불필요한 emission을 방지하는 것은 좋은 구현입니다.feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt (1)
49-84: 필터 UI 컴포넌트가 잘 구성되었습니다!컴포저블 함수들이 적절하게 분리되어 있고, 재사용 가능한 구조로 잘 설계되었습니다. 스크롤 상태 관리와 테마 적용도 올바르게 구현되어 있습니다.
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt (1)
352-362: when 표현식을 exhaustive하게 만들어 컴파일 시점에 오류 감지새로운
AttractivePoints타입이 추가될 때 런타임 오류를 방지하기 위해 when 표현식을 exhaustive하게 만드세요.@Composable private fun attractivePointIcon(points: AttractivePointUiModel): ImageVector { - val resId = when (points.type) { + return when (points.type) { AttractivePoints.CHARACTER -> ic_library_character AttractivePoints.MATERIAL -> ic_library_material AttractivePoints.WORLDVIEW -> ic_library_world_view AttractivePoints.RELATIONSHIP -> ic_library_relationship AttractivePoints.VIBE -> ic_library_vibe - } - return ImageVector.vectorResource(id = resId) + }.let { ImageVector.vectorResource(id = it) } }Likely an incorrect or invalid review comment.
feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt (1)
43-47: 스크롤 상태 관리 구현 확인리스트와 그리드 뷰의 스크롤 상태를 별도로 관리하는 구현이 적절합니다. 탭 전환 시에도 각 뷰의 스크롤 위치가 유지됩니다.
app/src/main/java/com/into/websoso/ui/main/MainActivity.kt (1)
110-123: 서재 탭 재클릭 시 스크롤 초기화 구현 확인PR 목표에 맞게 서재 탭을 다시 클릭했을 때 스크롤이 맨 위로 이동하는 기능이 올바르게 구현되었습니다.
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt (2)
57-58: 스크롤 상태 관리가 올바르게 구현됨ViewModel에서 관리되는
listState와gridState를 사용하여 탭 전환 시에도 스크롤 위치가 유지되도록 구현되었습니다.Also applies to: 156-160, 164-168
59-62: BottomSheet 상태 변경 제한 확인 필요
confirmValueChange = { false }로 설정되어 있어 사용자가 스와이프로 BottomSheet를 닫을 수 없습니다. 의도적인 설정인지 확인이 필요합니다.의도적으로 스와이프 닫기를 막은 것이라면 문제없지만, 그렇지 않다면 다음과 같이 수정하세요:
val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, - confirmValueChange = { false }, + confirmValueChange = { true }, )feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryFilterBottomSheet.kt (6)
1-61: 패키지 구조와 import 문이 잘 구성되어 있습니다.필요한 Compose UI, Material3, 도메인 모델 및 리소스 imports가 체계적으로 정리되어 있고, 패키지 명명 규칙도 적절합니다.
62-131: 메인 LibraryFilterBottomSheet 컴포저블 구현이 우수합니다.ModalBottomSheet을 사용하여 필터링 UI를 잘 구성했으며, 각 필터 섹션을 별도의 컴포저블로 분리하여 가독성과 유지보수성을 향상시켰습니다. 콜백 기반의 상태 관리 패턴도 적절합니다.
133-159: 헤더 컴포저블 구현이 적절합니다.제목과 닫기 버튼을 포함한 헤더 레이아웃이 Material Design 패턴을 잘 따르고 있습니다.
253-303: 버튼 섹션 구현이 우수합니다.초기화와 검색 버튼의 레이아웃과 스타일링이 Material Design 가이드라인을 잘 따르고 있으며, 시각적 계층구조도 명확합니다.
305-364: 별점 필터 그리드 구현이 적절합니다.2x2 그리드 레이아웃으로 별점 옵션을 효과적으로 배치했습니다.
400-421: 프리뷰 구현이 적절합니다.개발 시 UI 확인을 위한 프리뷰가 잘 설정되어 있습니다.
| if (fragmentType != null) { | ||
| val intent = | ||
| MainActivity.getIntent(context, MainActivity.FragmentType.valueOf(fragmentType)) | ||
| startActivity(intent) | ||
| return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
문자열-열거형 변환 시 예외 처리 필요
valueOf(fragmentType) 호출 시 잘못된 문자열이 전달되면 IllegalArgumentException이 발생할 수 있습니다. 안전한 변환을 위해 예외 처리나 유효성 검사를 추가하는 것을 고려해보세요.
다음과 같이 안전한 변환을 구현할 수 있습니다:
- val intent =
- MainActivity.getIntent(context, MainActivity.FragmentType.valueOf(fragmentType))
+ val fragmentTypeEnum = try {
+ MainActivity.FragmentType.valueOf(fragmentType)
+ } catch (e: IllegalArgumentException) {
+ // 기본값 사용 또는 에러 처리
+ return
+ }
+ val intent = MainActivity.getIntent(context, fragmentTypeEnum)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (fragmentType != null) { | |
| val intent = | |
| MainActivity.getIntent(context, MainActivity.FragmentType.valueOf(fragmentType)) | |
| startActivity(intent) | |
| return | |
| if (fragmentType != null) { | |
| val fragmentTypeEnum = try { | |
| MainActivity.FragmentType.valueOf(fragmentType) | |
| } catch (e: IllegalArgumentException) { | |
| // 기본값 사용 또는 에러 처리 | |
| return | |
| } | |
| val intent = MainActivity.getIntent(context, fragmentTypeEnum) | |
| startActivity(intent) | |
| return | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt
around lines 34 to 38, the call to
MainActivity.FragmentType.valueOf(fragmentType) can throw an
IllegalArgumentException if fragmentType is invalid. To fix this, add a
try-catch block around the valueOf call to catch IllegalArgumentException and
handle it gracefully, or validate fragmentType against the enum values before
calling valueOf to ensure it is valid.
| fun resetScrollPosition() { | ||
| val viewModel: LibraryViewModel by viewModels() | ||
| viewModel.resetScrollPosition() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ViewModel 인스턴스 불일치 문제
resetScrollPosition() 메서드에서 by viewModels() 델리게이트를 사용하여 새로운 ViewModel 인스턴스를 생성하고 있습니다. 이는 LibraryScreen 컴포저블에서 사용하는 ViewModel 인스턴스와 다른 인스턴스가 되어 스크롤 위치 초기화가 제대로 동작하지 않을 것입니다.
다음과 같이 수정하여 동일한 ViewModel 인스턴스를 사용하도록 해야 합니다:
+ private val libraryViewModel: LibraryViewModel by viewModels()
+
fun resetScrollPosition() {
- val viewModel: LibraryViewModel by viewModels()
- viewModel.resetScrollPosition()
+ libraryViewModel.resetScrollPosition()
}또는 LibraryScreen 컴포저블에 ViewModel 인스턴스를 전달하는 방식으로 구현할 수 있습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fun resetScrollPosition() { | |
| val viewModel: LibraryViewModel by viewModels() | |
| viewModel.resetScrollPosition() | |
| } | |
| // Use a single ViewModel instance for this Fragment | |
| private val libraryViewModel: LibraryViewModel by viewModels() | |
| fun resetScrollPosition() { | |
| libraryViewModel.resetScrollPosition() | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt around
lines 49 to 52, the resetScrollPosition() method creates a new ViewModel
instance using by viewModels(), causing a mismatch with the ViewModel used in
LibraryScreen composable. To fix this, modify the method to use the existing
ViewModel instance shared with LibraryScreen, either by passing the ViewModel as
a parameter to this method or by accessing the same ViewModel instance through a
shared scope, ensuring the scroll position reset affects the correct ViewModel.
| fun valueOf(id: Int): FragmentType = | ||
| entries.find { it.resId == id } | ||
| ?: throw IllegalArgumentException() | ||
|
|
||
| fun valueOf(fragmentName: String): FragmentType = | ||
| entries.find { it.name == fragmentName } | ||
| ?: throw IllegalArgumentException() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
예외 처리 시 의미 있는 메시지 추가
예외 생성 시 구체적인 오류 메시지를 포함하여 디버깅을 용이하게 하세요.
fun valueOf(id: Int): FragmentType =
entries.find { it.resId == id }
- ?: throw IllegalArgumentException()
+ ?: throw IllegalArgumentException("Unknown fragment resource id: $id")
fun valueOf(fragmentName: String): FragmentType =
entries.find { it.name == fragmentName }
- ?: throw IllegalArgumentException()
+ ?: throw IllegalArgumentException("Unknown fragment name: $fragmentName")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fun valueOf(id: Int): FragmentType = | |
| entries.find { it.resId == id } | |
| ?: throw IllegalArgumentException() | |
| fun valueOf(fragmentName: String): FragmentType = | |
| entries.find { it.name == fragmentName } | |
| ?: throw IllegalArgumentException() | |
| } | |
| fun valueOf(id: Int): FragmentType = | |
| entries.find { it.resId == id } | |
| ?: throw IllegalArgumentException("Unknown fragment resource id: $id") | |
| fun valueOf(fragmentName: String): FragmentType = | |
| entries.find { it.name == fragmentName } | |
| ?: throw IllegalArgumentException("Unknown fragment name: $fragmentName") |
🧰 Tools
🪛 detekt (1.23.8)
[warning] 231-231: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
[warning] 235-235: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/main/MainActivity.kt around lines 229
to 236, the IllegalArgumentException thrown when no matching FragmentType is
found lacks a descriptive message. Update both valueOf functions to include
meaningful error messages in the exceptions, specifying which id or fragmentName
caused the error to improve debugging clarity.
| class Converters { | ||
| @TypeConverter | ||
| fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",") | ||
|
|
||
| @TypeConverter | ||
| fun listToString(list: List<String>): String = list.joinToString(",") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쉼표가 포함된 문자열 처리 시 데이터 손실 위험이 있습니다.
현재 구현은 리스트 항목에 쉼표가 포함된 경우 데이터 변환 과정에서 문제가 발생할 수 있습니다. 예를 들어, ["Hello, World", "Test"] 리스트가 "Hello, World,Test"로 변환되고, 다시 파싱할 때 ["Hello", " World", "Test"]가 되어 원본 데이터가 손실됩니다.
다음과 같은 개선 방안을 고려해보세요:
class Converters {
@TypeConverter
- fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",")
+ fun fromString(value: String): List<String> {
+ if (value.isEmpty()) return emptyList()
+ return try {
+ Json.decodeFromString<List<String>>(value)
+ } catch (e: Exception) {
+ // 레거시 쉼표 구분 방식에 대한 fallback
+ value.split(",").map { it.trim() }
+ }
+ }
@TypeConverter
- fun listToString(list: List<String>): String = list.joinToString(",")
+ fun listToString(list: List<String>): String = Json.encodeToString(list)
}또는 이스케이프 처리를 통한 해결:
@TypeConverter
- fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",")
+ fun fromString(value: String): List<String> {
+ if (value.isEmpty()) return emptyList()
+ return value.split("\\|\\|").map { it.replace("\\|", ",") }
+ }
@TypeConverter
- fun listToString(list: List<String>): String = list.joinToString(",")
+ fun listToString(list: List<String>): String =
+ list.map { it.replace(",", "\\|") }.joinToString("||")Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In core/database/src/main/java/com/into/websoso/core/database/Converters.kt
lines 5 to 11, the current string-to-list and list-to-string conversion using
commas fails when list items contain commas, causing data loss. To fix this,
implement an escaping mechanism for commas in list items during serialization
and unescaping during deserialization, or use a more robust serialization format
like JSON to safely encode and decode the list without losing data.
| @Qualifier | ||
| @Retention(AnnotationRetention.BINARY) | ||
| internal annotation class MyLibraryFilterDataStore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal 한정자 사용 시 모듈 간 DI 주입 불가 가능성
Qualifier가 다른 모듈(data, feature 등)에서 사용된다면 internal 범위는 접근을 막아 컴파일 오류가 납니다.
외부 모듈에서 주입해야 한다면 한정자를 public(생략)으로 수정해 주세요.
-internal annotation class MyLibraryFilterDataStore
+annotation class MyLibraryFilterDataStore🤖 Prompt for AI Agents
In
core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt
at lines 9 to 11, the qualifier annotation class is marked as internal, which
restricts its visibility and prevents dependency injection across different
modules. To fix this, remove the internal modifier to make the annotation
public, allowing it to be accessed and used for DI in other modules.
| companion object { | ||
| fun valueOf(name: String): ReadStatus = | ||
| entries.find { readStatus -> readStatus.name == name } | ||
| ?: throw IllegalArgumentException() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
예외 메시지 추가 및 함수명 개선이 필요합니다.
정적 분석 도구의 경고가 유효합니다. 다음 개선사항들을 고려해주세요:
IllegalArgumentException에 의미있는 메시지 추가- 기본 enum
valueOf와 혼동을 피하기 위해 함수명 변경
companion object {
- fun valueOf(name: String): ReadStatus =
+ fun fromName(name: String): ReadStatus =
entries.find { readStatus -> readStatus.name == name }
- ?: throw IllegalArgumentException()
+ ?: throw IllegalArgumentException("Unknown ReadStatus: $name")
}🧰 Tools
🪛 detekt (1.23.8)
[warning] 12-12: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.
(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)
🤖 Prompt for AI Agents
In
domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt
around lines 9 to 13, the custom valueOf function throws an
IllegalArgumentException without a message and its name conflicts with the
default enum valueOf method. Rename the function to a more descriptive name like
fromName or findByName, and update the exception to include a meaningful message
indicating the invalid name input.
| @Composable | ||
| private fun ReadStatusBadge( | ||
| readStatus: ReadStatusUiModel, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| Text( | ||
| text = readStatus.label, | ||
| color = White, | ||
| style = WebsosoTheme.typography.label2, | ||
| modifier = modifier | ||
| .background( | ||
| color = readStatus.backgroundColor, | ||
| shape = RoundedCornerShape(4.dp), | ||
| ).padding(horizontal = 8.dp, vertical = 4.dp), | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
ReadStatusBadge 구현의 일관성을 확인해주세요.
이 파일의 ReadStatusBadge와 LibraryListItem.kt의 동일한 함수 사이에 구현 차이가 있습니다. 현재 구현은 더 간단하지만, 일관성을 위해 동일한 구현을 사용하는 것을 고려해보세요.
LibraryListItem.kt의 구현과 통일하거나, 공통 컴포넌트로 추출하는 것을 권장합니다:
// 공통 컴포넌트로 추출
@Composable
internal fun ReadStatusBadge(
readStatus: ReadStatusUiModel?,
modifier: Modifier = Modifier,
width: Dp? = null,
) {
if (readStatus != null) {
val badgeModifier = if (width != null) {
modifier.width(width)
} else modifier
Box(
modifier = badgeModifier
.background(
color = readStatus.backgroundColor,
shape = RoundedCornerShape(4.dp),
)
.padding(horizontal = 8.dp, vertical = 4.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = readStatus.label,
color = White,
style = WebsosoTheme.typography.label2,
)
}
}
}🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt
around lines 130 to 145, the ReadStatusBadge composable implementation differs
from the one in LibraryListItem.kt, causing inconsistency. To fix this, refactor
ReadStatusBadge to match the implementation in LibraryListItem.kt by accepting a
nullable ReadStatusUiModel, optionally applying a width modifier, wrapping the
Text inside a Box with background and padding, and centering the content.
Alternatively, extract this as a shared common component used by both files to
ensure consistency.
| private fun ReadStatusBadge( | ||
| readStatus: ReadStatusUiModel?, | ||
| width: Dp, | ||
| ) { | ||
| if (readStatus != null) { | ||
| Box( | ||
| modifier = Modifier | ||
| .width(width) | ||
| .background( | ||
| color = readStatus.backgroundColor, | ||
| shape = RoundedCornerShape(8.dp), | ||
| ) | ||
| .padding(vertical = 4.dp), | ||
| contentAlignment = Alignment.Center, | ||
| ) { | ||
| Text( | ||
| text = readStatus.label, | ||
| color = White, | ||
| style = WebsosoTheme.typography.label2, | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
ReadStatusBadge 구현의 일관성 개선 필요
LibraryGridListItem.kt의 ReadStatusBadge와 구현이 다릅니다. 두 구현체는 파라미터 타입(nullable vs non-nullable), 코너 반경(8.dp vs 4.dp), 패딩 방식이 다릅니다.
공통 컴포넌트로 추출하여 재사용하는 것을 고려해보세요:
-@Composable
-private fun ReadStatusBadge(
- readStatus: ReadStatusUiModel?,
- width: Dp,
-) {
- if (readStatus != null) {
- Box(
- modifier = Modifier
- .width(width)
- .background(
- color = readStatus.backgroundColor,
- shape = RoundedCornerShape(8.dp),
- )
- .padding(vertical = 4.dp),
- contentAlignment = Alignment.Center,
- ) {
- Text(
- text = readStatus.label,
- color = White,
- style = WebsosoTheme.typography.label2,
- )
- }
- }
-}core.designsystem 모듈에 공통 컴포넌트를 만들고:
// In core.designsystem module
@Composable
fun ReadStatusBadge(
readStatus: ReadStatusUiModel,
modifier: Modifier = Modifier,
cornerRadius: Dp = 8.dp,
) {
Box(
modifier = modifier
.background(
color = readStatus.backgroundColor,
shape = RoundedCornerShape(cornerRadius),
)
.padding(horizontal = 8.dp, vertical = 4.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = readStatus.label,
color = White,
style = WebsosoTheme.typography.label2,
)
}
}🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt
around lines 158 to 180, the ReadStatusBadge implementation differs from the one
in LibraryGridListItem.kt in parameter nullability, corner radius, and padding.
To fix this, refactor both implementations into a single reusable composable in
the core.designsystem module with non-nullable readStatus, a Modifier parameter,
and a cornerRadius parameter defaulting to 8.dp. Update usages to use this
common component with consistent styling and padding as suggested.
| internal fun LibraryTopBar(onSearchClick: () -> Unit = {}) { | ||
| Row( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 20.dp, vertical = 10.dp), | ||
| horizontalArrangement = Arrangement.SpaceBetween, | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| ) { | ||
| Text( | ||
| text = stringResource(id = library_title), | ||
| style = WebsosoTheme.typography.headline1, | ||
| color = Black, | ||
| ) | ||
| /* | ||
| TODO: 추후 검색 기능 구현 시 롤백 | ||
| IconButton(onClick = onSearchClick) { | ||
| Image( | ||
| imageVector = ImageVector.vectorResource(id = ic_common_search), | ||
| contentDescription = "검색", | ||
| modifier = Modifier.size(24.dp), | ||
| ) | ||
| } | ||
| } | ||
| */ | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
레이아웃 구성 최적화 필요
현재 SpaceBetween 정렬을 사용하지만 실제로는 하나의 자식만 표시되어 의미가 없습니다. 검색 기능이 구현될 때까지 더 간단한 구성을 사용하는 것이 좋겠습니다.
@Composable
-internal fun LibraryTopBar(onSearchClick: () -> Unit = {}) {
+internal fun LibraryTopBar() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 10.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
+ horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = library_title),
style = WebsosoTheme.typography.headline1,
color = Black,
)
- /*
- TODO: 추후 검색 기능 구현 시 롤백
- ... commented code ...
- */
}
}주석 처리된 코드는 TODO 이슈로 분리하거나 제거하는 것이 좋습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| internal fun LibraryTopBar(onSearchClick: () -> Unit = {}) { | |
| Row( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .padding(horizontal = 20.dp, vertical = 10.dp), | |
| horizontalArrangement = Arrangement.SpaceBetween, | |
| verticalAlignment = Alignment.CenterVertically, | |
| ) { | |
| Text( | |
| text = stringResource(id = library_title), | |
| style = WebsosoTheme.typography.headline1, | |
| color = Black, | |
| ) | |
| /* | |
| TODO: 추후 검색 기능 구현 시 롤백 | |
| IconButton(onClick = onSearchClick) { | |
| Image( | |
| imageVector = ImageVector.vectorResource(id = ic_common_search), | |
| contentDescription = "검색", | |
| modifier = Modifier.size(24.dp), | |
| ) | |
| } | |
| } | |
| */ | |
| } | |
| } | |
| @Composable | |
| internal fun LibraryTopBar() { | |
| Row( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .padding(horizontal = 20.dp, vertical = 10.dp), | |
| horizontalArrangement = Arrangement.Start, | |
| verticalAlignment = Alignment.CenterVertically, | |
| ) { | |
| Text( | |
| text = stringResource(id = library_title), | |
| style = WebsosoTheme.typography.headline1, | |
| color = Black, | |
| ) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryTopBar.kt
between lines 18 and 44, the Row uses SpaceBetween arrangement but only has one
visible child, making the layout ineffective. Simplify the layout by removing
the SpaceBetween arrangement and use a simpler alignment until the search
feature is implemented. Also, remove or move the commented-out search IconButton
code to a separate TODO issue or file instead of keeping it commented here.
| viewModelScope.launch { | ||
| libraryRepository.myLibraryFilter.collect { myFilter -> | ||
| if (myFilter != null) { | ||
| _uiState.update { uiState -> | ||
| uiState.copy( | ||
| isInterested = myFilter.isInterest ?: false, | ||
| libraryFilterUiState = uiState.libraryFilterUiState.copy( | ||
| readStatuses = uiState.libraryFilterUiState.readStatuses + | ||
| ( | ||
| myFilter.readStatuses?.map { | ||
| ReadStatus.valueOf(it) to true | ||
| } ?: emptyList() | ||
| ), | ||
| attractivePoints = uiState.libraryFilterUiState.attractivePoints + | ||
| ( | ||
| myFilter.attractivePoints?.map { | ||
| AttractivePoints.valueOf(it) to true | ||
| } ?: emptyList() | ||
| ), | ||
| novelRating = myFilter.novelRating ?: 0f, | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
필터 상태 업데이트 시 중복 항목 방지 필요
readStatuses와 attractivePoints 업데이트 시 기존 리스트에 추가하는 방식으로 인해 중복 항목이 발생할 수 있습니다.
libraryFilterUiState = uiState.libraryFilterUiState.copy(
- readStatuses = uiState.libraryFilterUiState.readStatuses +
- (
- myFilter.readStatuses?.map {
- ReadStatus.valueOf(it) to true
- } ?: emptyList()
- ),
+ readStatuses = myFilter.readStatuses?.associate {
+ ReadStatus.valueOf(it) to true
+ } ?: emptyMap(),
- attractivePoints = uiState.libraryFilterUiState.attractivePoints +
- (
- myFilter.attractivePoints?.map {
- AttractivePoints.valueOf(it) to true
- } ?: emptyList()
- ),
+ attractivePoints = myFilter.attractivePoints?.associate {
+ AttractivePoints.valueOf(it) to true
+ } ?: emptyMap(),
novelRating = myFilter.novelRating ?: 0f,
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| viewModelScope.launch { | |
| libraryRepository.myLibraryFilter.collect { myFilter -> | |
| if (myFilter != null) { | |
| _uiState.update { uiState -> | |
| uiState.copy( | |
| isInterested = myFilter.isInterest ?: false, | |
| libraryFilterUiState = uiState.libraryFilterUiState.copy( | |
| readStatuses = uiState.libraryFilterUiState.readStatuses + | |
| ( | |
| myFilter.readStatuses?.map { | |
| ReadStatus.valueOf(it) to true | |
| } ?: emptyList() | |
| ), | |
| attractivePoints = uiState.libraryFilterUiState.attractivePoints + | |
| ( | |
| myFilter.attractivePoints?.map { | |
| AttractivePoints.valueOf(it) to true | |
| } ?: emptyList() | |
| ), | |
| novelRating = myFilter.novelRating ?: 0f, | |
| ), | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| viewModelScope.launch { | |
| libraryRepository.myLibraryFilter.collect { myFilter -> | |
| if (myFilter != null) { | |
| _uiState.update { uiState -> | |
| uiState.copy( | |
| isInterested = myFilter.isInterest ?: false, | |
| libraryFilterUiState = uiState.libraryFilterUiState.copy( | |
| readStatuses = myFilter.readStatuses | |
| ?.associate { ReadStatus.valueOf(it) to true } | |
| ?: emptyMap(), | |
| attractivePoints = myFilter.attractivePoints | |
| ?.associate { AttractivePoints.valueOf(it) to true } | |
| ?: emptyMap(), | |
| novelRating = myFilter.novelRating ?: 0f, | |
| ), | |
| ) | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt
between lines 54 and 80, the update logic for readStatuses and attractivePoints
appends new items to existing lists without checking for duplicates, causing
duplicate entries. Modify the code to merge the existing list and new items into
a set or use a distinct operation to ensure no duplicates before updating the
state.
s9hn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어려운 테스크였는데 고생했습니다!
다만 코드 수정은 필요해보여요!
FragmentManager를 통해 Fragment를 활용할 수 있는 구조로 변경하고,
객체간 역할과 책임을 고민해보면 도움이 될 것 같아요
| private val fragmentTags = mapOf( | ||
| menu_home to HomeFragment::class.java.name, | ||
| menu_explore to ExploreFragment::class.java.name, | ||
| menu_feed to FeedFragment::class.java.name, | ||
| menu_library to LibraryFragment::class.java.name, | ||
| menu_my_page to MyPageFragment::class.java.name, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: 각 프래그먼트에서 관리하면 좋을 것 같아요!
| private fun setupInitialFragment() { | ||
| val initialItemId = menu_home | ||
| val initialTag = fragmentTags[initialItemId]!! | ||
| val initialFragment = findOrCreateFragment(initialTag) | ||
|
|
||
| if (!initialFragment.isAdded) { | ||
| supportFragmentManager | ||
| .beginTransaction() | ||
| .add(fcv_main, initialFragment, initialTag) | ||
| .commit() | ||
| } | ||
|
|
||
| currentFragment = initialFragment | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r:
각 프래그먼트에 선언된 태그를 사용해 initialFragment를 가져오는 식으로 수정해보면 좋을 것 같아요
추가로 제공되는 fragment KTX DSL을 이용해주세요!
supportFragmentManager.commit {
setReorderingAllowed(true)
if (!initialFragment.isAdded) {
add(R.id.fcv_main, initialFragment, initialTag)
...
// 로직에 맞게
}
}
내부에 currentFragment를 직접 관리하는 것보다, supportFragmentManager에게 현재 프래그먼트를 요청해주세요
| private fun replaceCurrentFragment(itemId: Int) { | ||
| val tag = fragmentTags[itemId]!! | ||
| val targetFragment = findOrCreateFragment(tag) | ||
|
|
||
| val transaction = supportFragmentManager.beginTransaction() | ||
|
|
||
| currentFragment?.let { transaction.hide(it) } | ||
|
|
||
| if (!targetFragment.isAdded) { | ||
| transaction.add(fcv_main, targetFragment, tag) | ||
| } else { | ||
| transaction.show(targetFragment) | ||
| } | ||
|
|
||
| transaction.commit() | ||
| currentFragment = targetFragment | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: 위랑 동일합니다! fragment dsl로 변경해주세요!
| private fun findOrCreateFragment(tag: String): Fragment = | ||
| supportFragmentManager.findFragmentByTag(tag) | ||
| ?: when (tag) { | ||
| HomeFragment::class.java.name -> HomeFragment() | ||
| ExploreFragment::class.java.name -> ExploreFragment() | ||
| FeedFragment::class.java.name -> FeedFragment() | ||
| LibraryFragment::class.java.name -> LibraryFragment() | ||
| MyPageFragment::class.java.name -> MyPageFragment() | ||
| else -> throw IllegalArgumentException("Unknown fragment tag: $tag") | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: 여기도 각 프래그먼트에 정의된 태그를 기반으로 !
|
|
||
| private fun setupBottomNavListener() { | ||
| binding.bnvMain.setOnItemSelectedListener { item -> | ||
| if (item.itemId == currentSelectedItemId && item.itemId == menu_library) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: currentSelectedItemId 로 내부 상태를 이용하기보다 supportFragmentManager를 통해 조회 및 비교해보시죠
만약 현재 프래그먼트가 라이브러리가 맞다면, 굳이 아래에 libraryFragment를 가져오지 않고 바로 supportFragmentManager를 통해 불러와도 될 것 같아요
| LibraryFragment::class.java.name, | ||
| ) as? LibraryFragment | ||
|
|
||
| libraryFragment?.resetScrollPosition() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: fragment로 함수를 직접 호출하는 것보단, 메시지를 전달하는 구조가 좋을 것 같아요.
FragmentManager를 통해 Bundle 메시지 전달을 위임해도 좋아보입니다!
// 송신
supportFragmentManager.setFragmentResult( 키값, 번들.Empty)
// 수신
parentFragmentManager.setFragmentResultListener (키값, viewLifecycleowner) {
위로 스크롤
}
이렇게 구현하면, libraryFragment를 직접 find할 필요도 없겠네요 !
| fun resetScrollPosition() { | ||
| val viewModel: LibraryViewModel by viewModels() | ||
| viewModel.resetScrollPosition() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: 코드래빗 말대로 함수호출마다 매번 뷰모델을 생성하고 있어요
Fragment API를 이용해봅시다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 Fragment와 LibraryScreen이 같은 함수를 공유해야하니, viewModel을 매번 생성하는 것보다 Fragment에서 생성하고 Screen에 주입하는 구조가 좋을 것 같습니다!
| var listState by mutableStateOf(LazyListState()) | ||
| private set | ||
|
|
||
| var gridState by mutableStateOf(LazyGridState()) | ||
| private set | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: mutableStateOf는 Compose API입니다! 뷰모델보단 뷰에 어울리는 의존성이예요
| fun resetScrollPosition() { | ||
| viewModelScope.launch { | ||
| if (uiState.value.isGrid) { | ||
| gridState.scrollToItem(0) | ||
| } else { | ||
| listState.scrollToItem(0) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: 이벤트를 전파하고, 뷰에서 이를 감지해 동작하도록 구현해도 좋겠어요
| ) | ||
|
|
||
| LaunchedEffect(Unit) { | ||
| libraryViewModel.scrollToTopEvent.collect { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: 현재 코드는 백그라운드에서도 수집을 계속해요!
| LaunchedEffect(Unit) { | ||
| libraryViewModel.scrollToTopEvent.collect { | ||
| if (uiState.isGrid) { | ||
| gridState.scrollToItem(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a: 이런건 상수화하는 편입니다 '이동할포지션'이란 뜻으로?
| var gridState by mutableStateOf(LazyGridState()) | ||
| private set | ||
| private val _scrollToTopEvent = MutableSharedFlow<Unit>() | ||
| val scrollToTopEvent: SharedFlow<Unit> = _scrollToTopEvent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: LoginViewModel 참고해서 Channel로 바꿔주세요 !
| class MainActivity : BaseActivity<ActivityMainBinding>(activity_main) { | ||
| private val mainViewModel: MainViewModel by viewModels() | ||
| private var backPressedTime: Long = 0L | ||
| private var currentFragment: Fragment? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: 근데 이 프로퍼티는 어디에 사용하나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replaceCurrentFragment 함수에서 사용되고 있어요!
| lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| libraryViewModel.scrollToTopEvent.collect { | ||
| if (uiState.isGrid) { | ||
| gridState.scrollToItem(SCROLL_POSITION_TOP) | ||
| } else { | ||
| listState.scrollToItem(SCROLL_POSITION_TOP) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r: 저희 core모듈 확장함수에 collectAsEventWithLifecycle 있습니다!
📌𝘐𝘴𝘴𝘶𝘦𝘴
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵
KakaoTalk_Video_2025-07-21-18-06-24.mp4
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴
Summary by CodeRabbit
New Features
버그 수정
리팩터/구조 개선
리소스/디자인
기타