Conversation
| super.onViewCreated(view, savedInstanceState) | ||
|
|
||
| binding.composeView.apply { | ||
| setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) |
There was a problem hiding this comment.
이런 기능이 있는진 몰랐네요
OnboardingActivity 에서 추가하지 않은 건 Activity 이기 때문일까요 ??
There was a problem hiding this comment.
오 정확합니다!! 온보딩 때는 액티비티라서 안해줬습니당
Fragment의 ComposeView는 뷰 생명주기에 맞춰 정리해줘야 해서 DisposeOnViewTreeLifecycleDestroyed를 Fragment에서 설정했어요
| imageRes = R.drawable.img_home_hospital, | ||
| nameRes = R.string.home_button_hospital | ||
| ), | ||
| PROTECT_PART( | ||
| imageRes = R.drawable.img_home_protect_part, | ||
| nameRes = R.string.home_button_protect_part | ||
| ), | ||
| VOLUNTEER( | ||
| imageRes = R.drawable.img_home_volunteer, |
There was a problem hiding this comment.
R.drawable.img_home_hospital 랑 R.drawable.img_home_hospital 혹시 drawable 에 추가되있나요 ??
There was a problem hiding this comment.
엥 이상하네요 확인해봤는데 없는 것 같아요..
근데 위에 pr 에 추가해둔 영상에 보면 분병 추가했던 것 같은데 왜 사라졌지...
다시 추가해두겠습니다!
| ); | ||
|
|
||
| companion object { | ||
| val ALL_ITEMS = entries |
There was a problem hiding this comment.
혹시 이렇게 entries 를 할당한 이유가 있으실까요 ??
단순 궁금입니다!
There was a problem hiding this comment.
과거의 제가 가공을 위해 습관적으로 만들어 둔 것 같은데,
여기에는 굳이 이렇게 할 필요가 없을것 같아 삭제하겠습니다!🤣
| data object LoadHomeData : HomeUiEvent() | ||
| data object RefreshData : HomeUiEvent() |
There was a problem hiding this comment.
이 두 부분은 구현이 아직 안 된걸까요??
There was a problem hiding this comment.
RefershData는 추후 API를 연결하거나 사용자가 새로고침을 의도하고 화면을 끌어당겼을 때 기능을 추가하는 등 나중을 위해 만들어만 두었고 LoadHomeData는 추가해두었습니다!
app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
Show resolved
Hide resolved
Walkthrough홈 화면을 XML/뷰 기반에서 Jetpack Compose로 전면 전환하고 HomeViewModel에 UI 상태·이벤트·이펙트 아키텍처를 도입. 다수의 Compose 컴포넌트, 리소스(아이콘/문자열), 유틸·모델·버전 카탈로그 및 페이저 의존성이 추가됨. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant HomeFragment
participant HomeViewModel
participant Repo as HomeRepository
participant HomeScreen
HomeFragment->>HomeViewModel: uiState/uiEffect 구독 시작
HomeViewModel->>Repo: getHome() 호출
Repo-->>HomeViewModel: Result<HomeData>
HomeViewModel->>HomeFragment: uiState 업데이트 (homeData, homeReportData, loadState)
HomeFragment->>HomeScreen: HomeScreen(uiState, 콜백들)
User->>HomeScreen: 상호작용(클릭/스크롤/배너)
HomeScreen->>HomeViewModel: HomeUiEvent 전송
HomeViewModel->>HomeFragment: HomeUiEffect 발행 (Navigate / ShowDialog / OpenWebLink / ShowToast)
HomeFragment-->>User: 내비게이션·다이얼로그·브라우저·토스트 실행
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ 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. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
# Conflicts: # app/src/main/res/values/strings.xml
There was a problem hiding this comment.
Actionable comments posted: 13
♻️ Duplicate comments (2)
app/src/main/java/com/example/findu/presentation/type/HomeButtonType.kt (1)
11-26: 관련 drawable 존재 여부 재확인 필요이전에 리소스 누락 이슈가 언급되었습니다. 각 아이콘이 실제 res/drawable*에 존재하는지 한번 더 체크 부탁드려요. 누락 시 런타임에서 Resources$NotFoundException이 발생합니다.
다음 스크립트로 리소스 존재 여부를 점검할 수 있어요:
#!/bin/bash # drawable 리소스 존재 확인 targets=( "img_home_protect_center" "img_home_hospital" "img_home_protect_part" "img_home_volunteer" ) for t in "${targets[@]}"; do echo "== Checking $t ==" rg -nP "(?s)<(bitmap|vector|drawable).+?name=\"$t\"" -g "app/src/main/res/**/drawable*/*.xml" -n -C0 || \ fd -HI "res/**/drawable*/$t.*" app/src/main | sed 's/^/found: /' || echo "MISSING: $t" doneapp/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt (1)
51-53: LGTM – ComposeView 생명주기 정리 전략 사용이 적절합니다Fragment의 View 생명주기에 맞춘
DisposeOnViewTreeLifecycleDestroyed적용 좋아요. 메모리 릭 리스크를 줄입니다.
🧹 Nitpick comments (69)
app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt (1)
3-3: 도메인 모델이 Android 리소스/어노테이션에 직접 의존하고 있어 계층 결합도가 높습니다현재 enum이 @ColorRes, R.color를 참조하고 있어 domain(pure) 계층이 Android UI 자원에 묶입니다. 유지보수/테스트/멀티플랫폼 관점에서 불리할 수 있습니다. 가벼운 제안입니다:
- Domain에는 상태 의미(텍스트/키 등)만 두고
- Presentation(UI) 계층에서 SearchStatus → UI 색상 리소스 매핑을 제공(Mapper/extension)
- 예: data class SearchStatusUi(@ColorRes contentColorRes: Int, @ColorRes containerColorRes: Int)와 SearchStatus.toUi()
현재 PR 범위에서 급한 건 아니나, 다음 단계에서 분리 고려 부탁드립니다.
app/src/main/res/drawable/ic_reported_card_location_18.xml (1)
6-8: 하드코딩 컬러(#585858) 대신 테마/틴트 기반으로 전환 고려다크/라이트 테마나 동적 컬러 대응 시, 고정된 회색 값은 대비 이슈가 생길 수 있어요. Vector 자체에는 기본 fillColor만 두고, 실제 색상은 Compose 레벨에서 tint(ColorFilter)나 AppCompat tint를 통해 주입하는 쪽이 유연합니다. 색 리소스(@color/...) 참조로 바꾸는 것도 방법입니다.
- 이 아이콘 사용처(HomeReportedAnimalCard 등)에서 실제로 tint를 적용하고 있는지 한 번 확인 부탁드려요.
- 다크 모드에서 happenLocation 텍스트와 함께 배치했을 때 명도 대비가 충분한지 UI 확인 부탁드립니다.
app/src/main/res/drawable/ic_reported_card_gender_male_20.xml (1)
6-8: ic_reported_card_gender_male_20 사용 확인 필요
현재.kt/.xml에서 해당 아이콘 참조가 검색되지 않았습니다.
- Java 파일이나 다른 레이아웃·코드에서도 사용 여부를 확인해주세요.
- 사용 중이라면, 하드코딩된
android:fillColor="#000000"대신 @color 리소스 참조 혹은 코드에서 tint 적용을 권장합니다.- 사용되지 않는 리소스라면 제거를 검토해주세요.
app/src/main/java/com/example/findu/presentation/type/view/LoadState.kt (1)
3-8: 에러/성공 메타데이터가 필요하면 sealed 계층으로 확장 고려추후 오류 원인(메시지/Throwable)이나 성공 시 메타(갱신 시각 등)를 UI에 노출해야 한다면, enum 대신 sealed interface/class로 확장성을 확보하는 것도 방법입니다.
예시:
sealed interface LoadState { data object Idle : LoadState data object Loading : LoadState data object Success : LoadState data class Error(val cause: Throwable? = null, val message: String? = null) : LoadState }UI 상태 객체(HomeUiState)에 이미 데이터 필드가 별도로 있다면 현 구조 유지도 충분합니다.
app/src/main/res/drawable/ic_arrow_top_white_24.xml (1)
6-8: 완전한 흰색(#ffffff) 고정은 테마 전환 시 대비 이슈 가능이 아이콘이 배경(버튼 원형 배경 등) 위에 올라갈 때 라이트/다크 테마 모두에서 충분한 대비가 나는지 확인 부탁드립니다. 필요 시:
- 코드에서 tint를 통해 동적으로 색상을 주입하거나,
- @color 리소스(예: onPrimary, onSurfaceVariant 등)로 참조
하는 방식을 추천드립니다.가능하다면 실제 HomeScrollToTopButton 배경 위에서 라이트/다크 미리보기로 대비를 확인해 주세요.
app/src/main/java/com/example/findu/presentation/util/extension/IntExt.kt (1)
7-7: 성능/확장성 소소한 개선 포인트
- NumberFormat 인스턴스 생성 비용이 있는 편이라, 리스트 대량 렌더링 등 잦은 호출 상황에서는 ThreadLocal 캐싱을 고려해볼 수 있어요. (NumberFormat은 스레드-세이프하지 않음에 유의)
- 이름은 'toStringWithComma'보다 로컬라이즈 의미가 드러나는 'toKoreanNumberString' 또는 'toGroupedString' 쪽이 더 명확할 수 있습니다. (선호도에 따라 닛픽)
- 카운트 값이 Int 범위를 넘을 가능성이 있다면 Long에 대한 오버로드도 함께 두면 안전합니다.
예시 구현(참고용):
private val koreanNumberFormatterTL = ThreadLocal.withInitial { NumberFormat.getNumberInstance(Locale.KOREA) } fun Int.toKoreanNumberString(): String = koreanNumberFormatterTL.get().format(this) fun Long.toKoreanNumberString(): String = koreanNumberFormatterTL.get().format(this)주의: 캐싱을 도입해도 멀티스레드 안전성은 ThreadLocal로 확보해야 합니다. Compose 메인 스레드 사용만이라면 현재 방식도 충분히 실용적입니다.
app/src/main/res/drawable/ic_reported_card_center_18.xml (1)
6-8: 하드코딩된 색상 대신 테마/틴트 활용 고려android:fillColor="#585858"는 다크모드/테마 변경 시 부자연스러울 수 있어요. Compose 사용 시 Icon(..., tint=...) 또는 Painter에 ColorFilter를 주는 방식으로 색상을 교체할 수 있으니, 벡터에는 기본 단색(또는 프로젝트 팔레트 컬러 리소스)만 유지하고 호출부에서 틴트로 일관되게 제어하는 것을 권장합니다.
app/src/main/res/drawable/ic_home_info_title_24.xml (1)
6-8: 색상 테마 대응성 향상 제안android:fillColor="#111111"는 테마와 동기화되지 않으므로, 호출부(Icon 등)에서 tint로 제어하거나 @color/... 리소스 참조로 치환해 다크모드 대응성을 높이는 것을 추천합니다.
app/src/main/java/com/example/findu/presentation/type/HomeReportDurationType.kt (1)
3-7: 레이블을 하드코딩 문자열 대신 문자열 리소스로 전환 제안현재 label: String에 "7일/3개월/1년"이 직접 들어가 있어 다국어/접근성·테마 대응이 어려워요. @stringres를 들고 Compose에서 stringResource(...)로 사용하는 쪽이 안전합니다. 동의하시면 관련 string 리소스 추가까지 정리해드릴게요.
가능한 방향 예:
- enum에 @stringres val labelRes: Int 보관
- UI에서 stringResource(type.labelRes) 사용
app/src/main/java/com/example/findu/domain/model/HomeData.kt (2)
33-35: HomeReportData의 레이어 적합성 점검HomeReportData는 뷰 표현을 위한 집계/정렬 성격이 강해 보입니다. 도메인 모델보다는 presentation 계층(혹은 별도 mapper DTO)에 두고, 도메인에는 원자료(HomeData)와 순수 비즈니스 개념만 남기는 것을 추천합니다. 레이어 경계가 명확해지면 테스트/유지보수성이 좋아집니다.
37-42: ReportDataType의 label 하드코딩 및 네이밍 일관성
- label에 "구조/보호중/입양/신고"가 직접 박혀 있어 i18n에 취약합니다. @stringres 보관 + UI에서 stringResource(...) 매핑을 권장합니다.
- presentation의 AnimalStateType은 PROTECT, 도메인 ReportDataType은 PROTECTION으로 달라 약간의 인지부하가 있습니다. 동일한 어휘(예: PROTECT)로 맞추면 코드 가독성이 좋아집니다.
네이밍 혼선을 줄이기 위해 두 enum 간 매핑/사용부에서 혼용되지 않는지 한 번 확인 부탁드립니다.
app/src/main/java/com/example/findu/presentation/type/AnimalStateType.kt (2)
27-35: fromTag 매핑 중복 제거 및 가독성 개선 제안state 문자열을 이미 보유하고 있으므로, when 분기 대신 enum 컬렉션 탐색으로 단순화할 수 있습니다.
아래처럼 변경 가능:
- fun fromTag(tag: String): AnimalStateType = - when (tag) { - "보호중" -> PROTECT - "실종신고" -> MISSING - "목격신고" -> FIND - else -> PROTECT - } + fun fromTag(tag: String): AnimalStateType = + entries.firstOrNull { it.state == tag } ?: PROTECT참고: Kotlin 1.9+의 entries 사용이 어려우면 values()로 대체 가능합니다.
27-35: 기본값(PROTECT) 폴백은 이슈 은닉 가능 — 최소 로깅 권장알 수 없는 태그가 들어온 경우에도 PROTECT로 묻히면 데이터 품질 이슈를 놓칠 수 있어요. 폴백 시 로그를 남기거나, 필요하면 UNKNOWN 타입을 추가하는 것도 고려해 주세요.
app/src/main/java/com/example/findu/presentation/type/HomeBannerType.kt (2)
6-9: page 필드, 중복 상태 가능 — 단일 소스 유지 권장page 값은 enum 순서나 PagerState.currentPage(또는 UI에서 인덱스)로 쉽게 도출되는 정보라 중복 상태가 됩니다. 유지보수 시 순서가 바뀌면 값 불일치 가능성이 있어요. 사용처가 많지 않다면 제거하거나, 실제 네비게이션 목적(예: route)으로 의미를 명확화하는 쪽을 권장합니다.
6-22: 접근성(스크린리더)용 설명 리소스 매핑 추가 제안배너가 이미지 위주라면 contentDescription이 필요합니다. enum에 생성자 파라미터를 추가하면 바이너리 호환이 깨질 수 있어, 우선 확장 함수로 매핑을 두는 방향을 추천합니다.
아래처럼 별도 확장 함수를 추가해 사용처에서 쉽게 Semantics를 채울 수 있어요:
// e.g. in the same package (new file OK) import androidx.annotation.StringRes import com.example.findu.R @StringRes fun HomeBannerType.contentDescRes(): Int = when (this) { HomeBannerType.ADOPT -> R.string.home_banner_adopt HomeBannerType.VOLUNTEER -> R.string.home_banner_volunteer HomeBannerType.REPORT -> R.string.home_banner_report }app/src/main/java/com/example/findu/presentation/util/Constraints.kt (2)
4-6: 빈 URL 상수는 런타임 이슈 유발 가능PRIVACY_POLICY_URL, REPORT_URL, ASK_URL이 빈 문자열이면 클릭 시 잘못된 인텐트/웹뷰 로딩 에러가 날 수 있어요. 최소한 사용 전에 isNotBlank 검사 또는 null 허용 타입으로 전환+가드 처리 권장합니다.
가능하면 실제 값이 준비될 때까지 버튼/항목을 비활성화하거나 숨기는 UX 처리를 해주세요.
1-9: 파일/객체 네이밍 정합성파일명이 Constraints인데 내용은 URL 상수입니다. WebViewUrl.kt 또는 Constants.kt 등 도메인에 맞는 이름으로 정리하면 검색성과 가독성이 좋아집니다. 네이밍 피드백 환영하신다고 하셔서 제안드립니다.
app/src/main/res/values/strings.xml (1)
433-433: 용어 표기 일관화: ‘동물 병원’ ↔ ‘동물병원’동일 리소스 파일 내에서 ‘동물병원’(무띄어쓰기) 표기가 이미 사용됩니다. 버튼 레이블도 일관되게 ‘동물병원’으로 맞추는 것을 권장합니다.
- <string name="home_button_hospital">동물 병원</string> + <string name="home_button_hospital">동물병원</string>app/src/main/java/com/example/findu/presentation/type/HomeButtonType.kt (2)
7-10: nameRes → labelRes 등 의미가 더 선명한 네이밍 고려nameRes는 다소 포괄적입니다. UI 라벨임이 명확하도록 labelRes 같은 이름을 제안드립니다. 필수 변경은 아니니 취향대로 선택하시면 됩니다.
11-26: 표시 순서가 중요하다면 별도 리스트로 고정 권장UI 노출 순서가 기획 고정이라면 enum 선언 순서 변경 시 영향이 있으니, 사용처(HomeButtonList 등)에서 explicit한 List를 두거나, enum에 order 필드를 부여해 정렬하는 방식을 고려해 주세요.
app/build.gradle.kts (1)
137-138: Compose BOM & Accompanist Pager 버전 확인 및 ViewPager2 의존성 재검토 요청스크립트에서 버전·사용 여부를 정확히 뽑아내지 못해요. 아래 사항을 직접 확인 부탁드립니다:
- gradle/libs.versions.toml에서
[versions]블록 안의composeBom실제 값 확인accompanist-pager(version.ref = "accompanistPager" → "0.25.0")가 Compose BOM/컴파일러 extension 1.5.14와 호환되는지 공식 릴리즈 노트나 Migration 가이드 참조- 코드베이스 전체에서
androidx.viewpager2.widget.ViewPager2- Compose
HorizontalPager()com.google.accompanist.pagerimport
사용 여부 확인- Compose HorizontalPager만 사용 중이라면 app/build.gradle.kts (L137-138) 의 ViewPager2 관련 의존성 제거 고려
- libs.bundles.pager 번들에 데이터 페이징(paging-common-android)과 UI Pager가 함께 들어가 있다면, 번들을 분리하거나 이름을 더 구체화하는 걸 추천
app/src/main/java/com/example/findu/domain/usecase/GetHomeUseCase.kt (3)
13-15: 불필요한 identity map 제거 제안
map { homeData -> homeData }는 아무 변화가 없어 가독성만 떨어집니다. 그대로 반환하도록 단순화하는 게 좋습니다.아래처럼 정리하면 됩니다.
- suspend operator fun invoke(): Result<HomeData> = - homeRepository.getHome().map { homeData -> - homeData - } + suspend operator fun invoke(): Result<HomeData> = + homeRepository.getHome()
25-31: PROTECTION/ADOPTION count=0은 임시값인지 명시 필요두 항목이 0으로 고정되는데, 서버 미지원으로 인한 임시값이라면 주석으로 의도를 남기거나 TODO를 추가해 후속 작업을 추적할 수 있게 해두면 좋아요.
예시:
- ReportItem( - type = ReportDataType.PROTECTION, - count = 0 - ), + // TODO: 서버 필드 연동 시 실제 값으로 교체 + ReportItem( + type = ReportDataType.PROTECTION, + count = 0 + ),
17-38: 멤버 확장 함수 스코프 제한으로 인한 with 호출 제거 및 top-level 확장 함수로 이동 권장현재
GetHomeUseCase내부에 선언된fun HomeData.toHomeReportData()는 클래스 외부에서 바로 호출할 수 없어HomeViewModel에서with(homeUseCase) { data.toHomeReportData() }형태로 래핑해 사용 중입니다.
이럴 경우 재사용성과 가독성이 떨어지므로, 아래와 같이 멤버 확장 함수를 제거하고 top-level(또는 별도 mapper) 확장 함수로 분리하세요.• GetHomeUseCase.kt
- 클래스 내부에 선언된
fun HomeData.toHomeReportData()제거• app/src/main/java/com/example/findu/domain/mapper/HomeMappers.kt (또는 동일 파일 하단)
internal fun HomeData.toHomeReportData(): HomeReportData { return HomeReportData( reports = listOf( ReportItem( type = ReportDataType.RESCUE, count = this.todayRescuedAnimalCount ), ReportItem( type = ReportDataType.PROTECTION, count = 0 ), ReportItem( type = ReportDataType.ADOPTION, count = 0 ), ReportItem( type = ReportDataType.REPORT, count = this.todayReportAnimalCount ) ) ) }• HomeViewModel.kt
with(homeUseCase) { … }블록을 제거하고, 직접 호출하도록 변경- homeReportData = with(homeUseCase) { - data.toHomeReportData() - }, + homeReportData = data.toHomeReportData(),[optional_refactors_recommended]
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkButton.kt (4)
42-45: URL 열기: LocalUriHandler 사용 + 예외 처리로 안정성 보강빈 문자열/잘못된 URL일 때
startActivity가 크래시 날 수 있어요. Compose의LocalUriHandler로 열고, 실패 시 토스트 등으로 사용자에게 안내하면 안전합니다.아래처럼 수정 제안드립니다.
- .noRippleClickable { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(webUrl)) - context.startActivity(intent) - }, + .noRippleClickable { + if (webUrl.isBlank()) return@noRippleClickable + runCatching { + // Compose way + uriHandler.openUri(webUrl) + }.onFailure { + // Fallback + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(webUrl)) + context.startActivity(intent) + } + },파일 상단 import/변수 추가도 함께 필요합니다.
import android.widget.Toast import androidx.compose.ui.platform.LocalUriHandler- val context = LocalContext.current + val context = LocalContext.current + val uriHandler = LocalUriHandler.current
34-41: 접근성: 버튼 역할 명시 권장클릭 가능한 Row에 semantics 역할을 부여하면 스크린리더에서 더 명확히 인지합니다.
필요 import:
- androidx.compose.ui.semantics.semantics
- androidx.compose.ui.semantics.role
- androidx.compose.ui.semantics.Role
Row( modifier = modifier .border(width = 1.dp, color = FindUTheme.colors.mainColor, shape = RoundedCornerShape(20.dp)) .roundedBackgroundWithPadding( backgroundColor = FindUTheme.colors.white, cornerRadius = 20.dp, padding = PaddingValues(vertical = 12.dp) ) + .semantics { role = Role.Button } .noRippleClickable {
49-55: 아이콘 contentDescription 제공보조기기 사용자에게 아이콘 의미를 전달하려면
contentDescription을 추가하는 게 좋아요.- BaseVectorIcon(vectorResource = R.drawable.ic_info_navigate) + BaseVectorIcon( + vectorResource = R.drawable.ic_info_navigate, + contentDescription = "외부 링크로 이동" + )
62-66: Preview에 샘플 URL 사용 제안빈 문자열은 런타임에서 예외를 유발할 수 있어요. Preview라도 의미 있는 예시를 넣어두면 오용을 줄일 수 있습니다.
- webUrl = "" + webUrl = "https://example.com"app/src/main/java/com/example/findu/presentation/ui/home/component/HomeScrollToTopButton.kt (2)
28-31: 클릭 영역과 터치 타깃 보완: clickable 위치 조정 + 패딩 12dp
- clickable을 padding 앞에 두면 시각적으로 보이는 원 전체가 터치 타깃이 됩니다.
- 24dp 아이콘 + 12dp 패딩*2 = 48dp로 최소 터치 타깃 권장 사이즈를 만족합니다.
- .padding(11.dp) - .noRippleClickable(onClick), contentAlignment = Alignment.Center + .noRippleClickable(onClick) + .padding(12.dp), contentAlignment = Alignment.Center
31-32: 아이콘 contentDescription 추가로 접근성 강화시각적 의미를 보조기기에 전달합니다.
- BaseVectorIcon(vectorResource = R.drawable.ic_arrow_top_white_24, tint = FindUTheme.colors.gray4) + BaseVectorIcon( + vectorResource = R.drawable.ic_arrow_top_white_24, + tint = FindUTheme.colors.gray4, + contentDescription = "맨 위로 이동" + )app/src/main/java/com/example/findu/presentation/ui/base/SearchTagChip.kt (1)
16-16: Compose 컨벤션: modifier 파라미터를 첫 번째로 두는 것을 권장대부분의 Compose 컴포저블이
modifier: Modifier = Modifier를 첫 번째 파라미터로 둡니다. 일관성을 높이면 사용성과 가독성이 좋아집니다. (신규 컴포넌트라면 지금 맞춰두는 것을 추천)-@Composable -fun SearchTagChip(animalStateType: AnimalStateType, modifier: Modifier = Modifier) { +@Composable +fun SearchTagChip( + modifier: Modifier = Modifier, + animalStateType: AnimalStateType +) {Preview 호출부도 함께 정렬 필요:
- SearchTagChip( - animalStateType = AnimalStateType.PROTECT - ) + SearchTagChip( + animalStateType = AnimalStateType.PROTECT + )app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkList.kt (3)
32-32: 하드코딩된 문자열을 stringResource로 교체해 i18n/접근성 강화UI 텍스트가 하드코딩되어 있어 현지화, 번역, 일관성 유지를 어렵게 합니다. string 리소스로 치환을 권장합니다.
아래와 같이 교체해 주세요:
- Text(text = "유기동물 관련 사이트", style = FindUTheme.typography.head2SemiBold20, color = FindUTheme.colors.gray6) + Text(text = stringResource(R.string.home_web_link_title), style = FindUTheme.typography.head2SemiBold20, color = FindUTheme.colors.gray6)- buttonText = "동물정보보호시스템 바로가기", + buttonText = stringResource(R.string.home_web_link_part_button),- buttonText = "동물자유연대 바로가기", + buttonText = stringResource(R.string.home_web_link_animal_rights_button),- buttonText = "종합유기견보호센터 바로가기", + buttonText = stringResource(R.string.home_web_link_center_button),- Text( - text = "서비스 관련 정보 \n" + - "문의처는 어쩌고 저쩌고 \n" + - "전화번호 00-10-3-20442", + Text( + text = stringResource(R.string.home_web_link_footer_info), style = FindUTheme.typography.captionRegular12, color = FindUTheme.colors.gray5 )추가로 strings.xml에 아래 리소스가 필요합니다:
<string name="home_web_link_title">유기동물 관련 사이트</string> <string name="home_web_link_part_button">동물정보보호시스템 바로가기</string> <string name="home_web_link_animal_rights_button">동물자유연대 바로가기</string> <string name="home_web_link_center_button">종합유기견보호센터 바로가기</string> <string name="home_web_link_footer_info">서비스 관련 정보 문의처는 어쩌고 저쩌고 전화번호 00-10-3-20442</string>Also applies to: 39-41, 45-47, 51-53, 56-61
15-16: stringResource import 누락위 변경을 위해 stringResource import가 필요합니다.
import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp
68-69: Preview에 테마 래핑 권장디자인 확인을 위해 Preview를 FindUTheme로 감싸는 것을 권장합니다.
@Composable private fun HomeWebLinkListPreview() { - HomeWebLinkList() + FindUTheme { + HomeWebLinkList() + } }app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt (3)
43-45: border와 background 순서 교체 권장 (테두리 가림 방지)현재 .border → .background 순서입니다. background가 나중에 그려져 border를 가릴 수 있습니다. background를 먼저 적용하고 border를 그리도록 순서를 바꾸세요.
- .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp)) - .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp))
20-23: contentDescription 하드코딩(영문) → stringResource로 치환접근성과 현지화를 위해 이미지 설명을 string 리소스로 옮기는 것을 권장합니다.
imports:
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource컴포저블:
- contentDescription = "Animal Image", + contentDescription = stringResource(R.string.cd_reported_animal_image),strings.xml:
<string name="cd_reported_animal_image">신고 동물 이미지</string>Also applies to: 49-49
41-46: 클릭 가능한 카드에 접근성 Role 추가 권장보이스오버 사용성을 위해 semantics Role을 Button으로 지정하면 좋습니다.
Column( modifier = modifier .width(224.dp) - .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp)) - .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp)) + .semantics { role = Role.Button } .noRippleClickable { navigateToReportDetail(animal) } ) {필요 import:
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semanticsapp/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalList.kt (3)
68-77: LazyRow items API 개선 (인덱스 기반 → 아이템 기반)인덱스 기반 접근은 실수 여지를 늘리고 가독성이 떨어집니다. items(items = …) API로 전환을 권장합니다.
- items( - count = animalCards.size, - key = { index -> animalCards[index].protectId }, - contentType = { "animal_card" } - ) { index -> - HomeProtectAnimalCard( - animal = animalCards[index], - navigateToProtectDetail = navigateToProtectDetail - ) - } + items( + items = animalCards, + key = { it.protectId }, + contentType = { "animal_card" } + ) { animal -> + HomeProtectAnimalCard( + animal = animal, + navigateToProtectDetail = navigateToProtectDetail + ) + }
28-33: 파라미터 명명 일관성: navigationToSearch → navigateToSearch다른 콜백과 맞춰 동사구 형태(navigateTo…)로 통일하면 이해가 쉬워집니다. 외부 호출부 영향이 있으니 함께 변경해 주세요.
-fun HomeProtectAnimalList( - nickname: String, - navigationToSearch: () -> Unit, +fun HomeProtectAnimalList( + nickname: String, + navigateToSearch: () -> Unit, navigateToProtectDetail: (ProtectAnimal) -> Unit, animalCards: List<ProtectAnimal>, modifier: Modifier = Modifier ) {- .noRippleClickable(navigationToSearch) + .noRippleClickable(navigateToSearch)HomeProtectAnimalList( nickname = "신민석", - navigationToSearch = {}, + navigateToSearch = {}, animalCards = dummyAnimalCards, navigateToProtectDetail = {}, )Also applies to: 43-43, 136-140
84-141: Preview에 테마 적용 권장디자인 확인을 위해 FindUTheme로 감싸 주세요.
@Composable private fun HomeProtectAnimalListPreview() { - val dummyAnimalCards = listOf( + FindUTheme { + val dummyAnimalCards = listOf( ... - ) - HomeProtectAnimalList( - nickname = "신민석", - navigateToSearch = {}, - animalCards = dummyAnimalCards, - navigateToProtectDetail = {}, - ) + ) + HomeProtectAnimalList( + nickname = "신민석", + navigateToSearch = {}, + animalCards = dummyAnimalCards, + navigateToProtectDetail = {}, + ) + } }app/src/main/java/com/example/findu/presentation/ui/home/component/HomeBannerPager.kt (2)
48-59: 페이지 인디케이터 계산을 page 변수로 전환 + stringResource로 i18npagerState.currentPage는 스크롤 중 재합성/표시 흔들림을 유발할 수 있습니다. page 인덱스를 사용하고 문자열은 리소스로 치환하세요.
- Text( - text = "${pagerState.currentPage + 1}/${bannerList.size}", + Text( + text = stringResource(R.string.home_banner_page_indicator, page + 1, bannerList.size), style = FindUTheme.typography.captionRegular11, color = FindUTheme.colors.white,imports:
import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResourcestrings.xml:
<string name="home_banner_page_indicator">%1$d/%2$d</string>
21-23: Accompanist Pager → Foundation Pager로의 마이그레이션 제안프로덕션 안정성과 의존성 축소를 위해 androidx.compose.foundation.pager로 전환을 고려해 주세요. API는 유사하며, 향후 유지보수 비용을 낮출 수 있습니다.
원하시면 마이그레이션 패치도 도와드릴게요.
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt (4)
44-46: border/background 순서 교체 권장 (테두리 가림 방지)background → border 순서로 적용해 테두리가 가려지지 않도록 해주세요.
- .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp)) - .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp))
52-54: contentDescription 하드코딩 → stringResource로 교체접근성/현지화를 위해 이미지 설명을 리소스로 옮기는 것을 권장합니다.
imports:
import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource컴포저블:
- contentDescription = "Animal Image", + contentDescription = stringResource(R.string.cd_protect_animal_image),strings.xml:
<string name="cd_protect_animal_image">보호 동물 이미지</string>Also applies to: 23-23
41-47: 클릭 가능한 카드에 접근성 Role 추가 권장보이스오버 사용자 배려를 위해 semantics Role 지정이 좋습니다.
Column( modifier = modifier .width(120.dp) - .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp)) - .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .background(shape = RoundedCornerShape(10.dp), color = FindUTheme.colors.white) + .border(width = 1.dp, color = FindUTheme.colors.gray3, shape = RoundedCornerShape(10.dp)) + .semantics { role = Role.Button } .noRippleClickable { navigateToProtectDetail(animal) } ) {필요 import:
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics
49-58: 사소한 레이아웃 단순화 제안Box에 정확한 고정 크기를 주고 내부 AsyncImage에서 aspectRatio와 fillMaxWidth를 함께 사용하는 패턴은 중복일 수 있습니다. 어느 한쪽만 유지해도 동일한 레이아웃을 얻습니다. 유지보수성을 위해 정리하는 것을 권장합니다.
원하시면 원하는 목표 사이즈 기준으로 정리 패치 제안 드릴게요.
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeTopBar.kt (1)
53-64: 접근성(스크린리더) 보강: 버튼 역할/설명 추가 권장
- 클릭 가능한 텍스트를 버튼 역할로 노출하면 접근성이 좋아집니다.
- 알림 아이콘에는 contentDescription을 제공해주세요(위 코멘트 diff 참고).
예시:
Text( text = stringResource(R.string.home_report_button_text), color = FindUTheme.colors.white, style = FindUTheme.typography.body1SemiBold16, modifier = Modifier + .semantics { this.role = Role.Button } .roundedBackgroundWithPadding( backgroundColor = FindUTheme.colors.mainColor, cornerRadius = 12.dp, padding = PaddingValues(vertical = 10.dp, horizontal = 17.dp) ) .noRippleClickable(reportButtonClicked) )app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalList.kt (2)
68-77: LazyRow items는 index 기반보다 items(list) 기반이 안전하고 가독성이 좋습니다.리스트 변경 시 인덱스-키 불일치 가능성을 줄이고, 코드도 간결해집니다.
- items( - count = animalCards.size, - key = { index -> animalCards[index].reportId }, - contentType = { "animal_card" } - ) { index -> - HomeReportedAnimalCard( - animal = animalCards[index], - navigateToReportDetail = navigateToReportDetail, - ) - } + items( + items = animalCards, + key = { it.reportId }, + contentType = { "animal_card" } + ) { animal -> + HomeReportedAnimalCard( + animal = animal, + navigateToReportDetail = navigateToReportDetail, + ) + }
27-33: 네이밍 일관성 제안: navigationToSearch → navigateToSearch동일 파일/인접 컴포넌트에서 navigateToReportDetail처럼 동사 원형으로 시작하는 네이밍을 사용 중입니다. 검색 네비게이션도 navigate 접두어로 맞추면 읽기/검색성이 좋아집니다. (타 파일 호출부(HomeScreen)도 함께 변경 필요)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeButtonList.kt (1)
45-52: 접근성 힌트 추가: 클릭 가능한 Column에 contentDescription/role 부여 권장텍스트가 있어도 요소 전체에 의미를 부여하면 스크린리더 사용성이 좋아집니다.
- Column( - modifier = Modifier.noRippleClickable { navigation() }, + Column( + modifier = Modifier + .semantics { + contentDescription = stringResource(homeButton.nameRes) + role = Role.Button + } + .noRippleClickable { navigation() }, horizontalAlignment = Alignment.CenterHorizontally ) {app/src/main/java/com/example/findu/presentation/ui/home/composeview/HomeScreen.kt (2)
83-89: 배너 자동 슬라이드: 아이템 수가 0/1일 때는 애니메이션을 건너뛰면 안전합니다.현재 enum 기반이라 0개 가능성이 낮지만, 방어적으로 처리하면 안정성이 올라갑니다.
- LaunchedEffect(pagerState) { - while (true) { - delay(3000) - val nextPage = (pagerState.currentPage + 1) % bannerList.size - pagerState.animateScrollToPage(nextPage) - } - } + if (bannerList.size > 1) { + LaunchedEffect(pagerState) { + while (true) { + delay(3000) + val nextPage = (pagerState.currentPage + 1) % bannerList.size + pagerState.animateScrollToPage(nextPage) + } + } + }
73-81: 스크롤-탑 버튼 노출 조건은 OK, 다만 UX적으로 완전 끝까지 내려갔을 때만 보이는 것은 아쉬울 수 있음firstVisibleItemIndex 기준(예: 2 이상)으로 바꾸면 유저가 중간쯤에서도 바로 올라갈 수 있어 UX가 개선됩니다. 선택 사항입니다.
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportCard.kt (6)
150-169: 프리뷰에서 선택 상태가 반영되지 않아 클릭해도 Indicator가 바뀌지 않습니다프리뷰에서
selected상태를 유지하지 않고 고정된YEAR를 넘기고 있어 동작 확인이 어렵습니다.아래처럼 수정해 주세요:
- HomeReportCard( - homeReportData = homeReportData, - indicatorClicked = { clickedLabel -> selected = clickedLabel }, - homeReportDuration = HomeReportDurationType.YEAR - ) + HomeReportCard( + homeReportData = homeReportData, + indicatorClicked = { selected = it }, + homeReportDuration = selected + )
110-116: Color.Unspecified 대신 Color.Transparent 사용 및 else 분기 간소화 제안
Color.Unspecified는 배경을 그리지 않는 의미라 의도가 모호합니다. 투명 명시를 원하면Color.Transparent가 더 명확합니다. 또한 비선택 분기는 모서리 배경을 그릴 필요 없이 패딩만 주는 편이 간결합니다.- val backgroundColor = if (isSelected) FindUTheme.colors.white else Color.Unspecified + val backgroundColor = if (isSelected) FindUTheme.colors.white else Color.Transparent @@ - .then( - if (isSelected) { - Modifier - .background(backgroundColor, RoundedCornerShape(30.dp)) - .border(1.dp, FindUTheme.colors.mainColor, RoundedCornerShape(30.dp)) - .padding(vertical = 8.dp) - } else { - Modifier.roundedBackgroundWithPadding( - backgroundColor = backgroundColor, - padding = PaddingValues(vertical = 8.dp), - cornerRadius = 30.dp - ) - } - ) + .then( + if (isSelected) { + Modifier + .background(backgroundColor, RoundedCornerShape(30.dp)) + .border(1.dp, FindUTheme.colors.mainColor, RoundedCornerShape(30.dp)) + .padding(vertical = 8.dp) + } else { + Modifier.padding(vertical = 8.dp) + } + )Also applies to: 119-136
84-91: 세로 구분선 높이 하드코딩(55.dp) → 콘텐츠 대응형으로 개선 권장현재 55.dp로 고정되어 있어 폰트 스케일/문구 길이에 따라 어긋날 수 있습니다. Row를 IntrinsicSize.Min으로 측정하고, Divider는 fillMaxHeight로 채우세요.
- Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min), + verticalAlignment = Alignment.CenterVertically + ) { @@ - Spacer( - modifier = Modifier - .width(1.dp) - .height(55.dp) - .background(FindUTheme.colors.gray3) - ) + Spacer( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(FindUTheme.colors.gray3) + )추가로 파일 상단에 아래 임포트가 필요합니다:
import androidx.compose.foundation.layout.IntrinsicSize
39-44: 이벤트 이름 의미 명확화 제안: indicatorClicked → onDurationClick(or onDurationSelected)콜백 이름을 의도를 직접 드러내는 형태로 바꾸면 가독성이 좋아집니다. 해당 이름은 HomeScreen/HomeFragment 등 호출부에서도 함께 변경해야 합니다.
원하시면 호출부까지 포함한 리팩터링 패치를 만들어 드릴게요.
Also applies to: 98-102
78-81: 레이블 로컬라이제이션 확인 필요
data.type.label이 하드코딩 문자열이라면 다국어 지원에 문제가 생깁니다. 문자열 리소스로의 매핑 여부를 확인해 주세요. 필요 시@StringResid를 들고 있도록 타입을 조정하는 걸 권장합니다.리소스 매핑 유틸을 추가해드릴까요?
103-147: 선택 그룹 접근성(a11y) 보완: selectableGroup/selectable 사용 권장세그먼트 컨트롤은 선택 가능 그룹으로 노출하는 게 스크린리더 친화적입니다. role/상태 전달을 위해 아래처럼 보완해 주세요.
예시:
Row( modifier = modifier .fillMaxWidth() .roundedBackgroundWithPadding( backgroundColor = FindUTheme.colors.gray1, cornerRadius = 30.dp ) .selectableGroup() // 그룹 지정 ) { HomeReportDurationType.entries.forEach { duration -> val isSelected = selected == duration Box( modifier = Modifier .weight(1f) .selectable( // 개별 항목 지정 selected = isSelected, onClick = { indicatorClicked(duration) }, role = Role.Tab // 또는 Role.RadioButton ) .then( if (isSelected) { Modifier .background(FindUTheme.colors.white, RoundedCornerShape(30.dp)) .border(1.dp, FindUTheme.colors.mainColor, RoundedCornerShape(30.dp)) .padding(vertical = 8.dp) } else { Modifier.padding(vertical = 8.dp) } ), contentAlignment = Alignment.Center ) { ... } } }필요 임포트:
import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.ui.semantics.Roleapp/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt (6)
56-58: uiEffect 수집 시 flowWithLifecycle는 중복 방어일 뿐이라 간소화 가능
LaunchedEffect자체가 컴포지션 생명주기에 묶이므로 내부에서flowWithLifecycle까지 쓰지 않아도 충분합니다. 간소화하면 가독성이 좋아집니다.- LaunchedEffect(homeViewModel.uiEffect, lifecycleOwner) { - homeViewModel.uiEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle) - .collect { sideEffect -> + LaunchedEffect(homeViewModel.uiEffect, lifecycleOwner) { + homeViewModel.uiEffect.collect { sideEffect ->대안으로, Compose 밖(Fragment 범위)에서
viewLifecycleOwner.lifecycleScope+repeatOnLifecycle로 수집하면 UI와 더 느슨하게 결합됩니다. 원하시면 해당 형태로 리팩터링해 드릴게요.
96-126: 로딩/에러 상태 UI 보강 권장Idle/Loading/Error에서 아무것도 렌더링하지 않으면 사용자 경험이 비어 보일 수 있습니다. 스켈레톤/로딩 인디케이터/에러 리트라이 UI를 붙이는 걸 추천합니다.
간단한 Shimmer 스켈레톤/에러 리트라이 컴포저블을 제공해드릴까요?
115-115: 닉네임 하드코딩 제거 필요
"사용자"하드코딩은 추후 여러 군데에 퍼질 우려가 있습니다. ViewModel에서 제공하거나 로그인/프로필 소스로부터 주입하는 방식으로 바꾸는 게 좋습니다.필요 시
HomeUiState에userNickname을 추가하고 ViewModel에서 설정하도록 리팩터링하겠습니다.
132-144: 지원하지 않는 tag에 대한 방어 로직 추가 권장현재
PROTECT만 처리합니다. 그 외 태그가 들어오면 무시됩니다. 최소한 사용자에게 피드백을 주거나 로깅하세요.private fun navigateToProtectDetail(id: String, tag: String, name: String) { when (tag) { AnimalStateType.PROTECT.state -> { findNavController().navigate( HomeFragmentDirections.actionFragmentHomeToFragmentSearchDetailProtecting( id = id, tag = tag, name = name ) ) } + else -> { + Toast.makeText(requireContext(), "지원하지 않는 태그입니다: $tag", Toast.LENGTH_SHORT).show() + } } }
146-167: 신고 상세 이동도 기타 태그 방어 로직 추가 권장예상 외 태그 입력 시 유저 피드백을 주도록 처리해 주세요.
private fun navigateToReportDetail(id: String, tag: String, name: String) { when (tag) { AnimalStateType.MISSING.state -> { findNavController().navigate( HomeFragmentDirections.actionFragmentHomeToFragmentSearchDetailDisappear( id = id, tag = tag, name = name ) ) } AnimalStateType.FIND.state -> { findNavController().navigate( HomeFragmentDirections.actionFragmentHomeToFragmentSearchDetailWitness( id = id, tag = tag, name = name ) ) } + else -> { + Toast.makeText(requireContext(), "지원하지 않는 태그입니다: $tag", Toast.LENGTH_SHORT).show() + } } }
20-22: 불필요한 import 정리
HomeReportData,ReportDataType,ReportItem는 사용되지 않는 것 같습니다. 정리하면 가독성이 좋아집니다.-import com.example.findu.domain.model.HomeReportData -import com.example.findu.domain.model.ReportDataType -import com.example.findu.domain.model.ReportItemapp/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (5)
35-35: 줄바꿈 포맷 정리 (가독성)
), val reportDataDuration...한 줄에 이어져 있어 읽기 어렵습니다. 줄바꿈으로 정리해주세요.- ), val reportDataDuration: HomeReportDurationType = HomeReportDurationType.WEEK, + ), + val reportDataDuration: HomeReportDurationType = HomeReportDurationType.WEEK,
197-201: 집계 기간 변경 시 데이터 소스와 동기화 필요현재는 기간만 바꾸고 데이터를 다시 계산/조회하지 않습니다. 기간별로 값이 달라지는 요구사항이라면 재계산 또는 재요청 로직이 필요합니다.
옵션:
- 간단:
changeReportDuration에서loadHomeData()재호출- 최적화:
homeData와duration을 입력으로toHomeReportData(duration)같은 확장/유스케이스 제공
원하시면 두 번째 방안으로 유스케이스/매퍼까지 리팩터링 제안 드릴게요.
76-78: uiEffect 채널 버퍼 적용으로 안정성 향상 제안구독이 잠시 끊긴 타이밍에 send가 걸릴 수 있습니다.
Channel.BUFFERED로 완화하세요.- private val _uiEffect = Channel<HomeUiEffect>() + private val _uiEffect = Channel<HomeUiEffect>(Channel.BUFFERED)
79-87: Lazy init 패턴 단순화 가능 (선택)
MutableStateFlow를 다시stateIn할 필요는 없습니다. 초기 로딩 트리거만 필요하면init { handleEvent(LoadHomeData) }+asStateFlow()가 더 간결합니다.대안 예시:
private val _uiState = MutableStateFlow(HomeUiState()) val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow() init { handleEvent(HomeUiEvent.LoadHomeData) }현행도 동작엔 문제 없습니다.
223-226: 매직 넘버(2) 상수화 제안임계값을 상수로 분리하면 의도가 명확해집니다.
예시:
private companion object { const val SCROLL_TO_TOP_VISIBLE_THRESHOLD = 2 } private fun updateScrollToTopVisibility(firstVisibleItemIndex: Int) { val isVisible = firstVisibleItemIndex > SCROLL_TO_TOP_VISIBLE_THRESHOLD _uiState.value = _uiState.value.copy(isScrollToTopVisible = isVisible) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (3)
app/src/main/res/drawable/img_home_bell_32.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_home_protect_center.pngis excluded by!**/*.pngapp/src/main/res/drawable/img_home_protect_part.pngis excluded by!**/*.png
📒 Files selected for processing (34)
app/build.gradle.kts(1 hunks)app/src/main/java/com/example/findu/domain/model/HomeData.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/GetHomeUseCase.kt(1 hunks)app/src/main/java/com/example/findu/presentation/type/AnimalStateType.kt(2 hunks)app/src/main/java/com/example/findu/presentation/type/HomeBannerType.kt(1 hunks)app/src/main/java/com/example/findu/presentation/type/HomeButtonType.kt(1 hunks)app/src/main/java/com/example/findu/presentation/type/HomeReportDurationType.kt(1 hunks)app/src/main/java/com/example/findu/presentation/type/view/LoadState.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/base/SearchTagChip.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeBannerPager.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeButtonList.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalList.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportCard.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalList.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeScrollToTopButton.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeTopBar.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkButton.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkList.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/composeview/HomeScreen.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt(1 hunks)app/src/main/java/com/example/findu/presentation/util/Constraints.kt(1 hunks)app/src/main/java/com/example/findu/presentation/util/extension/IntExt.kt(1 hunks)app/src/main/res/drawable/ic_arrow_top_white_24.xml(1 hunks)app/src/main/res/drawable/ic_home_info_title_24.xml(1 hunks)app/src/main/res/drawable/ic_reported_card_center_18.xml(1 hunks)app/src/main/res/drawable/ic_reported_card_gender_male_20.xml(1 hunks)app/src/main/res/drawable/ic_reported_card_location_18.xml(1 hunks)app/src/main/res/layout/fragment_home.xml(1 hunks)app/src/main/res/values/strings.xml(2 hunks)gradle/libs.versions.toml(5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (10)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt (4)
app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1)
navigateToProtectDetail(165-176)app/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt (1)
navigateToProtectDetail(132-144)app/src/main/java/com/example/findu/presentation/ui/base/SearchTagChip.kt (1)
SearchTagChip(15-30)app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeScrollToTopButton.kt (1)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt (4)
app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1)
navigateToReportDetail(178-189)app/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt (1)
navigateToReportDetail(146-168)app/src/main/java/com/example/findu/presentation/ui/base/SearchTagChip.kt (1)
SearchTagChip(15-30)app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkButton.kt (1)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkList.kt (2)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkButton.kt (1)
HomeWebLinkButton(26-57)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalList.kt (2)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt (1)
HomeReportedAnimalCard(34-99)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalList.kt (2)
app/src/main/java/com/example/findu/presentation/ui/base/BaseVectorIcon.kt (1)
BaseVectorIcon(11-24)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalCard.kt (1)
HomeProtectAnimalCard(35-91)
app/src/main/java/com/example/findu/presentation/ui/home/composeview/HomeScreen.kt (9)
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeTopBar.kt (1)
HomeTopBar(25-66)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportCard.kt (1)
HomeReportCard(38-95)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeButtonList.kt (1)
HomeButtonList(25-57)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeBannerPager.kt (1)
HomeBannerPager(25-62)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeProtectAnimalList.kt (1)
HomeProtectAnimalList(26-80)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalList.kt (1)
HomeReportedAnimalList(26-80)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkList.kt (1)
HomeWebLinkList(23-64)app/src/main/java/com/example/findu/presentation/ui/home/component/HomeScrollToTopButton.kt (1)
HomeScrollToTopButton(18-33)app/src/main/java/com/example/findu/ui/theme/Theme.kt (1)
FindUTheme(39-59)
app/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt (2)
app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (5)
navigateToProtectDetail(165-176)navigateToReportDetail(178-189)showReportDialog(191-195)showFindDialog(207-211)openWebLink(213-217)app/src/main/java/com/example/findu/presentation/ui/home/composeview/HomeScreen.kt (1)
HomeScreen(53-197)
app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1)
app/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt (5)
navigateToProtectDetail(132-144)navigateToReportDetail(146-168)showReportDialog(170-173)showFindDialog(175-178)openWebLink(180-183)
🔇 Additional comments (16)
app/src/main/res/drawable/ic_reported_card_location_18.xml (1)
1-9: 벡터 리소스 추가 LGTM사이즈/뷰포트 일치하고 pathData 이상 없습니다. 홈 신고 카드 용도로 적절해 보여요.
app/src/main/res/drawable/ic_reported_card_gender_male_20.xml (1)
1-9: 벡터 리소스 추가 LGTM사이즈/뷰포트/경로 모두 정상입니다. 성별 아이콘 셋과의 일관성(여성 아이콘 등)만 추후 함께 들어오는지 정도만 체크하면 좋겠습니다.
app/src/main/java/com/example/findu/presentation/type/view/LoadState.kt (1)
3-8: 단순 명확한 로딩 상태 Enum, 목적에 부합합니다Idle/Loading/Success/Error 구분만 필요한 경우, 가볍고 쓰기 편합니다. Compose에서도 enum은 안정적(불변)이라 상태 추적에 문제 없습니다.
app/src/main/res/drawable/ic_arrow_top_white_24.xml (1)
1-9: 스크롤 투 탑 아이콘 추가 LGTM경로/사이즈 모두 이상 없고, 상단 화살표로 의미도 분명합니다.
app/src/main/java/com/example/findu/presentation/util/extension/IntExt.kt (1)
7-7: 숫자 포매팅 확장 함수 유용합니다Korean locale로 천 단위 구분 적용이 직관적이고, UI 숫자 표기에 잘 맞습니다.
app/src/main/res/drawable/ic_reported_card_center_18.xml (1)
1-9: 아이콘 리소스 추가 적절합니다뷰포트/크기 매칭(18↔18)과 path 데이터 모두 문제 없어 보입니다. 홈/신고 카드 아이콘 세트 내 일관된 네이밍도 괜찮아요.
app/src/main/res/drawable/ic_home_info_title_24.xml (1)
1-9: 리소스 스펙 적합, 이름/사이즈 컨벤션도 일관적입니다24dp 아이콘으로 헤더 타이틀 컨텍스트에 적절해 보입니다.
app/src/main/java/com/example/findu/presentation/type/HomeReportDurationType.kt (1)
3-7: enum.entries 사용 안전 확인 완료프로젝트가 Kotlin 1.9.24를 사용 중이므로
HomeReportDurationType.entries를 안정적으로 활용할 수 있습니다. 별도로values()로 대체할 필요가 없습니다.app/src/main/java/com/example/findu/presentation/type/AnimalStateType.kt (1)
11-25: 구 심볼 참조 없음 확인
rg -n -C2 -P 'AnimalStateType\.(Protect|Missing|Find)'실행 결과 이전 Protect/Missing/Find 호출부가 코드에서 전혀 발견되지 않았습니다.
– 다만, JSON 직렬화나 리플렉션 등으로 enum 이름(Protect→PROTECT등)을 사용 중인 부분이 있는지 런타임 검증 부탁드립니다.app/src/main/java/com/example/findu/presentation/type/HomeBannerType.kt (1)
6-22: enum 구성 적절 — 기본 형태는 LGTM배너 타입을 enum으로 묶고 @DrawableRes로 명시한 점 좋아요. 단순 데이터 홀더로 쓰기 깔끔합니다.
app/src/main/res/values/strings.xml (1)
225-229: 문구 톤앤매너 확인 요청
- home_banner_title: 이모지 사용은 눈에 띄지만, 특정 환경(저사양/특정 폰트)에서 깨질 수 있어요. 서비스 톤에 맞는지 PM/디자이너와 합의 체크 부탁드립니다.
- ‘님의 주변’ 표현은 위치 권한 미허용/위치 미설정 상황에서 대체 문구가 준비되어야 합니다. 해당 경우의 fallback 문자열 필요 여부 확인 바랍니다.
gradle/libs.versions.toml (1)
55-55: Paging 라이브러리 선택 검토androidx-paging-common-android만 추가되어 있는데, 실제 런타임에서 Paging을 사용한다면 일반적으로 paging-runtime 또는 paging-compose가 필요합니다. 의도적으로 공통 모듈만 쓴 것인지 한번 확인 부탁드립니다.
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeWebLinkList.kt (1)
45-47: 두 버튼이 동일한 CENTER_URL을 사용 — 의도 확인 필요"동물자유연대"와 "종합유기견보호센터"가 동일한 CENTER_URL로 연결됩니다. 실제로는 서로 다른 기관 사이트일 가능성이 높습니다. URL 상수 분리 여부 확인 부탁드립니다. 필요시 WebViewUrl에 별도 상수(예: ANIMAL_RIGHTS_URL)를 추가해 주세요.
원하시면 URL 상수 추가와 사용처 변경 패치도 함께 드리겠습니다.
Also applies to: 51-53
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeButtonList.kt (1)
33-43: 구현 방향은 명확하고 간결합니다.enum 엔트리 기반으로 네비게이션 콜백을 매핑하는 방식, SpaceBetween 배치 모두 적절합니다.
app/src/main/res/layout/fragment_home.xml (1)
7-14: ComposeView 전환 LGTM + 메모리 릭 방지 전략 확인 권장XML에서 ComposeView 단일 진입점으로 단순화한 방향 좋습니다. HomeFragment에서 ViewLifecycle에 맞춰 컴포지션이 dispose 되도록 전략 설정을 확인해주세요.
예시 (HomeFragment.kt):
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val composeView: ComposeView = view.findViewById(R.id.compose_view) composeView.setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) composeView.setContent { // HomeScreen(...) } }app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1)
42-58: 이벤트/효과/상태 설계 전반: 일관성 좋습니다UiState/UiEvent/UiEffect로 구분하고, 네비게이션/다이얼로그/웹링크를 Effect로 단방향 처리한 점이 깔끔합니다. 채널/플로우 조합도 적절합니다.
app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt
Outdated
Show resolved
Hide resolved
| val textColor: Int, | ||
| val backgroundRes: Int | ||
| @ColorRes val textColor: Int, | ||
| @ColorRes val backgroundColorRes: Int |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
배경 프로퍼티 명 변경 영향 점검 필요
backgroundRes → backgroundColorRes로 네이밍이 바뀐 것으로 보입니다. 기존 사용처에서 .backgroundRes를 참조하는 코드가 남아있다면 컴파일 에러가 발생합니다. 간단히 검색해 확인 부탁드립니다.
🏁 Script executed:
#!/bin/bash
# 구 명칭/신 명칭 사용처 점검
rg -nP '\.backgroundRes\b' -C2 -g '!**/build/**'
rg -nP '\.backgroundColorRes\b' -C2 -g '!**/build/**'Length of output: 3893
backgroundRes → backgroundColorRes 프로퍼티명 변경 반영 필요
SearchStatus 모델의 프로퍼티명이 backgroundRes에서 backgroundColorRes로 바뀌어, 기존 .backgroundRes 참조가 남아 있으면 컴파일 오류가 발생합니다. 아래 위치들을 모두 .backgroundColorRes로 수정해주세요.
• app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchContentRVAdapter.kt
– 라인 97, 123 (item.tag.backgroundRes → item.tag.backgroundColorRes)
• app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt
– 라인 171 (tagInfo.backgroundRes → tagInfo.backgroundColorRes)
• app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchDisappearDetailFragment.kt
– 라인 201 (tagInfo.backgroundRes → tagInfo.backgroundColorRes)
• app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchWitnessDetailFragment.kt
– 라인 203 (tagInfo.backgroundRes → tagInfo.backgroundColorRes)
위 항목들을 점검·수정해주시면 됩니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt
around line 10 the property was renamed from backgroundRes to
backgroundColorRes; update all usages accordingly: change item.tag.backgroundRes
to item.tag.backgroundColorRes in
app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchContentRVAdapter.kt
at lines ~97 and ~123, change tagInfo.backgroundRes to
tagInfo.backgroundColorRes in
app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt
at ~171, in SearchDisappearDetailFragment.kt at ~201, and in
SearchWitnessDetailFragment.kt at ~203 so all references match the new property
name and the project compiles.
| Row(verticalAlignment = Alignment.CenterVertically) { | ||
| BaseVectorIcon(vectorResource = R.drawable.ic_reported_card_center_18) | ||
| Spacer(modifier = Modifier.width(7.dp)) | ||
| Text( | ||
| text = animal.happenLocation, | ||
| style = FindUTheme.typography.captionRegular12, | ||
| color = FindUTheme.colors.gray5, | ||
| maxLines = 1, | ||
| overflow = TextOverflow.Ellipsis | ||
| ) | ||
| } | ||
| Spacer(modifier = Modifier.height(2.dp)) | ||
| Row(verticalAlignment = Alignment.CenterVertically) { | ||
| BaseVectorIcon(vectorResource = R.drawable.ic_reported_card_location_18) | ||
| Spacer(modifier = Modifier.width(7.dp)) | ||
| Text( | ||
| text = animal.happenLocation, | ||
| style = FindUTheme.typography.captionRegular12, | ||
| color = FindUTheme.colors.gray5, | ||
| maxLines = 1, | ||
| overflow = TextOverflow.Ellipsis | ||
| ) | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
두 줄 모두 같은 happenLocation 표기 — 정보 중복/오표기 가능성
센터(보호소) 아이콘과 위치 아이콘이 각각 다른 정보를 보여줘야 하는데, 현재 두 줄 모두 happenLocation을 출력합니다. 도메인에 센터명/주소(예: careName/careAddress)가 있다면 첫 번째 줄은 센터 정보를, 두 번째 줄은 발생 위치를 표기하는 것이 자연스럽습니다. 해당 필드가 없다면 한 줄만 남기는 편이 낫습니다.
임시 방안으로 중복 줄을 제거하는 패치를 제안드립니다:
- Row(verticalAlignment = Alignment.CenterVertically) {
- BaseVectorIcon(vectorResource = R.drawable.ic_reported_card_center_18)
- Spacer(modifier = Modifier.width(7.dp))
- Text(
- text = animal.happenLocation,
- style = FindUTheme.typography.captionRegular12,
- color = FindUTheme.colors.gray5,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
- Spacer(modifier = Modifier.height(2.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
BaseVectorIcon(vectorResource = R.drawable.ic_reported_card_location_18)
Spacer(modifier = Modifier.width(7.dp))
Text(
text = animal.happenLocation,
style = FindUTheme.typography.captionRegular12,
color = FindUTheme.colors.gray5,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}필요 시 ReportAnimal의 필드 정의를 확인해 적절한 필드로 바꾸는 패치도 도와드릴게요.
🏁 Script executed:
#!/bin/bash
# 🐇 도메인 모델에서 Animal 관련 data class 및 센터 정보 필드(careName, careAddress 등) 확인
# 1) Animal 모델 정의 파일 탐색
echo "🔍 Animal 관련 data class 파일 찾는 중..."
model_files=$(rg -l 'data class .*Animal' -l)
if [ -z "$model_files" ]; then
echo "⚠️ Animal 모델을 찾지 못했습니다."
exit 0
fi
# 2) 각 파일에서 happenLocation 외 센터 정보 필드 존재 여부 검사
for f in $model_files; do
echo "==> $f"
rg -nE 'happenLocation|careName|careAddress|center|care' -n "$f" || echo " • 관련 필드 없음"
doneLength of output: 6348
#!/bin/bash
set -e
# 1) HomeReportedAnimalCard 파일 내용과 animal 파라미터 타입 확인
file="app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt"
echo "==> $file (상위 200줄)"
sed -n '1,200p' "$file"
echo
echo "==> animal 파라미터 타입 추출"
rg -oP 'HomeReportedAnimalCard\([^)]*animal:\s*\K[A-Za-z0-9_]+' -n "$file" || true
# 2) 타입에 해당하는 data class 파일 찾기 및 필드 목록 출력
type=$(rg -oP 'HomeReportedAnimalCard\([^)]*animal:\s*\K[A-Za-z0-9_]+' -n "$file" | head -1)
if [ -n "$type" ]; then
echo
echo "==> $type 데이터 클래스 정의 위치 및 필드 확인"
model_file=$(rg -l "data class $type" -n . | head -1)
if [ -n "$model_file" ]; then
echo "파일: $model_file"
sed -n '1,200p' "$model_file"
else
echo "⚠️ data class $type를 찾을 수 없습니다."
fi
else
echo "⚠️ animal 파라미터 타입을 찾지 못했습니다."
fi🏁 Script executed:
#!/usr/bin/env bash
set -e
# 1) HomeReportedAnimalCard.kt에서 Composable 함수 시그니처 확인
file="app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt"
echo "🔍 $file에서 HomeReportedAnimalCard 시그니처 찾기"
rg -n "fun HomeReportedAnimalCard" -n "$file" || true
# 2) animal 파라미터 타입 추출
type=$(rg -oP 'HomeReportedAnimalCard\([^\)]*animal:\s*\K\w+' "$file" | head -1) || true
echo "▶ animal 타입: ${type:-(없음)}"
# 3) 해당 타입의 데이터 클래스 정의 파일 위치 탐색
if [ -n "$type" ]; then
echo "🔍 data class $type 정의 파일 찾기"
model_file=$(rg -l "data class $type" -n app/src/main/java/com/example/findu) || true
echo "▶ 모델 파일: ${model_file:-(찾을 수 없음)}"
# 4) 모델 파일에서 필드 목록 출력 (상위 200줄)
if [ -f "$model_file" ]; then
echo "🔍 $model_file 필드 목록 (처음 200줄)"
sed -n '1,200p' "$model_file"
else
echo "⚠️ 데이터 클래스 파일을 찾을 수 없습니다."
fi
else
echo "⚠️ animal 파라미터 타입을 추출하지 못했습니다."
fiLength of output: 891
🏁 Script executed:
#!/usr/bin/env bash
set -e
# happenLocation 필드가 정의된 데이터 클래스 위치 및 주변 필드 확인
echo "🔍 happenLocation 필드 정의 위치 및 주변 필드 출력"
rg -nC2 "val happenLocation" -n app/src/main/java/com/example/finduLength of output: 1024
HomeReportedAnimalCard 중복 happenLocation 제거 필요
현재 ‘보호소 센터’ 아이콘 아래와 ‘위치’ 아이콘 아래 모두 animal.happenLocation을 표시하고 있어 정보가 완전히 중복됩니다. 도메인 모델(HomeData)에는 센터명(careName)이나 센터주소(careAddress) 필드가 정의되어 있지 않으므로, 첫 번째 Row는 제거하는 편이 자연스럽습니다.
수정 제안 (app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt):
@@ Lines 74–96
- Row(verticalAlignment = Alignment.CenterVertically) {
- BaseVectorIcon(vectorResource = R.drawable.ic_reported_card_center_18)
- Spacer(modifier = Modifier.width(7.dp))
- Text(
- text = animal.happenLocation,
- style = FindUTheme.typography.captionRegular12,
- color = FindUTheme.colors.gray5,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
- Spacer(modifier = Modifier.height(2.dp))필요 시 도메인 모델에 careName/careAddress 필드를 추가한 뒤, 첫 번째 Row를 센터 정보용으로 재구성하시면 됩니다.
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeReportedAnimalCard.kt
around lines 74 to 96, the code renders animal.happenLocation twice (once under
the center icon and once under the location icon), causing duplicate
information; remove the first Row (the one with ic_reported_card_center_18 and
its Text) so only the location Row remains, or alternatively if you plan to
display center info later, replace that Row's Text with a new
careName/careAddress field after adding it to the HomeData domain model.
| Icon( | ||
| painter = painterResource(R.drawable.img_findu_text_logo), | ||
| contentDescription = null, | ||
| modifier = Modifier | ||
| .width(72.dp) | ||
| .noRippleClickable(alarmButtonClicked), | ||
| tint = FindUTheme.colors.mainColor | ||
| ) |
There was a problem hiding this comment.
알림 콜백이 로고에 연결되어 있고, 종 아이콘은 클릭이 비활성화되어 있습니다.
UI 의미에 맞게 콜백 배치를 교정해주세요. 현재 로고 클릭 시 알림 콜백이 호출되고, 실제 알림(종) 아이콘은 클릭이 되지 않습니다.
아래처럼 로고의 클릭 제거 + 종 아이콘에 클릭 부여를 제안합니다.
Icon(
painter = painterResource(R.drawable.img_findu_text_logo),
contentDescription = null,
modifier = Modifier
- .width(72.dp)
- .noRippleClickable(alarmButtonClicked),
+ .width(72.dp),
tint = FindUTheme.colors.mainColor
)
Spacer(modifier = Modifier.weight(1f))
Icon(
painter = painterResource(R.drawable.ic_home_top_bar_bell_24),
- contentDescription = null,
- tint = Color.Unspecified,
+ contentDescription = "알림",
+ modifier = Modifier.noRippleClickable(alarmButtonClicked),
+ tint = Color.Unspecified,
)Also applies to: 47-51
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/home/component/HomeTopBar.kt
around lines 38-45 (and similarly lines 47-51), the alarm callback is
incorrectly attached to the logo and the bell icon is not clickable; remove the
noRippleClickable(alarmButtonClicked) (or any click handler) from the logo/Icon
block and instead add a clickable/noRippleClickable wrapper or modifier to the
bell (alarm) icon block so the alarmButtonClicked callback is invoked when the
bell is tapped; ensure the logo remains non-interactive and the bell icon
receives the click modifier only.
| fun HomeScreen( | ||
| uiState: HomeUiState = HomeUiState(), | ||
| reportButtonClicked: () -> Unit, | ||
| alarmButtonClicked: () -> Unit, | ||
| homeReportData: HomeReportData, | ||
| navigateToProtectDetail: (ProtectAnimal) -> Unit, | ||
| navigateToReportDetail: (ReportAnimal) -> Unit, | ||
| indicatorClicked: (HomeReportDurationType) -> Unit, | ||
| navigationToSearch: () -> Unit, | ||
| userNickname: String, | ||
| modifier: Modifier = Modifier, | ||
| innerPaddingValues: PaddingValues = PaddingValues(0.dp) | ||
| ) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
상단 버튼 콜백을 HomeScreen API에 노출해 전달하세요.
위 수정과 함께 HomeScreen 시그니처에 콜백을 추가하면 미리보기는 기본값으로 유지하면서 실제 화면에서는 동작하도록 만들 수 있습니다.
fun HomeScreen(
uiState: HomeUiState = HomeUiState(),
reportButtonClicked: () -> Unit,
alarmButtonClicked: () -> Unit,
homeReportData: HomeReportData,
navigateToProtectDetail: (ProtectAnimal) -> Unit,
navigateToReportDetail: (ReportAnimal) -> Unit,
indicatorClicked: (HomeReportDurationType) -> Unit,
navigationToSearch: () -> Unit,
userNickname: String,
modifier: Modifier = Modifier,
- innerPaddingValues: PaddingValues = PaddingValues(0.dp)
+ innerPaddingValues: PaddingValues = PaddingValues(0.dp),
+ navigateToProtectCenter: () -> Unit = {},
+ navigateToHospital: () -> Unit = {},
+ navigateToProtectPart: () -> Unit = {},
+ navigateToVolunteer: () -> Unit = {}
)📝 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 HomeScreen( | |
| uiState: HomeUiState = HomeUiState(), | |
| reportButtonClicked: () -> Unit, | |
| alarmButtonClicked: () -> Unit, | |
| homeReportData: HomeReportData, | |
| navigateToProtectDetail: (ProtectAnimal) -> Unit, | |
| navigateToReportDetail: (ReportAnimal) -> Unit, | |
| indicatorClicked: (HomeReportDurationType) -> Unit, | |
| navigationToSearch: () -> Unit, | |
| userNickname: String, | |
| modifier: Modifier = Modifier, | |
| innerPaddingValues: PaddingValues = PaddingValues(0.dp) | |
| ) { | |
| fun HomeScreen( | |
| uiState: HomeUiState = HomeUiState(), | |
| reportButtonClicked: () -> Unit, | |
| alarmButtonClicked: () -> Unit, | |
| homeReportData: HomeReportData, | |
| navigateToProtectDetail: (ProtectAnimal) -> Unit, | |
| navigateToReportDetail: (ReportAnimal) -> Unit, | |
| indicatorClicked: (HomeReportDurationType) -> Unit, | |
| navigationToSearch: () -> Unit, | |
| userNickname: String, | |
| modifier: Modifier = Modifier, | |
| innerPaddingValues: PaddingValues = PaddingValues(0.dp), | |
| navigateToProtectCenter: () -> Unit = {}, | |
| navigateToHospital: () -> Unit = {}, | |
| navigateToProtectPart: () -> Unit = {}, | |
| navigateToVolunteer: () -> Unit = {} | |
| ) { | |
| // … | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/home/composeview/HomeScreen
around lines 55 to 67, the composable currently lacks callbacks for the top
buttons so previews use defaults but the real screen cannot react; add
parameters for the top button callbacks (e.g., onBackClicked, onSettingsClicked
or whichever top buttons exist) to the HomeScreen signature with default empty
implementations so previews remain unchanged, and update actual call sites
(navigation container / activity) to pass concrete implementations that perform
navigation or actions.
| HomeReportedAnimalList( | ||
| nickname = userNickname, | ||
| navigationToSearch = navigationToSearch, | ||
| animalCards = uiState.homeData!!.reportAnimalCards, | ||
| navigateToReportDetail = navigateToReportDetail | ||
| ) |
There was a problem hiding this comment.
NPE 위험: uiState.homeData!! 강제 언래핑 (Reported 리스트도 동일)
위와 동일한 이슈입니다. 안전 호출로 교체를 권장합니다.
- HomeReportedAnimalList(
+ HomeReportedAnimalList(
nickname = userNickname,
navigationToSearch = navigationToSearch,
- animalCards = uiState.homeData!!.reportAnimalCards,
+ animalCards = uiState.homeData?.reportAnimalCards.orEmpty(),
navigateToReportDetail = navigateToReportDetail
)📝 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.
| HomeReportedAnimalList( | |
| nickname = userNickname, | |
| navigationToSearch = navigationToSearch, | |
| animalCards = uiState.homeData!!.reportAnimalCards, | |
| navigateToReportDetail = navigateToReportDetail | |
| ) | |
| HomeReportedAnimalList( | |
| nickname = userNickname, | |
| navigationToSearch = navigationToSearch, | |
| animalCards = uiState.homeData?.reportAnimalCards.orEmpty(), | |
| navigateToReportDetail = navigateToReportDetail | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/home/composeview/HomeScreen.kt
around lines 171 to 176, the code force-unwraps uiState.homeData!! (and
similarly for the reported lists), which risks an NPE; change these to safe
calls and provide a sensible fallback (for example use
uiState.homeData?.reportAnimalCards ?: emptyList() or early-return when homeData
is null) so the composable receives a non-null list without using !! and handle
other occurrences of !! on reported lists the same way.
| private fun openWebLink(url: String) { | ||
| val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) | ||
| requireActivity().startActivity(intent) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
외부 링크 열기 안전성 보강 (ActivityNotFoundException 처리)
디바이스에 핸들러가 없으면 크래시 납니다. 예외 처리와 간단한 피드백을 추가하세요.
private fun openWebLink(url: String) {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
- requireActivity().startActivity(intent)
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ try {
+ requireActivity().startActivity(intent)
+ } catch (e: Exception) { // ActivityNotFoundException 포함
+ Toast.makeText(requireContext(), "열 수 있는 앱이 없습니다.", Toast.LENGTH_SHORT).show()
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private fun openWebLink(url: String) { | |
| val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) | |
| requireActivity().startActivity(intent) | |
| } | |
| private fun openWebLink(url: String) { | |
| val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) | |
| try { | |
| requireActivity().startActivity(intent) | |
| } catch (e: Exception) { // ActivityNotFoundException 포함 | |
| Toast.makeText(requireContext(), "열 수 있는 앱이 없습니다.", Toast.LENGTH_SHORT).show() | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/presentation/ui/home/HomeFragment.kt
around lines 180 to 183, the openWebLink function starts an ACTION_VIEW intent
directly which can crash with ActivityNotFoundException on devices without a
handler; wrap the startActivity call in a try-catch for
ActivityNotFoundException (or first check with
requireActivity().packageManager.resolveActivity(intent, 0) != null) and on
failure show a brief user feedback (Toast or Snackbar) indicating no app is
available to open the link, avoiding the crash and keeping behavior graceful.
| private fun refreshData() { | ||
| viewModelScope.launch { | ||
| _uiState.value = _uiState.value.copy(isRefreshing = true) | ||
|
|
||
| homeUseCase().fold( | ||
| onSuccess = { data -> | ||
| _uiState.value = _uiState.value.copy( | ||
| loadState = LoadState.Success, | ||
| homeData = data, | ||
| errorMessage = null, | ||
| isRefreshing = false | ||
| ) | ||
| }, | ||
| onFailure = { error -> | ||
| _uiState.value = _uiState.value.copy( | ||
| loadState = LoadState.Error, | ||
| errorMessage = error.message ?: "데이터를 새로고침하는 중 오류가 발생했습니다.", | ||
| isRefreshing = false | ||
| ) | ||
| } | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
refresh 시 homeReportData가 갱신되지 않아 UI 데이터가 stale 상태로 남습니다
loadHomeData()에서는 homeReportData를 갱신하지만 refreshData()에선 누락되어 있습니다. 카드에 표시되는 통계가 업데이트되지 않을 수 있습니다.
onSuccess = { data ->
_uiState.value = _uiState.value.copy(
loadState = LoadState.Success,
homeData = data,
+ homeReportData = with(homeUseCase) {
+ data.toHomeReportData()
+ },
errorMessage = null,
isRefreshing = false
)
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private fun refreshData() { | |
| viewModelScope.launch { | |
| _uiState.value = _uiState.value.copy(isRefreshing = true) | |
| homeUseCase().fold( | |
| onSuccess = { data -> | |
| _uiState.value = _uiState.value.copy( | |
| loadState = LoadState.Success, | |
| homeData = data, | |
| errorMessage = null, | |
| isRefreshing = false | |
| ) | |
| }, | |
| onFailure = { error -> | |
| _uiState.value = _uiState.value.copy( | |
| loadState = LoadState.Error, | |
| errorMessage = error.message ?: "데이터를 새로고침하는 중 오류가 발생했습니다.", | |
| isRefreshing = false | |
| ) | |
| } | |
| ) | |
| } | |
| } | |
| private fun refreshData() { | |
| viewModelScope.launch { | |
| _uiState.value = _uiState.value.copy(isRefreshing = true) | |
| homeUseCase().fold( | |
| onSuccess = { data -> | |
| _uiState.value = _uiState.value.copy( | |
| loadState = LoadState.Success, | |
| homeData = data, | |
| homeReportData = with(homeUseCase) { | |
| data.toHomeReportData() | |
| }, | |
| errorMessage = null, | |
| isRefreshing = false | |
| ) | |
| }, | |
| onFailure = { error -> | |
| _uiState.value = _uiState.value.copy( | |
| loadState = LoadState.Error, | |
| errorMessage = error.message | |
| ?: "데이터를 새로고침하는 중 오류가 발생했습니다.", | |
| isRefreshing = false | |
| ) | |
| } | |
| ) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt
around lines 134 to 156, refreshData currently updates loadState, homeData, and
errorMessage but fails to update homeReportData causing the UI to show stale
report statistics; update the success branch to also set homeReportData =
data.homeReportData (or the appropriate field from the returned data model) when
copying _uiState, and ensure isRefreshing is set false as already done so the
report cards reflect the refreshed values.
| <string name="home_bottom_part">동물보호\n부서 조회</string> | ||
| <string name="home_banner_title">오늘도 찾아유랑 1분 지식 충전🔥</string> | ||
| <string name="home_adoptable_list_title">입양 가능 동물</string> | ||
| <string name="home_adoptable_list_description">%1$s 님의 주변 보호소에서 입양가능한 동물들이에요.</string> |
There was a problem hiding this comment.
맞춤법/띄어쓰기 수정 제안: ‘입양가능한’ → ‘입양 가능한’
사용자 노출 문자열의 품질 이슈입니다. 한국어 표준 띄어쓰기에 따라 ‘입양 가능한’으로 수정해주세요.
- <string name="home_adoptable_list_description">%1$s 님의 주변 보호소에서 입양가능한 동물들이에요.</string>
+ <string name="home_adoptable_list_description">%1$s 님의 주변 보호소에서 입양 가능한 동물들이에요.</string>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/res/values/strings.xml around line 227, the displayed Korean
string uses incorrect spacing "입양가능한"; update the text to use standard spacing
"입양 가능한" so the string becomes "%1$s 님의 주변 보호소에서 입양 가능한 동물들이에요." and ensure XML
escaping and formatting remain intact.
| material = "1.12.0" | ||
| activity = "1.9.3" | ||
| constraintlayout = "2.2.0" | ||
| accompanistPager = "0.25.0" |
There was a problem hiding this comment.
Accompanist Pager 버전 호환성 문제 — Compose BOM과 미스매치
현재 accompanistPager = "0.25.0"은 매우 구버전으로, Compose BOM 2024.04.01(Compose 1.6.x 계열)과 호환되지 않을 가능성이 큽니다. 컴파일/런타임 이슈가 발생할 수 있어요. 최소 0.34.x 계열로 올리거나, 더 나은 대안으로 공식 Foundation Pager(Compose 1.4+ 내장)로 마이그레이션을 권장합니다.
- 빠른 해결: Accompanist 버전 업그레이드
-[versions]
-accompanistPager = "0.25.0"
+[versions]
+accompanistPager = "0.34.0"- 더 나은 대안: androidx.compose.foundation.pager.HorizontalPager/VerticalPager 사용으로 Accompanist 의존성 제거
- accompanist-pager 및 accompanist-pager-indicators 항목/번들을 제거
- 코드에서
import com.google.accompanist.pager.*→import androidx.compose.foundation.pager.*로 변경 - 지표(indicator)는 Foundation TabRow/커스텀 인디케이터로 대체 가능
추가로 bundles.pager에 paging-common-android가 함께 묶여 있어 개념적으로 혼동됩니다. UI Pager와 데이터 Paging은 별도 번들로 분리하는 편이 유지보수에 유리합니다.
Also applies to: 56-57, 107-111
🤖 Prompt for AI Agents
In gradle/libs.versions.toml around line 17 (also applies to lines 56-57 and
107-111), accompanistPager = "0.25.0" is too old for Compose BOM 2024.04.01
(Compose 1.6.x); update the project by either bumping Accompanist to a
compatible 0.34.x+ release across the libs file and any bundles using
accompanist-pager/accompanist-pager-indicators, or migrate to the built-in
Foundation pager APIs (replace accompanist imports/usages with
androidx.compose.foundation.pager.* and remove accompanist pager dependencies
and bundles); also separate UI pager bundles from paging-common-android into
distinct bundles to avoid conceptual confusion.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt (2)
3-3: 도메인 모델에 UI 리소스 의존성(@ColorRes/R) 혼입 — 프레젠테이션 레이어로 분리 권장
domain.model계층의 enum이 Android 리소스(R.color,@ColorRes)에 의존하면 재사용성/테스팅/모듈 분리가 어려워집니다. 색상·라벨은 UI 관심사이므로, 도메인은 상태 식별만 유지하고 프레젠테이션에서 매핑하는 구성이 더 깔끔합니다.
- 제안: Presentation에 전용 UI 모델/매퍼를 두세요.
- UI 모델 예:
SearchStatusUi(@StringRes labelRes, @ColorRes textColorRes, @ColorRes backgroundColorRes)- 매퍼 예:
data class SearchStatusUi( @StringRes val labelRes: Int, @ColorRes val textColorRes: Int, @ColorRes val backgroundColorRes: Int, ) fun SearchStatus.toUi(): SearchStatusUi = when (this) { SearchStatus.PROTECTING -> SearchStatusUi(R.string.status_protecting, R.color.green1, R.color.green2) SearchStatus.WITNESS -> SearchStatusUi(R.string.status_witness, R.color.blue1, R.color.blue2) SearchStatus.MISSING -> SearchStatusUi(R.string.status_missing, R.color.red1, R.color.red2) SearchStatus.UNKNOWN -> SearchStatusUi(R.string.status_unknown, R.color.white, R.color.gray2) }- 장점: 테마/다크모드 대응이 쉬워지고, 도메인이 Android에 묶이지 않습니다.
7-15: 하드코딩된 한글 라벨 → @stringres로 전환 권장라벨 텍스트가 코드에 하드코딩되어 있어 다국어/접근성/번들 최적화 측면에서 불리합니다. 라벨은 string 리소스로 분리하고
@StringRes참조로 전환하는 것을 권장합니다.아래는 enum 시그니처와 상수 초기화를 리소스 기반으로 바꾸는 예시입니다.
enum class SearchStatus( - val text: String, + @StringRes val textRes: Int, @ColorRes val textColorRes: Int, @ColorRes val backgroundColorRes: Int ) : Serializable { - PROTECTING("보호중", R.color.green1, R.color.green2), - WITNESS("목격신고", R.color.blue1, R.color.blue2), - MISSING("실종신고", R.color.red1, R.color.red2), - UNKNOWN("기본", R.color.white, R.color.gray2) + PROTECTING(R.string.status_protecting, R.color.green1, R.color.green2), + WITNESS(R.string.status_witness, R.color.blue1, R.color.blue2), + MISSING(R.string.status_missing, R.color.red1, R.color.red2), + UNKNOWN(R.string.status_unknown, R.color.white, R.color.gray2) }enum 상단에는 아래 import 추가가 필요합니다.
import androidx.annotation.StringRes참고: 위 string 리소스 키는 예시입니다. 실제 프로젝트 네이밍 컨벤션에 맞춰 values/strings.xml(및 values-ko/values-en 등)에 정의해 주세요.
또한, 앞선 코멘트처럼 라벨/컬러 자체를 UI 계층의 매퍼로 옮기는 것도 좋은 대안입니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt(1 hunks)
🔇 Additional comments (1)
app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt (1)
9-10: 네이밍 명확성 향상: textColorRes/backgroundColorRes 👍리소스 타입을 드러내는 명명과
@ColorRes어노테이션으로 오용 여지를 줄였습니다. 일관성 측면에서도 좋습니다.
| PROTECTING("보호중", R.color.green1, R.color.green2), | ||
| WITNESS("목격신고", R.color.blue1, R.color.blue2), | ||
| MISSING("실종신고", R.color.red1, R.color.red2), | ||
| UNKNOWN("기본", R.color.white, R.color.gray2) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
색 대비/다크 모드 점검 요청 — UNKNOWN의 white on gray2는 대비 부족 가능성
현재 페어링(예: UNKNOWN의 white 텍스트 + gray2 배경)은 WCAG 대비(권장 4.5:1) 미달 가능성이 있습니다. 다크모드 팔레트와 함께 실제 대비를 한번 확인해 주세요. 미달 시:
- 텍스트 컬러를 배경 대비 높은 색으로 조정하거나
- MaterialTheme의 on-color 페어링(예: onPrimary/onSurface)로 매핑하는 것을 권장합니다.
아래 스크립트로 values/values-night에서 각 상태의 텍스트/배경 대비비를 계산해 볼 수 있습니다.
- 기대 결과: 일반 텍스트는 4.5:1 이상 권장. 미달 항목은 텍스트 컬러(or 배경 톤)를 조정하거나 onColor 매핑으로 전환해 주세요.
[suggest_optional_refactor]
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 대상 상태와 페어링 정의
declare -A TEXTS BACKS
TEXTS[PROTECTING]="green1"; BACKS[PROTECTING]="green2"
TEXTS[WITNESS]="blue1"; BACKS[WITNESS]="blue2"
TEXTS[MISSING]="red1"; BACKS[MISSING]="red2"
TEXTS[UNKNOWN]="white"; BACKS[UNKNOWN]="gray2"
# colors.xml/values-night/colors.xml 경로 탐색
VALS=$(fd -a -t f 'colors.xml' app/src/main/res/values | head -n1 || true)
NIGHT=$(fd -a -t f 'colors.xml' app/src/main/res/values-night | head -n1 || true)
python - << 'PY'
import os, re, sys, math, json
def parse_colors(path):
if not path or not os.path.exists(path):
return {}
txt = open(path, encoding='utf-8').read()
pairs = re.findall(r'<color[^>]*name=[\'"]([^\'"]+)[\'"][^>]*>(#[0-9A-Fa-f]{6,8})</color>', txt)
return {k:v for k,v in pairs}
def hex_to_rgb(hexs):
hexs = hexs.lstrip('#')
if len(hexs)==8: hexs = hexs[2:] # AARRGGBB -> RRGGBB
r = int(hexs[0:2],16)/255.0
g = int(hexs[2:4],16)/255.0
b = int(hexs[4:6],16)/255.0
return (r,g,b)
def lin(c): return ((c+0.055)/1.055)**2.4 if c>0.04045 else c/12.92
def luminance(rgb):
r,g,b = map(lin, rgb)
return 0.2126*r + 0.7152*g + 0.0722*b
def contrast_ratio(fg, bg):
L1 = luminance(hex_to_rgb(fg))
L2 = luminance(hex_to_rgb(bg))
lmax, lmin = (L1,L2) if L1>=L2 else (L2,L1)
return (lmax + 0.05) / (lmin + 0.05)
vals = os.environ.get('VALS','')
night = os.environ.get('NIGHT','')
colors_day = parse_colors(vals)
colors_night = parse_colors(night)
pairs = {
"PROTECTING": ("green1","green2"),
"WITNESS": ("blue1","blue2"),
"MISSING": ("red1","red2"),
"UNKNOWN": ("white","gray2"),
}
def report(colors, label):
print(f"== {label} ==")
for k,(fg,bg) in pairs.items():
if fg in colors and bg in colors:
c = contrast_ratio(colors[fg], colors[bg])
print(f"{k:10s}: {fg} on {bg} -> contrast {c:.2f}")
else:
missing = [x for x in (fg,bg) if x not in colors]
print(f"{k:10s}: missing colors: {missing}")
report(colors_day, "values/colors.xml")
if colors_night:
report(colors_night, "values-night/colors.xml")
else:
print("== values-night/colors.xml not found ==")
PYLength of output: 695
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate colors.xml in values
FILE=$(fd -a -t f colors.xml app/src/main/res/values | head -n1)
echo "Inspecting $FILE"
# Show the first 200 lines
sed -n '1,200p' "$FILE"
# Search for relevant color entries
echo "--- grep 'green' ---"
grep -n "green" "$FILE" || true
echo "--- grep 'blue' ---"
grep -n "blue" "$FILE" || true
echo "--- grep 'red' ---"
grep -n "red" "$FILE" || true
echo "--- grep 'white' ---"
grep -n "white" "$FILE" || true
echo "--- grep 'gray2' ---"
grep -n "gray2" "$FILE" || true
echo "--- All <color> tags ---"
grep -n "<color" "$FILE"Length of output: 3011
필수: UNKNOWN 상태 텍스트 대비 개선 필요
app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt (Lines 12–15)
현재 UNKNOWN("기본", R.color.white, R.color.gray2) 의 대비비는 약 1.09:1로 WCAG AA(최소 4.5:1) 미달입니다.
또한 values-night/colors.xml이 없어 다크모드에서도 동일한 색이 적용될 것으로 예상됩니다.
조치 사항(하나 이상 선택):
R.color.white대신 충분히 어두운(onSurface 등) 색상 사용- MaterialTheme의 onPrimary/onSurface 매핑으로 대체
- 다크모드 전용
values-night/colors.xml에 적절한 텍스트·배경 색 추가 및 대비 재검토
이 이슈는 접근성 관점에서 반드시 해결되어야 합니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/model/search/SearchStatus.kt
around lines 12 to 15, the UNKNOWN entry uses R.color.white with R.color.gray2
producing ~1.09:1 contrast which fails WCAG AA; update the implementation to use
an accessible color mapping: either replace R.color.white with a sufficiently
dark color (or a semantic color like onSurface/onPrimary) so text vs background
reaches at least 4.5:1, or map colors via MaterialTheme semantic colors
(onSurface/onPrimary) instead of hardcoded white, and add a
values-night/colors.xml defining appropriate night-mode text/background colors;
after changes, verify contrast ratio >=4.5:1 in both light and dark themes and
update references in SearchStatus to use the new semantic/dark-aware colors.
nasohee
left a comment
There was a problem hiding this comment.
코리 안달은지 몰라서 늦게 답니다 죄송합니다......
확실히 xml보다 컴포즈 쓰는게 간편해보이네요... 이거보다가 내 코드보니까 너무 더러움ㅠㅠ
Related issue 🛠
Work Description 📝
새로 디자인된 홈의 UI를 구현합니다
Screenshot 📸
홈 뷰
Screen_recording_20250707_181332.mp4
신고 접수 동물 리스트(서버에서 안내려와서 Preview로 대체)
2025-07-07.5.47.23.mov
Uncompleted Tasks 😅
To Reviewers 📢
나중에 합류하게 되어 변수나 함수 네이밍이 기존에 사용하던것과 다를 수 있습니다! 최대한 기존 네이밍을 따르려 했지만 놓친 부분이 있을 수 있으니 코멘트 남겨주시면 수정하도록 하겠습니다!
+아직 서버에서 내려오지 않는 값들은 띄우지 않기 때문에 프리뷰 확인해보시면 좋을 것 같습니다!
Summary by CodeRabbit