[Feat] API 통신 구조 세팅 및 프로필 키워드 조회 API 연동#135
Conversation
# Conflicts: # app/src/main/java/com/flint/presentation/login/LoginScreen.kt # app/src/main/java/com/flint/presentation/login/LoginViewModel.kt
📝 WalkthroughWalkthroughAPI 설계 구조를 개선하기 위해 의존성 주입 모듈을 재정렬하고, 단일 샘플 API에서 도메인별 API로 마이그레이션했습니다. DTO 및 도메인 모델 패키지를 재구성하고, 영화 관련 용어를 콘텐츠로 통일하며, 새로운 리포지토리와 사용자 키워드 조회 기능을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant ProfileRoute
participant ProfileViewModel
participant UserRepository
participant UserApi
participant Backend
User->>ProfileRoute: 프로필 화면 진입
ProfileRoute->>ProfileViewModel: hiltViewModel() 생성
ProfileViewModel->>ProfileViewModel: loadInitialData() 호출
ProfileViewModel->>UserRepository: getUserKeywords(userId)
UserRepository->>UserApi: getUserKeywords(userId)
UserApi->>Backend: GET /api/v1/users/{userId}/keywords
Backend-->>UserApi: UserKeywordsResponseDto
UserApi-->>UserRepository: Result<List<UserKeywordResponseModel>>
UserRepository->>UserRepository: DTO를 Model로 매핑
UserRepository-->>ProfileViewModel: Result<List<UserKeywordResponseModel>>
ProfileViewModel->>ProfileViewModel: UiState.Success로 업데이트
ProfileViewModel-->>ProfileRoute: StateFlow<UiState<ProfileUiState>>
ProfileRoute->>User: ProfileScreen 렌더링
sequenceDiagram
actor User
participant ServiceModule
participant Retrofit
participant ApiService
participant NetworkLayer
User->>ServiceModule: 의존성 주입 초기화
ServiceModule->>ServiceModule: provideUserApi(retrofit)
ServiceModule->>ServiceModule: provideBookmarkApi(retrofit)
ServiceModule->>ServiceModule: provideCollectionApi(retrofit)
ServiceModule->>ServiceModule: provideContentApi(retrofit)
ServiceModule->>ServiceModule: provideHomeApi(retrofit)
ServiceModule->>ServiceModule: provideSearchApi(retrofit)
ServiceModule->>Retrofit: Retrofit.create(API 인터페이스)
Retrofit-->>ServiceModule: API 구현체
ServiceModule-->>ApiService: `@Singleton으로` 캐싱
ApiService->>NetworkLayer: API 호출 준비 완료
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50분 Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
app/src/main/java/com/flint/presentation/home/HomeScreen.kt (1)
36-55: 프로덕션 라우트에 더미 데이터/미구현 핸들러가 남아 있음
HomeRoute가 실제 화면 진입점인데CollectionModel.FakeList/ContentModel.FakeList와TODO핸들러를 넘기고 있어 API 연동·탐색 이동·저장 콘텐츠 동작이 모두 무력화됩니다. 최소한 라우트 파라미터로 실제 상태/핸들러를 주입해 실제 플로우를 보장해야 합니다.🔧 제안 수정안 (이 파일 기준)
fun HomeRoute( paddingValues: PaddingValues, + recommendCollectionModelList: ImmutableList<CollectionModel>, + savedContentModelList: ImmutableList<ContentModel>, + recentCollectionModelList: ImmutableList<CollectionModel>, navigateToCollectionList: () -> Unit, navigateToCollectionDetail: (collectionId: String) -> Unit, navigateToCollectionCreate: () -> Unit, + navigateToExplore: () -> Unit, + onSavedContentItemClick: (contentId: Long) -> Unit, ) { HomeScreen( - recentCollectionModelList = CollectionModel.FakeList, - recommendCollectionModelList = CollectionModel.FakeList, - savedContentModelList = ContentModel.FakeList, + recentCollectionModelList = recentCollectionModelList, + recommendCollectionModelList = recommendCollectionModelList, + savedContentModelList = savedContentModelList, navigateToCollectionCreate = { navigateToCollectionCreate() }, - navigateToExplore = { - // TODO navigate to explore - }, + navigateToExplore = navigateToExplore, onRecentCollectionItemClick = { collectionId -> navigateToCollectionDetail(collectionId) }, onRecentCollectionAllClick = navigateToCollectionList, onRecommendCollectionItemClick = { collectionId -> navigateToCollectionDetail(collectionId) }, - onSavedContentItemClick = { contentId -> - // TODO show OttListBottomSheet - }, + onSavedContentItemClick = onSavedContentItemClick, modifier = Modifier.padding(paddingValues), ) }app/src/main/java/com/flint/core/designsystem/component/listView/SavedContentsSection.kt (1)
72-83: 접근성: 클릭 아이콘에 설명 추가와 터치 영역 확대 필요
contentDescription = null이면 스크린리더에서 버튼의 기능을 알 수 없습니다. 또한padding이noRippleClickable이후에 적용되어 실제 클릭 가능한 영역이 12.dp만큼 감소합니다. 터치 타겟 최소 기준(48.dp)을 고려하여 접근성을 개선해야 합니다.수정 제안
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.res.stringResource @@ Icon( imageVector = ImageVector.vectorResource(R.drawable.ic_more), - contentDescription = null, + contentDescription = stringResource(R.string.content_more), tint = Color.Unspecified, modifier = Modifier .size(48.dp) + .padding(12.dp) - .noRippleClickable { - onAllClick() - }.padding(12.dp), + .noRippleClickable { + onAllClick() + }, )
./app/src/main/res/values/strings.xml에 아래 문자열을 추가하세요:<string name="content_more">더보기</string>app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailScreen.kt (1)
70-87: Route가 실제 데이터/네비게이션을 무시하고 고정 더미로 동작합니다.
collectionId/navigateToCollectionList가 사용되지 않고, 화면이 하드코딩된 샘플로 고정되어 실제 API 연동 흐름을 막습니다. 릴리즈 전에는 ViewModel 상태 기반 데이터로 교체하고, 더미는 Preview/디버그 전용으로 분리하는 게 안전합니다.app/src/main/java/com/flint/domain/model/collection/CollectionModel.kt (1)
16-46: FakeList의collectionId가 모두 빈 문자열입니다.
LazyVerticalGrid에서key = { it.collectionId }를 사용하고 있는데,FakeList의 두 아이템 모두collectionId = ""로 동일합니다. 이는 Compose에서 중복 키 경고 또는 예상치 못한 동작을 유발할 수 있습니다.🐛 FakeList에 고유한 ID 부여
val FakeList = persistentListOf( CollectionModel( - collectionId = "", + collectionId = "fake-1", collectionTitle = "컬렉션 제목", ... ), CollectionModel( - collectionId = "", + collectionId = "fake-2", collectionTitle = "컬렉션 제목2", ... ), )
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/data/api/UserApi.kt`:
- Line 6: UserApi.kt contains an unused import: remove the unused import
statement "import retrofit2.http.Header" from the top of UserApi.kt; ensure no
remaining references to Header are needed by any functions or annotations in the
UserApi interface (e.g., methods inside UserApi), and run a build/IDE
auto-import cleanup to verify there are no other unused imports.
In
`@app/src/main/java/com/flint/data/dto/user/response/UserKeywordsResponseDto.kt`:
- Around line 12-24: The UserKeywordsResultDto data class currently defines
non-nullable properties without defaults, which will fail deserialization if API
returns null; update UserKeywordsResultDto by either assigning sensible default
values (e.g., empty string, 0, 0.0f) to properties color, rank, name,
percentage, imageUrl or make those properties nullable (String?, Int?, Float?)
to match SocialVerifyResponseDto style so coerceInputValues=true can safely
handle missing/null fields during JSON deserialization.
In `@app/src/main/java/com/flint/domain/model/user/UserKeywordResponseModel.kt`:
- Around line 14-15: The current declaration inside UserKeywordResponseModel
uses a member extension syntax (val UserKeywordResponseModel.preferenceType)
which prevents instance.property access and also calls
PreferenceType.valueOf(color) which throws on unknown values; change it to a
normal property on the class (e.g., val preferenceType: PreferenceType) and
implement safe parsing of color (use PreferenceType.values().firstOrNull {
it.name == color } ?: fallback or runCatching { PreferenceType.valueOf(color)
}.getOrElse { /* fallback */ }) so invalid color strings don't throw.
In `@app/src/main/java/com/flint/domain/repository/AuthRepository.kt`:
- Around line 1-22: The current AuthRepository class in the domain layer depends
on data.api.AuthApi, violating clean architecture; change AuthRepository to an
interface in the domain package (interface AuthRepository with suspend fun
signup(...) and suspend fun socialVerify(...), referencing SignupRequestModel,
SignupResponseModel, SocialVerifyRequestModel, SocialVerifyResponseModel), then
create a new implementation class (e.g., AuthRepositoryImpl) in the data layer
that injects AuthApi and implements those methods by delegating to
api.signup(...).data.toModel() and api.socialVerify(...).data.toModel(); update
DI bindings to bind AuthRepository to AuthRepositoryImpl and remove direct data
imports from the domain file.
- Line 10: AuthRepository.kt (and likewise ProfileViewModel.kt) currently import
jakarta.inject.Inject; change those imports to use javax.inject.Inject so DI
annotation usage matches the rest of the project (ServiceModule, NetworkModule,
DataStoreModule, etc.). Locate the import line in AuthRepository.kt and
ProfileViewModel.kt and replace jakarta.inject.Inject with javax.inject.Inject,
keeping the `@Inject` annotations on constructors/fields unchanged.
In
`@app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt`:
- Around line 150-166: rotateKeywordByRank currently uses
topRanks.zip(bottomRanks) which drops any remaining items when sizes differ
(losing keywords for odd counts); update the interleaving logic to iterate up to
max(topRanks.size, bottomRanks.size) and append pairs where present (or
alternately append the leftover items after the zip), ensuring all items from
sortedByRank are preserved; change the code around topRanks, bottomRanks and the
final flatMap/toPersistentList return to include remaining entries from the
longer list so no keywords are lost.
🧹 Nitpick comments (11)
app/src/main/java/com/flint/data/api/HomeApi.kt (1)
3-5: 빈 인터페이스는 필요 시점에 메서드 정의 후 추가를 권장합니다.
현재는 메서드가 없어 역할이 불명확하니, 실제 엔드포인트 정의가 준비되면 추가하거나 TODO/이슈로 추적하는 편이 명확합니다.app/src/main/java/com/flint/data/api/ContentApi.kt (1)
3-8: 실제 엔드포인트 정의 전까지는 추가를 보류하는 편이 명확합니다.
주석만 있는 빈 API는 사용처에 혼란을 줄 수 있어, 메서드 정의가 준비되면 추가하거나 TODO/이슈로 추적해 주세요.app/src/main/java/com/flint/data/api/SearchApi.kt (1)
3-5: 메서드 정의가 준비된 시점에 추가하는 것을 권장합니다.
현재 상태로는 역할이 모호하니 실제 계약이 준비되면 도입하거나 TODO/이슈로 관리해 주세요.app/src/main/java/com/flint/data/api/BookmarkApi.kt (1)
3-9: 빈 인터페이스 스캐폴드입니다.현재 이 인터페이스는 메서드 시그니처 없이 주석만 포함되어 있습니다. API 구조 세팅 단계에서 의도적인 것으로 보이지만, 추후 구현 시 누락되지 않도록 TODO 주석이나 이슈 트래킹이 필요할 수 있습니다.
메서드 시그니처를 미리 정의해두시겠습니까? 예시:
interface BookmarkApi { // 컬렉션 북마크 유저 조회 suspend fun getCollectionBookmarkUsers(collectionId: Long): Response<...> // 컬렉션 북마크 토글 suspend fun toggleCollectionBookmark(collectionId: Long): Response<...> // 콘텐츠 북마크 토글 suspend fun toggleContentBookmark(contentId: Long): Response<...> }app/src/main/java/com/flint/data/api/CollectionApi.kt (1)
3-10: 빈 인터페이스 노출 최소화 제안시그니처 없이 public 인터페이스만 추가되면 의미 없는 타입 의존이 생길 수 있습니다. 실제 계약이 확정되기 전이라면 TODO/이슈 링크로 의도를 명확히 하거나, 준비 완료 시점에 추가하는 방식을 고려해 주세요.
app/src/main/java/com/flint/core/designsystem/component/indicator/FlintLoadingIndicator.kt (1)
12-22: 재사용성을 위해modifier를 인자로 받도록 개선현재는 항상 전체 화면을 채워 다른 맥락에서 재사용하기 어렵습니다.
modifier(및 필요 시 배경색)를 인자로 받아 유연하게 쓰도록 해 주세요.♻️ 개선 예시
`@Composable` -fun FlintLoadingIndicator() { +fun FlintLoadingIndicator( + modifier: Modifier = Modifier, + backgroundColor: Color = Color.Transparent, +) { Box( modifier = - Modifier + modifier .fillMaxSize() - .background(Color.Transparent), + .background(backgroundColor), contentAlignment = Alignment.Center, ) { CircularProgressIndicator() } }app/src/main/java/com/flint/domain/type/PreferenceType.kt (1)
16-39: API 색상 문자열 대소문자 계약 확인 + 안전 매핑 헬퍼 권장.
valueOf를 사용하는 호출부가 있다면, 서버가 항상 대문자를 준다는 보장이 없을 때 런타임 예외가 납니다. 대소문자 무시 매핑 헬퍼를 추가하고 호출부에서 사용하도록 권장합니다.♻️ 안전 매핑 헬퍼 예시
enum class PreferenceType( val color: Color, `@DrawableRes` val backgroundRes: Int, ) { GREEN(...), ORANGE(...), YELLOW(...), BLUE(...), PINK(...), + ; + + companion object { + fun fromColor(color: String): PreferenceType? = + values().firstOrNull { it.name.equals(color, ignoreCase = true) } + } }app/src/main/java/com/flint/domain/mapper/user/KeywordMapper.kt (1)
1-13: Clean Architecture 레이어 의존성 위반도메인 레이어(
com.flint.domain)에서 데이터 레이어(com.flint.data.dto)를 직접 import하고 있습니다. Clean Architecture 원칙에 따르면 도메인 레이어는 외부 레이어에 의존하지 않아야 합니다.매퍼를 데이터 레이어(
com.flint.data.mapper)로 이동하는 것을 권장합니다.♻️ 제안하는 구조 변경
-package com.flint.domain.mapper.user +package com.flint.data.mapper.user import com.flint.data.dto.user.response.UserKeywordsResultDto import com.flint.domain.model.user.UserKeywordResponseModel fun UserKeywordsResultDto.toModel(): UserKeywordResponseModel = UserKeywordResponseModel( color = color, rank = rank, name = name, percentage = percentage, imageUrl = imageUrl, )app/src/main/java/com/flint/domain/repository/UserRepository.kt (1)
1-13: 도메인 레이어에서 데이터 레이어 의존
UserRepository가 도메인 패키지에 있지만 데이터 레이어의UserApi를 직접 의존하고 있습니다. 일반적인 Clean Architecture에서는 Repository 구현체를 데이터 레이어에 두고, 도메인 레이어에는 인터페이스만 정의합니다.PR 목표에 "repository를 클래스 형태로 직접 구현하도록 구조 조정"이 언급되어 있어 의도된 설계로 보이지만, 향후 테스트 용이성과 레이어 분리를 위해 인터페이스 분리를 고려해 주세요.
app/src/main/java/com/flint/presentation/profile/ProfileViewModel.kt (1)
41-57: 리프레시 시 로딩 상태 및 에러 처리 개선이 필요합니다.현재 구현에서 두 가지 개선점이 있습니다:
- API 호출 전 로딩 상태(
UiState.Loading)를 emit하지 않아 사용자에게 피드백이 없습니다.- 실패 시
UiState.Failure로 전체 상태가 덮어씌워져 기존 프로필 데이터가 손실됩니다.♻️ 제안된 개선
fun refreshProfileKeyword() { viewModelScope.launch { + // 옵션 1: 로딩 상태 표시 + // _uiState.emit(UiState.Loading) + userRepository.getUserKeywords(userId = 800370427074376635).fold( onFailure = { Timber.d("onFailure: $it") - _uiState.emit(UiState.Failure) + // 기존 상태 유지하면서 에러 로깅만 수행하거나, + // 별도의 에러 이벤트 채널 사용 고려 + // _uiState.emit(UiState.Failure) }, onSuccess = { result -> Timber.d("onSuccess: $result") _uiState.updateSuccess { it.copy( keywords = result.toImmutableList(), ) } }, ) } }app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt (1)
38-54: Empty 및 Failure 상태에 대한 UI 피드백이 누락되었습니다.
UiState.Empty와UiState.Failure상태가else -> {}로 처리되어 사용자에게 아무런 피드백이 표시되지 않습니다. 최소한 에러 메시지나 재시도 버튼을 표시하는 것이 좋습니다.♻️ 제안된 개선
when (val state = uiState) { is UiState.Loading -> { FlintLoadingIndicator() } is UiState.Success -> { ProfileScreen( modifier = Modifier.padding(paddingValues), uiState = state.data, onRefreshClick = viewModel::refreshProfileKeyword, onCollectionItemClick = navigateToCollectionDetail, onCollectionMoreClick = navigateToCollectionList, ) } - else -> {} + is UiState.Failure -> { + // TODO: 에러 UI 구현 (재시도 버튼 등) + } + + is UiState.Empty -> { + // 초기 로딩 또는 빈 상태 처리 + } }
| @Serializable | ||
| data class UserKeywordsResultDto( | ||
| @SerialName("color") | ||
| val color: String, | ||
| @SerialName("rank") | ||
| val rank: Int, | ||
| @SerialName("name") | ||
| val name: String, | ||
| @SerialName("percentage") | ||
| val percentage: Float, | ||
| @SerialName("imageUrl") | ||
| val imageUrl: String, | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
kotlinx.serialization null values non-nullable fields deserialization
💡 Result:
Short answer
- By default, kotlinx.serialization throws when JSON contains null for a non-nullable Kotlin property (you get a SerializationException / MissingFieldException / type error).
- To handle/allow nulls for non-nullable fields, use one of:
- Make the property nullable or give it a default value.
- Configure the JSON format to coerce invalid inputs: Json { coerceInputValues = true } — this treats null-for-non-nullable as a “missing” value and will substitute the property's default (if any) or otherwise still fail if no default. [1][2]
- Use explicitNulls and coerceInputValues together for specific behaviors (explicitNulls=false treats absent fields as null for nullable properties). [1][3]
- Implement a custom serializer to handle nulls in whatever custom way you need. [4]
Relevant docs/issues
- explicitNulls and behavior when field is absent vs explicit null. [1]
- coerceInputValues: treats null inputs for non-nullable types (and unknown enum values) as missing so defaults/nulls may be used. [3]
- MissingFieldException for required (non-optional) properties. [2]
If you want, tell me which behavior you want (coerce to default, treat as null, ignore field, or custom mapping) and I’ll show the exact Json config or a custom serializer example.
🏁 Script executed:
# Check the structure of the file and surrounding context
find . -name "UserKeywordsResponseDto.kt" -type f | head -20Repository: imflint/Flint-Android
Length of output: 144
🏁 Script executed:
# Look for API specs or documentation in the repository
find . -name "*api*" -o -name "*spec*" -o -name "*openapi*" | grep -i -E "(yaml|json|txt|md)" | head -20Repository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
# Check for similar DTO files to see how they handle nullable fields
find . -path "*/dto/*" -name "*.kt" -type f | head -20Repository: imflint/Flint-Android
Length of output: 508
🏁 Script executed:
# Look for test files or mock responses that might show API response structure
find . -name "*test*" -o -name "*mock*" | grep -i -E "(keyword|user)" | head -20Repository: imflint/Flint-Android
Length of output: 47
🏁 Script executed:
# Read the actual DTO file to see full structure
cat -n ./app/src/main/java/com/flint/data/dto/user/response/UserKeywordsResponseDto.ktRepository: imflint/Flint-Android
Length of output: 783
🏁 Script executed:
# Check a few other DTO files to see patterns in how they handle nullable fields
head -50 ./app/src/main/java/com/flint/data/dto/base/BaseResponse.kt
head -50 ./app/src/main/java/com/flint/data/dto/auth/response/SignupResponseDto.ktRepository: imflint/Flint-Android
Length of output: 705
🏁 Script executed:
# Look for JSON serialization configuration in the codebase
find . -name "*.kt" -type f | xargs rg -l "Json\s*{|coerceInputValues|explicitNulls" | head -20Repository: imflint/Flint-Android
Length of output: 195
🏁 Script executed:
# Search for any error handling or try-catch related to deserialization
rg -l "SerializationException|MissingFieldException" --type kt | head -10Repository: imflint/Flint-Android
Length of output: 91
🏁 Script executed:
# Search for JSON configuration in the codebase (fixed regex)
rg "Json\s*\{" -A 5 | head -50Repository: imflint/Flint-Android
Length of output: 588
🏁 Script executed:
# Search for coerceInputValues or explicitNulls configuration
rg "coerceInputValues|explicitNulls" | head -20Repository: imflint/Flint-Android
Length of output: 255
🏁 Script executed:
# Look at how DTOs are used - find deserialization code
find . -name "*.kt" -type f | xargs rg -l "fromJson|Json\.decodeFromString|kotlinx\.serialization" | head -20Repository: imflint/Flint-Android
Length of output: 687
🏁 Script executed:
# Check if there are any API responses or test data showing actual structure
find . -name "*.json" -type f | head -20Repository: imflint/Flint-Android
Length of output: 105
🏁 Script executed:
# Look for any other DTOs with nullable fields to understand the pattern
rg ":\s*\w+\?" --type-add 'kt:*.kt' -t kt app/src/main/java/com/flint/data/dto/ | head -30Repository: imflint/Flint-Android
Length of output: 516
🏁 Script executed:
# Verify the NetworkModule.kt configuration more thoroughly
cat -n app/src/main/java/com/flint/core/common/di/NetworkModule.kt | head -100Repository: imflint/Flint-Android
Length of output: 2671
🏁 Script executed:
# Check if UserKeywordsResultDto is used anywhere to see if actual issues occur
rg "UserKeywordsResultDto" -A 3 -B 3Repository: imflint/Flint-Android
Length of output: 2048
기본값(default value) 추가 또는 nullable 필드 사용 권장
프로젝트의 JSON 역직렬화 설정(coerceInputValues = true)은 null 값을 누락된 필드로 처리하도록 구성되어 있습니다. 다만, 이 클래스의 필드들에는 기본값이 없으므로 API 응답에서 null이 반환되는 경우 역직렬화에 실패할 수 있습니다.
SocialVerifyResponseDto와 같이 nullable 필드에 기본값을 지정하거나, 필드를 nullable로 선언하여 안전성을 높이는 것을 권장합니다.
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/data/dto/user/response/UserKeywordsResponseDto.kt`
around lines 12 - 24, The UserKeywordsResultDto data class currently defines
non-nullable properties without defaults, which will fail deserialization if API
returns null; update UserKeywordsResultDto by either assigning sensible default
values (e.g., empty string, 0, 0.0f) to properties color, rank, name,
percentage, imageUrl or make those properties nullable (String?, Int?, Float?)
to match SocialVerifyResponseDto style so coerceInputValues=true can safely
handle missing/null fields during JSON deserialization.
| // TODO: 배치 로직 수정 필요 | ||
| private fun rotateKeywordByRank(keywordList: ImmutableList<UserKeywordResponseModel>): ImmutableList<UserKeywordResponseModel> { | ||
| if (keywordList.size < 2) return keywordList | ||
|
|
||
| val sortedByRank = keywordList.sortedBy { it.rank } | ||
|
|
||
| // rank 1, 2, 3 (상위 3개) → Large | ||
| val topRanks = sortedByRank.take(3) | ||
| // rank 4, 5, 6 (하위 3개) → Small | ||
| val bottomRanks = sortedByRank.drop(3) | ||
|
|
||
| // 번갈아 배치: 1, 4, 2, 5, 3, 6 | ||
| return topRanks | ||
| .zip(bottomRanks) | ||
| .flatMap { (top, bottom) -> listOf(top, bottom) } | ||
| .toPersistentList() | ||
| } |
There was a problem hiding this comment.
rotateKeywordByRank에서 키워드가 손실될 수 있습니다.
zip() 함수는 두 리스트 중 짧은 쪽의 길이만큼만 결합합니다. 예를 들어 키워드가 5개인 경우, topRanks는 3개, bottomRanks는 2개가 되어 zip 결과는 4개만 반환됩니다.
🐛 제안된 수정
private fun rotateKeywordByRank(keywordList: ImmutableList<UserKeywordResponseModel>): ImmutableList<UserKeywordResponseModel> {
if (keywordList.size < 2) return keywordList
val sortedByRank = keywordList.sortedBy { it.rank }
// rank 1, 2, 3 (상위 3개) → Large
val topRanks = sortedByRank.take(3)
// rank 4, 5, 6 (하위 3개) → Small
val bottomRanks = sortedByRank.drop(3)
- // 번갈아 배치: 1, 4, 2, 5, 3, 6
- return topRanks
- .zip(bottomRanks)
- .flatMap { (top, bottom) -> listOf(top, bottom) }
- .toPersistentList()
+ // 번갈아 배치하되 남은 요소도 포함
+ val result = mutableListOf<UserKeywordResponseModel>()
+ val maxSize = maxOf(topRanks.size, bottomRanks.size)
+ for (i in 0 until maxSize) {
+ if (i < topRanks.size) result.add(topRanks[i])
+ if (i < bottomRanks.size) result.add(bottomRanks[i])
+ }
+ return result.toPersistentList()
}📝 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.
| // TODO: 배치 로직 수정 필요 | |
| private fun rotateKeywordByRank(keywordList: ImmutableList<UserKeywordResponseModel>): ImmutableList<UserKeywordResponseModel> { | |
| if (keywordList.size < 2) return keywordList | |
| val sortedByRank = keywordList.sortedBy { it.rank } | |
| // rank 1, 2, 3 (상위 3개) → Large | |
| val topRanks = sortedByRank.take(3) | |
| // rank 4, 5, 6 (하위 3개) → Small | |
| val bottomRanks = sortedByRank.drop(3) | |
| // 번갈아 배치: 1, 4, 2, 5, 3, 6 | |
| return topRanks | |
| .zip(bottomRanks) | |
| .flatMap { (top, bottom) -> listOf(top, bottom) } | |
| .toPersistentList() | |
| } | |
| private fun rotateKeywordByRank(keywordList: ImmutableList<UserKeywordResponseModel>): ImmutableList<UserKeywordResponseModel> { | |
| if (keywordList.size < 2) return keywordList | |
| val sortedByRank = keywordList.sortedBy { it.rank } | |
| // rank 1, 2, 3 (상위 3개) → Large | |
| val topRanks = sortedByRank.take(3) | |
| // rank 4, 5, 6 (하위 3개) → Small | |
| val bottomRanks = sortedByRank.drop(3) | |
| // 번갈아 배치하되 남은 요소도 포함 | |
| val result = mutableListOf<UserKeywordResponseModel>() | |
| val maxSize = maxOf(topRanks.size, bottomRanks.size) | |
| for (i in 0 until maxSize) { | |
| if (i < topRanks.size) result.add(topRanks[i]) | |
| if (i < bottomRanks.size) result.add(bottomRanks[i]) | |
| } | |
| return result.toPersistentList() | |
| } |
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/profile/component/ProfileKeywordSection.kt`
around lines 150 - 166, rotateKeywordByRank currently uses
topRanks.zip(bottomRanks) which drops any remaining items when sizes differ
(losing keywords for odd counts); update the interleaving logic to iterate up to
max(topRanks.size, bottomRanks.size) and append pairs where present (or
alternately append the leftover items after the zip), ensuring all items from
sortedByRank are preserved; change the code around topRanks, bottomRanks and the
final flatMap/toPersistentList return to include remaining entries from the
longer list so no keywords are lost.
There was a problem hiding this comment.
@nahy-512, 네, 알겠습니다! 나중에 수정하시면 됩니다. 👍
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/domain/repository/AuthRepository.kt`:
- Around line 17-21: Replace the synchronous runCatching usage in the suspend
functions signup and socialVerify with the coroutine-friendly suspendRunCatching
(following the UserRepository pattern) so CancellationException and
TimeoutCancellationException are handled correctly; update the calls in
AuthRepository's signup(SignupRequestModel) and
socialVerify(SocialVerifyRequestModel) to wrap api.signup(...).data.toModel()
and api.socialVerify(...).data.toModel() with suspendRunCatching, and ensure the
Result type remains Result<SignupResponseModel> and
Result<SocialVerifyResponseModel>; consider returning or mapping to your
project-wide custom error types later to distinguish network/server/timeout
errors.
In `@app/src/main/java/com/flint/presentation/profile/ProfileViewModel.kt`:
- Around line 41-44: The refreshProfileKeyword function is using a hardcoded
userId (800370427074376635) which breaks real-user lookups; update
refreshProfileKeyword in ProfileViewModel to obtain the userId dynamically
(either accept a userId parameter, use an existing ViewModel property, or fetch
the current user id from the session/userRepository) and replace the hardcoded
value in the call to userRepository.getUserKeywords; ensure
viewModelScope.launch still wraps the call and that any callers of
refreshProfileKeyword are updated to pass/initialize the real user id if you
choose the parameter/constructor injection approach.
- Around line 45-49: The onSuccess logging in ProfileViewModel is printing the
entire result object (Timber.d("onSuccess: $result")); change this to log only
the minimal non-sensitive fields needed (e.g., userId, keywords summary, or a
sanitized flag) inside the onSuccess lambda of the same network callback, avoid
logging full objects or personal preference lists, and ensure Timber.d uses a
concise message like "onSuccess: userId=%s, keywordsCount=%d" or similar so
sensitive data in result is not emitted.
🧹 Nitpick comments (2)
app/src/main/java/com/flint/domain/model/user/UserKeywordResponseModel.kt (1)
19-74: 도메인 모델에 UI용 더미 데이터가 섞여 있습니다.프리뷰/테스트용 더미 데이터는 presentation 또는 test 영역으로 분리하는 편이 계층 순수성을 지키는 데 유리합니다. 필요하다면 debug 전용 소스로 분리해 주세요.
학습 내용에 따라, 도메인 레이어는 UI/샘플 데이터와 분리하는 구조가 권장됩니다.
app/src/main/java/com/flint/presentation/profile/ProfileViewModel.kt (1)
33-36: 초기 Fake 데이터 방출은 실제 로딩 흐름과 충돌할 수 있습니다.출시 코드에서는
UiState.Loading등으로 분리하고, 더미 데이터는 디버그 전용으로만 노출하는 구조를 권장합니다.
| @Composable | ||
| fun FlintLoadingIndicator() { | ||
| Box( | ||
| modifier = | ||
| Modifier | ||
| .fillMaxSize() | ||
| .background(Color.Transparent), | ||
| contentAlignment = Alignment.Center, | ||
| ) { | ||
| CircularProgressIndicator() | ||
| } | ||
| } |
There was a problem hiding this comment.
p3: CircularProgressIndicator를 Box로 감싸신 이유가 있을까요? 👀
There was a problem hiding this comment.
Box로 감싸주지 않으면 위치 지정을 못 해줘서 CircularProgressIndicator가 우측 상단에서 돌아갑니당
| isAllVisible: Boolean = false, | ||
| onAllClick: () -> Unit = {}, |
There was a problem hiding this comment.
p3: 개인적으로는 디폴트 파라미터를 잘 정의하지 않는 편이에요. 해당 파라미터는 넘기지 않아도 괜찮다. 라는 인식을 주는 것 같아서요.
Button이 onClick에 디폴트 파라미터를 사용하지 않는 것처럼, contentDescription의 디폴트 파라미터가 null이 아닌 것처럼,
단순히 개발 편의성만 고려하기보다 해당 파라미터가 해당 컴포넌트에 얼마나 필수적인지가 반영되면 좋겠습니다. 👍
There was a problem hiding this comment.
저도 디폴트 파라미터를 별로 두지 않는 편인데, 이번에 변경사항을 반영하려다 '혹시 나중에도 달라지려나?' 싶은 생각으로 추가한 부분이었습니다. 개발 편의성보다 필수성을 고려하면 좋겠다는 말씀이 공감가서, 해당 부분 수정하도록 하겠습니다
|
|
||
| @Serializable | ||
| data class UserKeywordsResponseDto( | ||
| @SerialName("keywords") |
There was a problem hiding this comment.
p3: @SerialName을 전부 붙여주는 이유는 무엇인가요? JSON의 키 이름이 변경되면 그 때 붙여줘도 되지 않을까요? 🤔
There was a problem hiding this comment.
저도 해당 고민을 했던 적이 있는데, dto의 경우 @SerialName를 모두 붙여주는 것이 명확하다고 생각하고 붙이는 걸 습관화하게 되었습니다.
서버 키가 snake_case처럼 Kotlin 네이밍과 충돌하는 경우도 종종 있는데, 그런 경우 코틀린 프로퍼티 이름은 컨벤션에 맞추고 @SerialName을 따로 붙여주는 식으로도 많이 하는 것 같아요. 그리고 서버의 키가 변경됐을 때 어느 시점에는 @SerialName을 조정해야 하는데, 처음부터 붙여두면 변경 시 어떤 필드의 계약이 바뀌었는지가 어노테이션 diff로 명확히 남아 좋은 것 같습니다!
| accessToken = accessToken, | ||
| refreshToken = refreshToken, | ||
| userId = userId, | ||
| ) |
There was a problem hiding this comment.
p3: SignupRequestModel.toDto()의 경우 SignupRequestModel의 멤버 함수로 둘 수 있을 것 같아요. 확장함수로 구현하신 이유가 있나요? 👀
There was a problem hiding this comment.
@kimjw2003 구현 부분이었어서, 답변 남겨주시면 감사하겠습니다ㅎㅎ
There was a problem hiding this comment.
.toModel과의 통일성을 중요하다 생각해서 확장함수로 진행하였습니다
| private val api: AuthApi, | ||
| ) { | ||
| suspend fun signup(model: SignupRequestModel): Result<SignupResponseModel> = | ||
| runCatching { api.signup(model.toDto()).data.toModel() } |
There was a problem hiding this comment.
p3: 한 줄에 접근 및 메서드 호출이 많아서 읽기 어려울 수 있을 것 같아요. model.toDto()를 dto 등의 변수에 담는다거나, api.signup(model.toDto())를 response 변수에 담거나 하면 가독성이 올라갈 것 같습니다. 👍
|
|
||
| companion object { | ||
| fun getOtts(): List<OttType> = OttType.entries | ||
| fun getOtts(): List<OttType> = entries |
There was a problem hiding this comment.
p3: 필요한 함수일까요? entries를 그대로 사용할 수도 있을 것 같아서요. 👀
There was a problem hiding this comment.
'OTT 목록을 가져온다'라는 의미에서 getOtts라는 함수를 만들었을 때 바로 가져다 쓸 수 있지 않을까 했었는데, 생각해보니 불필요할 것도 같네요ㅎㅎ 수정하겠습니다
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt`:
- Around line 38-54: The current when on uiState leaves the else branch empty so
error/empty states render nothing; add explicit branches for UiState.Error and a
possible UiState.Empty (or use the else branch) to render a minimal error/empty
UI (e.g., an ErrorState or EmptyState composable) that preserves layout (use
Modifier.padding(paddingValues)) and provides a retry action wired to
viewModel::refreshProfileKeyword (or a retry lambda), and update callers of
ProfileScreen to accept and display an error/empty composable so users see a
message and a retry button instead of a blank screen.
♻️ Duplicate comments (1)
app/src/main/java/com/flint/domain/repository/AuthRepository.kt (1)
18-22: 체이닝된 호출의 가독성 개선을 고려해보세요.이전 리뷰에서 언급된 것처럼, 한 줄에 여러 메서드 호출이 체이닝되어 있어 가독성이 떨어질 수 있습니다. 중간 결과를 변수에 할당하면 디버깅과 코드 이해가 쉬워집니다.
♻️ 가독성 개선 제안
- suspend fun signup(model: SignupRequestModel): Result<SignupResponseModel> = - suspendRunCatching { api.signup(model.toDto()).data.toModel() } + suspend fun signup(model: SignupRequestModel): Result<SignupResponseModel> = + suspendRunCatching { + val response = api.signup(model.toDto()) + response.data.toModel() + } - suspend fun socialVerify(model: SocialVerifyRequestModel): Result<SocialVerifyResponseModel> = - suspendRunCatching { api.socialVerify(model.toDto()).data.toModel() } + suspend fun socialVerify(model: SocialVerifyRequestModel): Result<SocialVerifyResponseModel> = + suspendRunCatching { + val response = api.socialVerify(model.toDto()) + response.data.toModel() + }
🧹 Nitpick comments (2)
app/src/main/java/com/flint/presentation/onboarding/OnboardingOttScreen.kt (1)
34-40: ViewModel 연동 시 하드코딩된nickname값 교체 필요.현재
nickname = "user"가 하드코딩되어 있습니다. 주석에 명시된 대로 ViewModel 연동 시 실제 사용자 닉네임을 가져오도록 변경이 필요합니다. PR 설명에 따르면 현재는 동작 확인용이므로 이해하지만, 추후 작업 시 참고 부탁드립니다.app/src/main/java/com/flint/core/common/di/interceptor/TokenInterceptor.kt (1)
11-33:runBlocking로 인한 네트워크 스레드 블로킹 가능성 확인 필요Line 19-22에서 DataStore Flow를
runBlocking으로 읽고 있어, 디스크 I/O가 발생하면 OkHttp 스레드가 블로킹되어 요청 지연이 생길 수 있습니다. 가능한 경우PreferencesManager에 메모리 캐시(예:StateFlow/StateIn)를 두고 여기서는 캐시 값을 읽는 방식으로 전환하거나, 최소한 현재 구현이 충분히 빠른지 확인해 주세요.
| when (val state = uiState) { | ||
| is UiState.Loading -> { | ||
| FlintLoadingIndicator() | ||
| } | ||
|
|
||
| is UiState.Success -> { | ||
| ProfileScreen( | ||
| modifier = Modifier.padding(paddingValues), | ||
| uiState = state.data, | ||
| onRefreshClick = viewModel::refreshProfileKeyword, | ||
| onCollectionItemClick = navigateToCollectionDetail, | ||
| onCollectionMoreClick = navigateToCollectionList, | ||
| ) | ||
| } | ||
|
|
||
| else -> {} | ||
| } |
There was a problem hiding this comment.
에러/빈 상태가 현재는 빈 화면으로 끝납니다.
UiState.Success/Loading 외에는 no‑op이라 오류/빈 상태에서 아무 UI도 렌더링되지 않습니다. 최소한 빈/오류 상태 UI 또는 재시도 동선을 표시해 주세요.
🛠️ 빈 상태라도 레이아웃을 유지하는 예시
- else -> {}
+ else -> {
+ ProfileScreen(
+ modifier = Modifier.padding(paddingValues),
+ uiState = ProfileUiState.Empty,
+ onRefreshClick = viewModel::refreshProfileKeyword,
+ onCollectionItemClick = navigateToCollectionDetail,
+ onCollectionMoreClick = navigateToCollectionList,
+ )
+ }🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt` around
lines 38 - 54, The current when on uiState leaves the else branch empty so
error/empty states render nothing; add explicit branches for UiState.Error and a
possible UiState.Empty (or use the else branch) to render a minimal error/empty
UI (e.g., an ErrorState or EmptyState composable) that preserves layout (use
Modifier.padding(paddingValues)) and provides a retry action wired to
viewModel::refreshProfileKeyword (or a retry lambda), and update callers of
ProfileScreen to accept and display an error/empty composable so users see a
message and a retry button instead of a blank screen.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/domain/repository/UserRepository.kt`:
- Around line 14-17: The getUserKeywords implementation currently maps
apiService.getUserKeywords(userId).data.keywords without checking
BaseResponse.status, so responses with HTTP 200 but status != 0 are treated as
success; update suspend fun getUserKeywords (the suspendRunCatching block around
apiService.getUserKeywords) to inspect the returned BaseResponse.status and if
status indicates failure (e.g., status != 0) convert to Result.failure by
throwing or returning a specific exception (e.g., ApiException with the
status/message) before mapping data.keywords; alternatively implement the same
status check in a shared HTTP interceptor/response wrapper so all repository
methods (including getUserKeywords) get failure semantics for non-zero status.
♻️ Duplicate comments (1)
app/src/main/java/com/flint/presentation/profile/ProfileViewModel.kt (1)
40-43: 하드코딩된 userId 제거 필요실 사용자 기준 동작을 위해 userId를 주입/전달받는 방식으로 변경이 필요합니다.
💡 제안 변경
- fun refreshProfileKeyword() { + fun refreshProfileKeyword(userId: String) { viewModelScope.launch { - userRepository.getUserKeywords(userId = "800370427074376635").fold( // TODO: 임시 userId + userRepository.getUserKeywords(userId = userId).fold(
| suspend fun getUserKeywords(userId: String): Result<List<UserKeywordResponseModel>> = | ||
| suspendRunCatching { | ||
| apiService.getUserKeywords(userId).data.keywords.map { | ||
| it.toModel() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# BaseResponse와 관련 DTO 정의 찾기
echo "=== BaseResponse 검색 ==="
rg -t kotlin 'data class BaseResponse|class BaseResponse' -C 3
echo -e "\n=== UserKeywords 관련 DTO 검색 ==="
rg -t kotlin 'UserKeywordResponseModel|UserKeywordsResponseDto|UserKeywordsResultDto|data class.*Keyword' -C 2
echo -e "\n=== UserApi getUserKeywords 검색 ==="
rg -t kotlin 'fun getUserKeywords|suspend fun getUserKeywords' -C 3
echo -e "\n=== UserRepository의 전체 함수 검색 ==="
fd UserRepository.kt -x cat -n {}Repository: imflint/Flint-Android
Length of output: 16382
🏁 Script executed:
#!/bin/bash
# BaseResponse 전체 내용 확인
echo "=== BaseResponse.kt 전체 ==="
cat app/src/main/java/com/flint/data/dto/base/BaseResponse.kt
echo -e "\n=== UserKeywordsResponseDto 전체 ==="
cat app/src/main/java/com/flint/data/dto/user/response/UserKeywordsResponseDto.kt
echo -e "\n=== 다른 Repository의 에러 처리 패턴 ==="
rg -t kotlin 'suspendRunCatching|BaseResponse' app/src/main/java/com/flint/domain/repository/ -B2 -A5 | head -60
echo -e "\n=== suspendRunCatching 정의 ==="
rg -t kotlin 'fun suspendRunCatching|suspend fun.*runCatching' -C3Repository: imflint/Flint-Android
Length of output: 4248
🏁 Script executed:
#!/bin/bash
# suspendRunCatching 정의 찾기
echo "=== suspendRunCatching 정의 ==="
rg -t kotlin 'fun suspendRunCatching|suspend fun.*RunCatching' app/src/main/java/com/flint/core -B2 -A10
echo -e "\n=== suspendRunCatching 사용 패턴 (다른 API 호출들) ==="
rg -t kotlin 'suspendRunCatching.*api\.' app/src/main/java/com/flint/domain/repository -B1 -A3 | head -80
echo -e "\n=== status 필드 체크 패턴 ==="
rg -t kotlin 'status.*==|if.*status' app/src/main/java/com/flint -C2 | head -50Repository: imflint/Flint-Android
Length of output: 2170
🏁 Script executed:
#!/bin/bash
# 최종 확인: HTTP 상태 코드와 status 필드의 관계, 그리고 실제 에러 응답 처리
echo "=== suspendRunCatching 전체 코드 ==="
cat app/src/main/java/com/flint/core/common/util/SuspendRunCatching.kt
echo -e "\n=== UserApi 레트로핏 호출 구조 ==="
cat app/src/main/java/com/flint/data/api/UserApi.kt
echo -e "\n=== 코드베이스 내 status 필드 명시적 체크 ==="
rg -t kotlin '\.status\s*[!=]' app/src/main/java/com/flint --max-count=10Repository: imflint/Flint-Android
Length of output: 1231
BaseResponse의 status 필드를 검증하고 API 실패 응답 처리 추가 필요
data.keywords는 non-nullable이므로 null 접근은 문제가 아니지만, API가 HTTP 200으로 응답하면서 status 필드가 실패를 나타내는 경우(예: status ≠ 0) suspendRunCatching은 예외가 발생하지 않아 실패 응답을 성공으로 처리하게 됩니다. API 응답의 status 필드를 명시적으로 검증하고 실패 상태를 Result.failure로 변환하거나, 공통 HTTP 인터셉터에서 status 검증을 처리해 주세요.
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/domain/repository/UserRepository.kt` around lines
14 - 17, The getUserKeywords implementation currently maps
apiService.getUserKeywords(userId).data.keywords without checking
BaseResponse.status, so responses with HTTP 200 but status != 0 are treated as
success; update suspend fun getUserKeywords (the suspendRunCatching block around
apiService.getUserKeywords) to inspect the returned BaseResponse.status and if
status indicates failure (e.g., status != 0) convert to Result.failure by
throwing or returning a specific exception (e.g., ApiException with the
status/message) before mapping data.keywords; alternatively implement the same
status check in a shared HTTP interceptor/response wrapper so all repository
methods (including getUserKeywords) get failure semantics for non-zero status.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt (2)
46-64:navigateToAddContent가 사용되지 않습니다.
navigateToAddContent파라미터가 선언되었지만CollectionCreateScreen에 전달되지 않아, Line 261의 "작품 추가하기" 버튼이 동작하지 않습니다.🛠️ 수정 제안
CollectionCreateScreen에onAddContentClick파라미터를 추가하고 연결하세요:fun CollectionCreateRoute( paddingValues: PaddingValues, navigateToAddContent: () -> Unit, ) { // ... CollectionCreateScreen( thumbnailImageUrl = "", contentList = contentList.toImmutableList(), onRemoveContent = { contentList.remove(it) }, onBackClick = {}, onGalleryClick = {}, onCoverDeleteClick = {}, + onAddContentClick = navigateToAddContent, ) }그리고
CollectionCreateScreen함수 시그니처와 버튼에도 연결이 필요합니다.
254-270: "작품 추가하기" 버튼의 onClick이 빈 람다입니다.Line 261의
onClick = {}가 빈 람다로 되어 있어 버튼을 눌러도 아무 동작이 없습니다.navigateToAddContent와 연결되어야 합니다.🛠️ 수정 제안
CollectionCreateScreen에onAddContentClick파라미터를 추가하세요:fun CollectionCreateScreen( thumbnailImageUrl: String, contentList: ImmutableList<CollectionContentUiModel>, onRemoveContent: (CollectionContentUiModel) -> Unit, onBackClick: () -> Unit, onGalleryClick: () -> Unit, onCoverDeleteClick: () -> Unit, + onAddContentClick: () -> Unit, ) {그리고 버튼에 연결:
FlintIconButton( text = "작품 추가하기", iconRes = R.drawable.ic_plus, state = FlintButtonState.ColorOutline, - onClick = {}, + onClick = onAddContentClick, modifier = ... )
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt`:
- Around line 39-45: Remove the duplicate local data class definition of
CollectionContentUiModel in AddContentScreen.kt and update the file to import
and use the single canonical model
com.flint.presentation.collectioncreate.model.CollectionContentUiModel instead;
delete the local data class block (the data class named CollectionContentUiModel
with contentId, imageUrl, title, director, createdYear) and add the appropriate
import, then ensure any usages in AddContentScreen (references to
CollectionContentUiModel) remain unchanged and compile against the imported
model.
- Around line 29-37: AddContentRoute is not wiring its parameters into the
screen: pass the incoming paddingValues into AddContentScreen and replace the
empty onBackClick lambda with the provided navigateToCollectionCreate callback
so the back navigation works; update the AddContentRoute function signature
usage to call AddContentScreen(paddingValues = paddingValues, onBackClick =
navigateToCollectionCreate) so both paddingValues and navigateToCollectionCreate
are actually used.
In `@app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt`:
- Around line 39-41: In the UiState.Loading branch the FlintLoadingIndicator is
rendered without applying the provided paddingValues, causing overlap with the
bottom navigation; wrap FlintLoadingIndicator in a composable that applies
padding (e.g., Box(modifier = Modifier.padding(paddingValues))) or pass padding
to the indicator itself, and add the required import for
androidx.compose.foundation.layout.Box; update the UiState.Loading case to use
Box and apply paddingValues so the loading UI respects system/inset paddings.
♻️ Duplicate comments (1)
app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt (1)
53-53: 에러/빈 상태가 현재는 빈 화면으로 끝납니다.
UiState.Error등의 상태에서 아무 UI도 렌더링되지 않습니다. 최소한 에러 메시지나 재시도 버튼을 표시하는 것이 좋습니다.
🧹 Nitpick comments (7)
app/src/main/java/com/flint/core/designsystem/component/image/SelectedContentItem.kt (1)
38-47: 접근성 개선 필요: contentDescription이 명확하지 않습니다.
contentDescription = "x"는 스크린 리더 사용자에게 의미 있는 정보를 제공하지 않습니다. "선택 해제" 또는 "항목 제거"와 같이 아이콘의 기능을 설명하는 문구로 변경해 주세요.♻️ 제안하는 수정 사항
Icon( imageVector = ImageVector.vectorResource(id = R.drawable.ic_deselect), - contentDescription = "x", + contentDescription = "선택 해제", tint = Color.Unspecified, modifier = Modifier .align(Alignment.TopEnd) .size(48.dp) .clickable { onRemoveClick() }, )app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt (1)
50-50:var대신val사용 권장
mutableStateListOf는 이미 mutable한 컬렉션이므로 변수 자체를var로 선언할 필요가 없습니다.val로 변경하면 의도치 않은 재할당을 방지할 수 있습니다.♻️ 제안
- var selectedContents = remember { mutableStateListOf<CollectionContentUiModel>() } + val selectedContents = remember { mutableStateListOf<CollectionContentUiModel>() }app/src/main/java/com/flint/presentation/savedcontent/SavedContentListScreen.kt (1)
6-13:paddingValues파라미터가 사용되지 않습니다.
SavedContentListRoute에서paddingValues를 받지만SavedContentListScreen에 전달하거나 사용하지 않습니다. 향후 구현 시 padding 적용이 필요할 것으로 보입니다.♻️ 향후 구현을 위한 제안
`@Composable` -fun SavedContentListRoute(paddingValues: PaddingValues) { - SavedContentListScreen() +fun SavedContentListRoute(paddingValues: PaddingValues) { + SavedContentListScreen( + modifier = Modifier.padding(paddingValues), + ) } `@Composable` -fun SavedContentListScreen() { +fun SavedContentListScreen( + modifier: Modifier = Modifier, +) { + // TODO: Implement UI }app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSection.kt (1)
88-110: Preview 함수명 개선 제안Preview 함수명
CollectionCreateContentSectionPreview가 Line 74의CollectionCreateContentSelect.kt에 있는CollectionCreateContentSectionPreview와 동일합니다. 파일 간 명확한 구분을 위해 이 파일의 preview 함수명을 좀 더 구체적으로 변경하는 것을 권장합니다.제안된 변경
-private fun CollectionCreateContentSectionPreview() { +private fun CollectionCreateContentSectionWithImagePreview() {app/src/main/java/com/flint/presentation/collectioncreate/component/CollectionCreateContentSelect.kt (1)
72-86: Preview 함수명이 프리뷰 대상과 불일치합니다.
CollectionCreateContentSectionPreview라는 이름이지만 실제로는CollectionCreateContentSelect를 프리뷰하고 있습니다. 명확성을 위해CollectionCreateContentSelectPreview로 변경하는 것이 좋겠습니다.제안된 변경
-private fun CollectionCreateContentSectionPreview() { +private fun CollectionCreateContentSelectPreview() {app/src/main/java/com/flint/presentation/collectioncreate/CollectionCreateScreen.kt (1)
306-319: 프리뷰에도 누락된 파라미터 추가가 필요합니다.위에서 제안한
onAddContentClick파라미터를 추가하면 프리뷰에도 해당 파라미터를 전달해야 합니다.♻️ 수정 제안
`@Preview` `@Composable` fun CollectionCreateScreenPreview() { FlintTheme { CollectionCreateScreen( thumbnailImageUrl = "", contentList = CollectionContentUiModel.dummyContentList, onRemoveContent = {}, onBackClick = {}, onGalleryClick = {}, onCoverDeleteClick = {}, + onAddContentClick = {}, ) } }app/src/main/java/com/flint/presentation/savedcontent/SavedContentListItem.kt (1)
202-221: Preview 함수명이 실제 미리보기 대상과 불일치합니다.
SavedContentListItemBookmarkPreview라는 이름이지만 실제로는 전체SavedContentListItem을 미리보기하고 있습니다. 명확성을 위해SavedContentListItemPreview로 변경하는 것을 권장합니다.♻️ 제안된 수정
`@Preview` `@Composable` -private fun SavedContentListItemBookmarkPreview() { +private fun SavedContentListItemPreview() { FlintTheme {
| @Composable | ||
| fun AddFilmRoute( | ||
| fun AddContentRoute( | ||
| paddingValues: PaddingValues, | ||
| navigateToCollectionCreate: () -> Unit, | ||
| ) { | ||
| AddFilmScreen( | ||
| AddContentScreen( | ||
| onBackClick = {}, | ||
| ) | ||
| } |
There was a problem hiding this comment.
사용되지 않는 파라미터 및 미연결된 콜백
AddContentRoute에서 다음 이슈가 있습니다:
paddingValues가AddContentScreen에 전달되지 않음navigateToCollectionCreate가 사용되지 않음onBackClick이 빈 람다{}로 전달됨 - 실제 뒤로가기 동작이 작동하지 않음
🔧 제안
`@Composable`
fun AddContentRoute(
paddingValues: PaddingValues,
navigateToCollectionCreate: () -> Unit,
+ navigateUp: () -> Unit,
) {
AddContentScreen(
- onBackClick = {},
+ onBackClick = navigateUp,
+ modifier = Modifier.padding(paddingValues),
)
}📝 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.
| @Composable | |
| fun AddFilmRoute( | |
| fun AddContentRoute( | |
| paddingValues: PaddingValues, | |
| navigateToCollectionCreate: () -> Unit, | |
| ) { | |
| AddFilmScreen( | |
| AddContentScreen( | |
| onBackClick = {}, | |
| ) | |
| } | |
| `@Composable` | |
| fun AddContentRoute( | |
| paddingValues: PaddingValues, | |
| navigateToCollectionCreate: () -> Unit, | |
| navigateUp: () -> Unit, | |
| ) { | |
| AddContentScreen( | |
| onBackClick = navigateUp, | |
| modifier = Modifier.padding(paddingValues), | |
| ) | |
| } |
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt`
around lines 29 - 37, AddContentRoute is not wiring its parameters into the
screen: pass the incoming paddingValues into AddContentScreen and replace the
empty onBackClick lambda with the provided navigateToCollectionCreate callback
so the back navigation works; update the AddContentRoute function signature
usage to call AddContentScreen(paddingValues = paddingValues, onBackClick =
navigateToCollectionCreate) so both paddingValues and navigateToCollectionCreate
are actually used.
| data class CollectionContentUiModel( | ||
| val contentId: Long, | ||
| val imageUrl: String, | ||
| val title: String, | ||
| val director: String, | ||
| val createdYear: String, | ||
| ) |
There was a problem hiding this comment.
중복된 data class 정의 제거 필요
CollectionContentUiModel이 이 파일에서 중복 정의되어 있습니다. 이미 com.flint.presentation.collectioncreate.model.CollectionContentUiModel에 동일한 클래스가 존재합니다. 이로 인해 import 충돌 또는 shadowing 문제가 발생할 수 있습니다.
🔧 제안: 중복 정의 제거 및 import 추가
+import com.flint.presentation.collectioncreate.model.CollectionContentUiModel
import com.flint.presentation.collectioncreate.component.CollectionCreateContentSelect
-data class CollectionContentUiModel(
- val contentId: Long,
- val imageUrl: String,
- val title: String,
- val director: String,
- val createdYear: String,
-)🤖 Prompt for AI Agents
In
`@app/src/main/java/com/flint/presentation/collectioncreate/AddContentScreen.kt`
around lines 39 - 45, Remove the duplicate local data class definition of
CollectionContentUiModel in AddContentScreen.kt and update the file to import
and use the single canonical model
com.flint.presentation.collectioncreate.model.CollectionContentUiModel instead;
delete the local data class block (the data class named CollectionContentUiModel
with contentId, imageUrl, title, director, createdYear) and add the appropriate
import, then ensure any usages in AddContentScreen (references to
CollectionContentUiModel) remain unchanged and compile against the imported
model.
| is UiState.Loading -> { | ||
| FlintLoadingIndicator() | ||
| } |
There was a problem hiding this comment.
Loading 상태에서 paddingValues가 적용되지 않습니다.
FlintLoadingIndicator에 paddingValues가 적용되지 않아, 로딩 중에 하단 네비게이션 바와 겹칠 수 있습니다.
🛠️ 수정 제안
is UiState.Loading -> {
- FlintLoadingIndicator()
+ Box(modifier = Modifier.padding(paddingValues)) {
+ FlintLoadingIndicator()
+ }
}Box 임포트 추가 필요:
import androidx.compose.foundation.layout.Box🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/profile/ProfileScreen.kt` around
lines 39 - 41, In the UiState.Loading branch the FlintLoadingIndicator is
rendered without applying the provided paddingValues, causing overlap with the
bottom navigation; wrap FlintLoadingIndicator in a composable that applies
padding (e.g., Box(modifier = Modifier.padding(paddingValues))) or pass padding
to the indicator itself, and add the required import for
androidx.compose.foundation.layout.Box; update the UiState.Loading case to use
Box and apply paddingValues so the loading UI respects system/inset paddings.
📮 관련 이슈
📌 작업 내용
📸 스크린샷
Screen_Recording_20260119-090842_Flint.1.mp4
Screen_Recording_20260119-092703_Flint.mp4
😅 미구현
🫛 To. 리뷰어
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.