Skip to content

Feat/migration compose UI#20

Open
seunghee17 wants to merge 121 commits intomainfrom
feat/migration_compose_ui
Open

Feat/migration compose UI#20
seunghee17 wants to merge 121 commits intomainfrom
feat/migration_compose_ui

Conversation

@seunghee17
Copy link
Owner

@seunghee17 seunghee17 commented Oct 6, 2025

Summary by CodeRabbit

  • 신기능

    • 통합 런처 스플래시 화면 및 Compose 기반 내비게이션 도입.
    • 공고 작성 온보딩/메인/완료 화면 추가 및 이력서 탭형 Compose 흐름(이미지 업로드·경력 추가·지역 선택) 제공.
    • 주소검색 WebView 연동, 확인 다이얼로그 및 개선된 바텀시트(헤더/본문/푸터) 추가.
    • 원격 지역 데이터 가져오기 및 로컬 DB 저장 기능 추가.
  • 개선

    • 내비게이션/시작 흐름 정비, 버튼 패딩과 날짜 선택 UX 개선, 로깅·크래시 수집 강화.
  • 리팩터

    • 여러 화면과 어댑터를 Compose로 전환하고 불필요한 레거시 화면 제거.
  • 테스트

    • 지역 로컬 DB 동작 검증용 Android 인스트루먼트 테스트 추가.

seunghee17 and others added 30 commits September 3, 2025 22:58
…feat/migration_compose_ui

# Conflicts:
#	app/src/main/java/com/capstone/nongglenonggle/data/model/worker/ResumeStep2State.kt
#	app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeActivity.kt
#	app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/compose_integration/WorkerResumeComposeViewModel.kt
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/home/FarmerhomeFragment.kt (1)

108-108: 인덱스 접근 시 범위 확인을 추가하세요.

categoryItem.get(0)은 리스트가 비어있을 경우 크래시를 발생시킬 수 있습니다.

다음 diff를 적용하여 안전하게 접근하세요:

-            when(noticeContent.categoryItem.get(0)){
+            when(noticeContent.categoryItem.getOrNull(0)){
                 "식량작물" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_rice)
                 "채소" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_greens)
                 "과수" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_fruite)
                 "특용작물" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_cashcrop)
                 "화훼" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_flower)
                 "축산" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_animal)
                 "농기계작업" -> binding.yesNotice.image.setImageResource(R.drawable.img_offer_car)
                 else-> binding.yesNotice.image.setImageResource(R.drawable.img_offer_etc)
             }
app/build.gradle (2)

4-60: Compose 플러그인과 컴파일러 버전을 같은 세대로 맞춰주세요.

org.jetbrains.kotlin.plugin.compose 2.0.20은 Kotlin 2.0.20 전용인데 kotlinCompilerExtensionVersion = "1.7.1"은 2.0.10까지만 지원합니다. 그대로 두면 Gradle sync 단계에서 “Incompatible Kotlin version” 오류로 빌드가 중단됩니다. Compose 호환표에 맞춰 2.0.20을 지원하는 컴파일러(예: 1.7.20 계열 이상이 공개됐다면 그 버전)로 올리거나, 플러그인을 2.0.10 이하로 낮춰 두 값을 동일 세대로 정렬해 주세요.


67-142: Room 아티팩트 버전을 한꺼번에 2.8.x로 통일해야 합니다.

room-runtime-android:2.8.0room-runtime/room-ktx/room-compiler:2.5.0을 동시에 넣으면 동일 패키지의 서로 다른 버전이 classpath에 올라 빌드 타임·런타임 중복 클래스 충돌이 납니다. 사용 의도를 결정해 모두 2.8.0 라인으로 정렬하고, 불필요한 2.5.0 의존성은 제거해 주세요.

♻️ Duplicate comments (19)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/component/NonggleDropDown.kt (1)

27-30: 인덱스 범위 검증이 여전히 누락되었습니다.

이전 리뷰에서 지적된 IndexOutOfBoundsException 위험이 아직 해결되지 않았습니다. items[selectedIndex] 접근 전에 범위 검증을 추가해야 합니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashViewModel.kt (1)

30-37: UserType.valueOf의 예외 처리를 추가하세요.

UserType.valueOf(it.signUpType)signUpType이 유효하지 않은 값일 경우 IllegalArgumentException을 발생시킬 수 있습니다. 이 예외가 처리되지 않으면 앱이 크래시됩니다.

다음과 같이 예외 처리를 추가하세요:

             getUserAuthDataRepositoryUseCase.invoke()
                 .onSuccess {
-                    val userType = UserType.valueOf(it.signUpType)
-                    when (userType) {
-                        UserType.WORKER -> {
-                            postEffect(SplashContract.Effect.NavigateToWorkerHome)
-                        }
-                        else -> {
-                            postEffect(SplashContract.Effect.NavigateToLogin)
-                        }
+                    try {
+                        val userType = UserType.valueOf(it.signUpType)
+                        when (userType) {
+                            UserType.WORKER -> {
+                                postEffect(SplashContract.Effect.NavigateToWorkerHome)
+                            }
+                            else -> {
+                                postEffect(SplashContract.Effect.NavigateToLogin)
+                            }
+                        }
+                    } catch (e: IllegalArgumentException) {
+                        postEffect(SplashContract.Effect.NavigateToLogin)
                     }
                 }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeCompleteActivity.kt (3)

50-65: 주석 처리된 RecyclerView 코드를 정리해 주세요

RecyclerView 어댑터 관련 주석 코드가 여전히 남아 있습니다. Compose로 전환된 만큼 해당 블록은 완전히 삭제해 코드 가독성과 유지보수성을 확보하는 편이 좋습니다.


158-228: 대량의 주석 처리된 Composable 블록을 삭제해 주세요

careerItem 전체가 주석 상태로 남아 있어 파일 가독성을 떨어뜨립니다. 필요 시 Git 히스토리에서 복구 가능하니 과감히 제거하는 것을 권장합니다.


233-239: dp/px 혼용으로 점 크기가 지나치게 작아집니다

6.dp 박스 안에서 radius를 3f(px)로 고정하면 고해상도 단말에서 점이 거의 보이지 않습니다. size.minDimension / 2f처럼 실제 픽셀 크기를 사용해 주세요.

             .drawBehind {
-                drawCircle(
-                    color = color,
-                    radius = 3f
-                )
+                val radiusPx = size.minDimension / 2f
+                drawCircle(
+                    color = color,
+                    radius = radiusPx
+                )
             }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Screen.kt (1)

3-105: 보안 예외 처리 누락으로 인한 크래시 위험

takePersistableUriPermission은 권한이 없는 URI에 대해 SecurityException을 던져 화면이 그대로 크래시납니다. 지난 리뷰에서도 언급됐던 부분이라 try-catch로 감싸고 실패 시 로그만 남긴 뒤 흐름을 이어가 주세요.

+import timber.log.Timber
 ...
     val pickerLauncher = rememberLauncherForActivityResult(
         contract = ActivityResultContracts.PickVisualMedia()
     ) { uri ->
-        if (uri != null) {
-            context.contentResolver.takePersistableUriPermission(
-                uri,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION
-            )
-        }
-        uri?.let { onEvent(Step1Event.GetImageFromGallery(uri)) }
+        uri?.let {
+            try {
+                context.contentResolver.takePersistableUriPermission(
+                    it,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+                )
+            } catch (se: SecurityException) {
+                Timber.w(se, "Failed to take persistable URI permission")
+            }
+            onEvent(Step1Event.GetImageFromGallery(it))
+        }
     }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2ViewModel.kt (2)

74-123: 필수 값 검증이 없어서 동일한 크래시가 그대로 남아 있습니다
이전 리뷰에서 지적된 대로 시작·종료일 또는 근무 일수가 비어 있으면 Period.between/toInt()에서 바로 죽습니다. parsingCareerItem()이 null을 반환하도록 바꾸고, AddCareerItem에서 null이면 추가를 막고 오류 이펙트를 노출해 주세요.

예시 수정:

             is Step2Event.AddCareerItem -> {
-                val newList = currentState.careerList.toMutableList()
-                newList.add(parsingCareerItem())
-                updateState(currentState.copy(careerList = newList))
-                sumTotalCareer()
+                val careerItem = parsingCareerItem() ?: run {
+                    postEffect(Step2Effect.ShowErrorToast("필수 항목을 모두 입력해 주세요."))
+                    return
+                }
+                val newList = currentState.careerList.toMutableList().apply { add(careerItem) }
+                updateState(currentState.copy(careerList = newList))
+                sumTotalCareer()
             }
 …
-    private fun parsingCareerItem(): UserCareerListItemModel {
+    private fun parsingCareerItem(): UserCareerListItemModel? {-        if(currentState.isLongerThenMonth == true) {
-            val periodDate = Period.between(currentState.careerStartDate, currentState.careerEndDate)
+        if (currentState.isLongerThenMonth == true) {
+            val startDate = currentState.careerStartDate ?: return null
+            val endDate = currentState.careerEndDate ?: return null
+            if (endDate.isBefore(startDate)) return null
+            val periodDate = Period.between(startDate, endDate)-        } else {
-            period = currentState.careerPeriodDay
-            periodDetail = currentState.careerPeriodDay
-            periodData = Period.ofDays(currentState.careerPeriodDay.filter { it.isDigit() }.toInt())
+        } else {
+            val dayText = currentState.careerPeriodDay.filter { it.isDigit() }
+            val days = dayText.toIntOrNull() ?: return null
+            period = "${days}일"
+            periodDetail = currentState.careerPeriodDay
+            periodData = Period.ofDays(days)
         }

81-84: 삭제 직후 총합이 이전 값으로 남습니다
sumTotalCareer()currentState.careerList에 의존하는데, 현재는 삭제 전에 호출되어 제거된 항목이 그대로 합산됩니다. 새 리스트로 상태를 갱신한 뒤 합계를 계산하도록 호출 순서를 바꿔 주세요.

예시 수정:

             is Step2Event.RemoveCareerItem -> {
-                val newList = currentState.careerList.toMutableList()
-                newList.remove(event.item)
-                sumTotalCareer()
-                updateState(currentState.copy(careerList = newList))
+                val newList = currentState.careerList.toMutableList().apply { remove(event.item) }
+                updateState(currentState.copy(careerList = newList))
+                sumTotalCareer()
             }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CalendarDialog.kt (1)

46-56: 초기 연도를 min/max 범위로 강제해야 합니다.

year 상태가 minDate/maxDate 범위 밖인 값으로 시작하면 SpinnerNumberPicker에 허용되지 않는 초기값이 전달되어 예외가 발생하거나 UI가 깨집니다. 초기/외부에서 전달된 날짜가 범위 밖일 수 있으므로, 연도 상태를 먼저 coerceIn으로 클램프해 주세요. 기존 리뷰에서도 동일한 문제가 지적되었지만 여전히 반영되지 않았습니다.

@@
-    var year by rememberSaveable(initialDate) { mutableStateOf(initialDate.year) }
+    var year by rememberSaveable(initialDate) { mutableStateOf(initialDate.year) }
     var month by rememberSaveable(initialDate) { mutableStateOf(initialDate.monthValue) }
 
     val minYear = minDate.year
     val maxYear = maxDate.year
+
+    year = year.coerceIn(minYear, maxYear)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupContract.kt (1)

47-51: Event에서 Context를 직접 보관하면 안 됩니다.

SaveUserInfoContext를 필드로 들고 있으면 ViewModel 이벤트 큐에 Activity/Fragment가 붙잡혀 메모리 누수와 생명주기 오류가 발생할 수 있습니다. Application Context를 주입해서 사용하거나, 필요한 원시 데이터만 이벤트로 넘기도록 구조를 바꿔 주세요.

app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/WorkerResumeRepositoryImpl.kt (1)

48-55: Storage 예외를 올바르게 매핑해주세요.

putFile/downloadUrlStorageException을 던지는데 FirebaseFirestoreException만 캐치하고 있어, 모든 Storage 오류가 Unknown으로 떨어집니다. Storage 권한/네트워크 오류를 정확히 분류하려면 StorageException을 먼저 처리해야 합니다. 아래처럼 수정해 주세요.

-            } catch (e: FirebaseFirestoreException) {
-                val failure = when (e.code) {
-                    PERMISSION_DENIED -> AppResult.Failure.PermissionDenied(e)
-                    UNAVAILABLE -> AppResult.Failure.NetworkError(e)
-                    else -> AppResult.Failure.Unknown(e)
-                }
+            } catch (e: StorageException) {
+                val failure = when (e.errorCode) {
+                    StorageException.ERROR_NOT_AUTHORIZED,
+                    StorageException.ERROR_NOT_AUTHENTICATED -> AppResult.Failure.PermissionDenied(e)
+                    StorageException.ERROR_RETRY_LIMIT_EXCEEDED,
+                    StorageException.ERROR_NETWORK_UNAVAILABLE -> AppResult.Failure.NetworkError(e)
+                    else -> AppResult.Failure.Unknown(e)
+                }
                 AppResultLogger.logFailure<WorkerResumeRepositoryImpl>(failure)
                 failure
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginViewModel.kt (2)

69-74: 로그인 타입 분기 로직을 안전하게 수정해야 합니다.

UserType.valueOf(result.data.signUpType)는 예상치 못한 문자열이 들어오면 그대로 크래시를 일으킵니다. 또한 FARMER 등 다른 타입을 모두 EnrollUser로 보내는 기존 문제도 그대로 남아 있습니다. 안전 파싱 후 사용자 유형별로 정확히 분기하도록 수정해 주세요.

-                    if (UserType.valueOf(result.data.signUpType) == UserType.WORKER) {
-                        postEffect(LoginEffect.NavigateToWorkerHome)
-                    } else {
-                        postEffect(LoginEffect.NavigateToEnrollUser)
-                    }
+                    val userType = runCatching {
+                        UserType.valueOf(result.data.signUpType)
+                    }.getOrNull()
+
+                    when (userType) {
+                        UserType.WORKER -> postEffect(LoginEffect.NavigateToWorkerHome)
+                        UserType.FARMER -> postEffect(LoginEffect.NavigateToFarmerHome)
+                        else -> postEffect(LoginEffect.NavigateToEnrollUser)
+                    }

76-79: 실패 시 바로 가입 화면으로 보내지 말고 오류를 안내해야 합니다.

인증 정보를 가져오지 못한 경우에도 NavigateToEnrollUser를 발행해 버리면, 단순 네트워크 오류인데도 사용자가 가입 과정으로 강제 이동합니다. 실패를 사용자에게 명확히 알리고 재시도를 유도하도록 처리해 주세요.

-                is AppResult.Failure -> {
-                    //postEffect(LoginEffect.ShowToastMessage("로그인을 다시 시도해주세요."))
-                    postEffect(LoginEffect.NavigateToEnrollUser)
-                }
+                is AppResult.Failure -> {
+                    postEffect(LoginEffect.ShowToastMessage("로그인 정보를 불러오지 못했습니다. 다시 시도해주세요."))
+                }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (1)

20-21: Compose 함수명을 PascalCase로 맞춰주세요.

googleLoginButton, kakaoLoginButton@Composable이므로 대문자로 시작해야 합니다. 컴포넌트 정의와 모든 호출부를 GoogleLoginButton, KakaoLoginButton으로 일관되게 변경해 주세요.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupViewModel.kt (1)

36-43: 지역 데이터 조회 실패 처리가 비어 있습니다.

getRegionUseCase가 실패해도 아무 액션이 없어 사용자는 무엇이 잘못됐는지 알 수 없습니다. 실패 시 로깅/토스트/재시도 트리거 등 최소한의 대응을 추가해 주세요.

app/build.gradle (3)

95-124: Coroutine 1.3.9는 Kotlin 2.0.x와 호환되지 않습니다.

메인(kotlinx-coroutines-android)과 테스트(kotlinx-coroutines-test) 모두 1.3.9라 Kotlin 2.0 기반 빌드에서 “module was compiled with an incompatible version of Kotlin” 오류가 발생합니다. 최소 1.8.x, 권장 1.9.0 이상으로 동시에 올려 동일 버전을 사용해 주세요.


32-32: BuildConfig LOCATION_URL은 미설정 시 안전하게 처리해야 합니다.

localProperties["LOCATION_URL"]가 비어 있으면 "null" 문자열이 그대로 BuildConfig에 들어가 런타임에서 잘못된 베이스 URL을 사용하게 됩니다. 존재 여부를 검사해 기본값을 부여하거나 명확한 예외로 빌드를 중단하도록 방어 로직을 추가해 주세요.


89-89: 구식 DataBinding 컴파일러 의존성을 제거해 주세요.

com.android.databinding:compiler:3.1.4는 AGP 3.x 전용으로, 최신 AGP와 함께 사용하면 kapt 단계에서 충돌하며 빌드가 실패합니다. 현대 AGP는 DataBinding 코드를 자체 생성하므로 이 의존성은 삭제해야 합니다.

app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/AuthenticationRepositoryImpl.kt (1)

52-55: CancellationException은 반드시 재전파해야 합니다.

CancellationExceptionAppResult.Failure.Cancelled로 감싸 반환하면 상위 코루틴이 취소 상태를 감지하지 못하고 작업이 계속됩니다. 취소는 정상 흐름이므로 그대로 throw e 하도록 바꿔 주세요.

🧹 Nitpick comments (9)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/component/NonggleDropDown.kt (1)

37-40: 프로세스 종료 시 상태 유지를 고려하세요.

현재 remember를 사용하고 있어 프로세스 종료(예: 백그라운드에서 시스템에 의한 앱 종료) 시 선택된 값이 유지되지 않습니다. 사용자 경험 개선이 필요한 경우 rememberSaveable을 사용하는 것을 고려해보세요.

다음과 같이 수정할 수 있습니다:

+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.saveable.Saver
+
+private val ExposedDropMenuStateHolderSaver = Saver<ExposedDropMenuStateHolder, List<Any>>(
+    save = { listOf(it.enabled, it.value, it.selectedIndex) },
+    restore = { 
+        ExposedDropMenuStateHolder().apply {
+            enabled = it[0] as Boolean
+            value = it[1] as String
+            selectedIndex = it[2] as Int
+        }
+    }
+)
+
 @Composable
-fun rememberExposedMenuStateHolder() = remember {
-    ExposedDropMenuStateHolder()
-}
+fun rememberExposedMenuStateHolder() = rememberSaveable(
+    saver = ExposedDropMenuStateHolderSaver
+) {
+    ExposedDropMenuStateHolder()
+}
app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1ViewModel.kt (2)

15-16: super.handleEvent 호출을 검토하세요.

Line 16에서 super.handleEvent(event)를 호출하고 있습니다. BaseViewModel에 기본 구현이 없다면 이 호출은 불필요할 수 있습니다.

BaseViewModelhandleEvent 구현을 확인하고, 기본 동작이 없다면 이 줄을 제거하는 것을 고려하세요:

 override fun handleEvent(event: NoticeStep1Contract.Event) {
-    super.handleEvent(event)
     when (event) {

26-27: 빈 else 분기를 검토하세요.

Line 26의 빈 else 분기는 NoticeStep1Contract.Event가 sealed class인 경우 불필요할 수 있습니다.

NoticeStep1Contract.Event가 sealed class이고 모든 하위 타입을 처리하고 있다면, 다음과 같이 else 분기를 제거할 수 있습니다:

             is Step1Event.ClearUserName -> {
                 updateState(currentState.copy(userName = ""))
             }
-
-            else -> {}
         }
     }

또는 향후 다른 이벤트가 추가될 예정이라면 현재 구현을 유지하세요.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/home/FarmerhomeFragment.kt (1)

42-44: 빈 클릭 리스너를 구현하거나 제거하세요.

onItemClickListener가 비어있어 클릭 이벤트가 처리되지 않습니다. 의도적으로 사용하지 않는다면 어댑터 구현을 수정하여 nullable 리스너를 지원하거나, 추후 구현할 계획이라면 TODO 주석을 추가하세요.

         val adapter = FilterFarmerHomeAdapter(emptyList(), object : FilterFarmerHomeAdapter.onItemClickListener{
             override fun onItemClickListener(uid: String) {
-
+                // TODO: 근로자 상세 화면으로 이동 구현 필요
             }
         }
         )
app/src/main/java/com/capstone/nongglenonggle/core/common/logger/AppResultLogger.kt (1)

10-66: 로그 메시지 구분자를 통일해 주세요.

상단 logFailure는 모든 메시지에 ":"를 사용하지만, 제네릭 확장 logFailure<T>는 유형에 따라 "-"":"가 섞여 있습니다. 로그 파서를 붙이거나 검색할 때 구분자가 섞여 있으면 패턴 매칭이 어려우니, 두 함수 전부에서 하나의 구분자만 사용하도록 정리해 주세요.

app/src/main/java/com/capstone/nongglenonggle/data/model/worker/UserCareerListItemModel.kt (1)

9-9: UUID 기본값 생성 방식을 검토하세요.

기본 파라미터에서 UUID.randomUUID()를 호출하면 각 인스턴스 생성 시마다 새로운 UUID가 생성됩니다. 이는 data class의 equals()hashCode() 동작에 영향을 줄 수 있으며, 동일한 데이터를 가진 두 객체가 서로 다른 것으로 간주될 수 있습니다.

생성자에서 ID를 생성하거나, ID 생성 로직을 별도 팩토리 메서드로 분리하는 것을 고려하세요.

 data class UserCareerListItemModel(
-    val id: String = UUID.randomUUID().toString(), // 고유 ID
+    val id: String, // 고유 ID
     val careerTitle: String = "",

그리고 생성 시점에 ID를 명시적으로 제공:

companion object {
    fun create(
        careerTitle: String = "",
        careerPeriodText: String = "",
        careerPeriodDetail: String = "",
        careerContent: String = "",
        careerPeriod: Period? = null
    ) = UserCareerListItemModel(
        id = UUID.randomUUID().toString(),
        careerTitle = careerTitle,
        careerPeriodText = careerPeriodText,
        careerPeriodDetail = careerPeriodDetail,
        careerContent = careerContent,
        careerPeriod = careerPeriod
    )
}
app/src/main/java/com/capstone/nongglenonggle/domain/repository/RegionRepository.kt (1)

5-9: 에러 핸들링을 위한 AppResult 래퍼를 고려하세요.

다른 리포지토리 인터페이스들이 AppResult<T>를 반환하는 것과 달리, 이 인터페이스는 List<String>을 직접 반환합니다. 이는 에러 상황과 빈 결과를 구분할 수 없게 만듭니다.

일관성을 위해 다른 리포지토리와 동일하게 AppResult<List<String>>을 반환하는 것을 권장합니다.

 interface RegionRepository {
-    suspend fun saveRegionToLocalDB(subRegions: List<RegionListModel>)
-    suspend fun getRegionList(): List<String>
-    suspend fun getSubRegionList(parentRegion: String): List<String>
+    suspend fun saveRegionToLocalDB(subRegions: List<RegionListModel>): AppResult<Unit>
+    suspend fun getRegionList(): AppResult<List<String>>
+    suspend fun getSubRegionList(parentRegion: String): AppResult<List<String>>
 }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/ResumeStep2Component.kt (1)

53-131: bulletComponent 중복 정의를 정리해주세요
ResumeCompleteActivity에도 동일한 bulletComponent가 있어서 중복이 생겼습니다. 디자인 시스템이나 공용 컴포넌트 모듈로 이동해 두 화면이 같은 구현을 재사용하도록 정리하는 편이 유지보수에 유리합니다.

app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/AuthenticationRepositoryImpl.kt (1)

78-79: 의미 있는 예외로 교체하면 디버깅이 쉬워집니다.

snapshot.toObject(...)가 null일 때 Exception()만 던지면 로그에서 원인이 드러나지 않습니다. IllegalStateException("UserDataClass 변환 실패")처럼 메시지·원인 정보를 담은 예외로 교체하는 편이 좋습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1c55c and 2d2236d.

📒 Files selected for processing (79)
  • app/build.gradle (4 hunks)
  • app/src/androidTest/java/com/capstone/nongglenonggle/LocalDataSourceTest.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/app/App.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/app/di/RepositoryModule.kt (2 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/appbar/NonggleAppBar.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/date_spinner/DateWidget.kt (5 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/dialog/NonggleBottomSheet.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/logger/AppLogger.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/logger/AppResultLogger.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/logger/ReleaseTree.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/local_datasource/RegionDao.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/model/worker/RegionListModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/model/worker/UserCareerListItemModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/model/worker/UserResumeModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/AuthenticationRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/FirestoreSetRepositoryImpl.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/RegionDataRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/WorkerResumeRepositoryImpl.kt (2 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/repository/FirestoreSetRepository.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/repository/RegionRepository.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/repository/WorkerResumeRepository.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/AddResumeRefToUserUseCase.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/AddResumeUseCase.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/FetchFirestoreDataUseCase.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/login/GetUserAuthDataRepositoryUseCase.kt (2 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/sign_up/GetRegionUseCase.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/sign_up/SaveRegionToLocalDataBaseUseCase.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/sign_up/SetUserSignUpUseCase.kt (2 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/worker/GetParentRegionListUseCase.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/worker/GetSubRegionListUseCase.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/worker/SetWorkerProfileImageUseCase.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/worker/SetWorkerResumeUseCase.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/FarmerAddressSearchScreen.kt (4 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/adapter/RegionFirstAdapter.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/adapter/RegionSecondAdapter.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/dialog/MonthpickerFragment.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/home/FarmerhomeFragment.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1Contract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1Screen.kt (3 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1ViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginContract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (4 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginViewModel.kt (3 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupAgreeTermsScreen.kt (4 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupContract.kt (3 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupViewModel.kt (7 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashViewModel.kt (2 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeCompleteActivity.kt (4 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeViewModel.kt (0 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/component/NonggleDropDown.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeMainViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeTabContract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeTabScreen.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/nav_controller/WorkerResumeGraph.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Screen.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1ViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/ResumeStep1Component.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2Contract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2Screen.kt (6 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2ViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CalendarDialog.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CareerAddBottomSheet.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/ResumeStep2Component.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3Contract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3Screen.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3ViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/ResumeStep3UiComponent.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/viewModel/farmer/FarmerNoticeViewModel.kt (0 hunks)
  • app/src/main/res/layout/fragment_location_select.xml (0 hunks)
  • app/src/main/res/layout/fragment_monthpicker.xml (0 hunks)
  • app/src/main/res/layout/fragment_resume_b.xml (0 hunks)
  • app/src/main/res/layout/fragment_resume_c.xml (0 hunks)
  • app/src/main/res/layout/fragment_resume_d.xml (0 hunks)
  • app/src/main/res/layout/location_list1.xml (0 hunks)
  • app/src/main/res/layout/location_list2.xml (0 hunks)
💤 Files with no reviewable changes (17)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/viewModel/farmer/FarmerNoticeViewModel.kt
  • app/src/main/res/layout/location_list2.xml
  • app/src/main/res/layout/fragment_location_select.xml
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/AddResumeUseCase.kt
  • app/src/main/res/layout/fragment_resume_b.xml
  • app/src/main/res/layout/fragment_resume_c.xml
  • app/src/main/java/com/capstone/nongglenonggle/domain/repository/FirestoreSetRepository.kt
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/FetchFirestoreDataUseCase.kt
  • app/src/main/res/layout/location_list1.xml
  • app/src/main/res/layout/fragment_monthpicker.xml
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/adapter/RegionSecondAdapter.kt
  • app/src/main/java/com/capstone/nongglenonggle/domain/usecase/AddResumeRefToUserUseCase.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/dialog/MonthpickerFragment.kt
  • app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/FirestoreSetRepositoryImpl.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/adapter/RegionFirstAdapter.kt
  • app/src/main/res/layout/fragment_resume_d.xml
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (6)
  • app/src/main/java/com/capstone/nongglenonggle/core/common/logger/ReleaseTree.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1Contract.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1Screen.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1ViewModel.kt
  • app/src/androidTest/java/com/capstone/nongglenonggle/LocalDataSourceTest.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2Contract.kt
🧰 Additional context used
🧬 Code graph analysis (22)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1ViewModel.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (1)
  • updateState (42-44)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CalendarDialog.kt (2)
app/src/main/java/com/capstone/nongglenonggle/core/common/date_spinner/DateWidget.kt (1)
  • DateSpinnerWithOutDay (92-139)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/ResumeStep1Component.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • OutlinedButton (177-210)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Screen.kt (5)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt (1)
  • isPhotoPickerAvailable (35-39)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt (1)
  • DateSpinnerBottomSheet (41-140)
app/src/main/java/com/capstone/nongglenonggle/core/common/textfield/NonggleTextField.kt (1)
  • NonggleTextField (36-128)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (2)
  • NonggleIconButton (260-276)
  • ContainedButton (144-175)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/ResumeStep1Component.kt (3)
  • genderSelectButton (35-55)
  • certificationButton (57-77)
  • certificationChipItem (79-119)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/nav_controller/WorkerResumeGraph.kt (1)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeTabScreen.kt (1)
  • ResumeTabScreen (34-138)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CareerAddBottomSheet.kt (5)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/component/NonggleDropDown.kt (1)
  • rememberExposedMenuStateHolder (37-40)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CalendarDialog.kt (1)
  • datePickerDialog (37-132)
app/src/main/java/com/capstone/nongglenonggle/core/common/dialog/NonggleBottomSheet.kt (1)
  • NonggleBottomSheet (18-56)
app/src/main/java/com/capstone/nongglenonggle/core/common/textfield/NonggleTextField.kt (1)
  • NonggleTextField (36-128)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupViewModel.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (2)
  • updateState (42-44)
  • postEffect (46-50)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginViewModel.kt (2)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (1)
  • postEffect (46-50)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashViewModel.kt (1)
  • getUserLoginType (26-44)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/ResumeStep2Component.kt (1)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeCompleteActivity.kt (1)
  • bulletComponent (230-242)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/ResumeStep3UiComponent.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • OutlinedButton (177-210)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (1)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/component/LoginUiComponent.kt (1)
  • googleLoginButton (55-71)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2Screen.kt (3)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/CareerAddBottomSheet.kt (1)
  • ResumeCareerAddBottomSheet (56-282)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/component/ResumeStep2Component.kt (1)
  • careerItem (32-118)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2Contract.kt (1)
  • item (40-40)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/design_system/NonggleTheme.kt (1)
  • NonggleTheme (7-12)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeMainViewModel.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (1)
  • postEffect (46-50)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2ViewModel.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (2)
  • updateState (42-44)
  • postEffect (46-50)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupAgreeTermsScreen.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/common/appbar/NonggleAppBar.kt (1)
  • NonggleAppBar (16-48)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3ViewModel.kt (2)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (2)
  • updateState (42-44)
  • postEffect (46-50)
app/src/main/java/com/capstone/nongglenonggle/data/local_datasource/RegionDao.kt (1)
  • getAllRegion (35-36)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt (3)
app/src/main/java/com/capstone/nongglenonggle/core/common/dialog/NonggleBottomSheet.kt (1)
  • NonggleBottomSheet (18-56)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/ResumeStep3UiComponent.kt (4)
  • selectedChipItem (55-95)
  • itemHorizontalDivider (106-113)
  • regionListItem (115-139)
  • itemVerticalDivider (97-104)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt (3)
app/src/main/java/com/capstone/nongglenonggle/core/common/dialog/NonggleBottomSheet.kt (1)
  • NonggleBottomSheet (18-56)
app/src/main/java/com/capstone/nongglenonggle/core/common/date_spinner/DateWidget.kt (1)
  • DateSpinner (24-90)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashViewModel.kt (2)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginViewModel.kt (1)
  • getUserLoginType (65-82)
app/src/main/java/com/capstone/nongglenonggle/core/base/BaseViewModel.kt (1)
  • postEffect (46-50)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeTabScreen.kt (6)
app/src/main/java/com/capstone/nongglenonggle/core/common/appbar/NonggleAppBar.kt (1)
  • NonggleAppBar (16-48)
app/src/main/java/com/capstone/nongglenonggle/core/common/component/NonggleTabRow.kt (1)
  • NonggleTabRow (21-67)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Screen.kt (1)
  • ResumeStep1Route (71-81)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2Screen.kt (1)
  • ResumeStep2Route (46-68)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3Screen.kt (1)
  • ResumeStep3Route (48-71)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3Screen.kt (2)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt (1)
  • SelectRegionBottomSheet (41-170)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/ResumeStep3UiComponent.kt (2)
  • selectedChipItem (55-95)
  • workCategoryChip (33-53)
🪛 detekt (1.23.8)
app/src/main/java/com/capstone/nongglenonggle/data/repositoryimpl/AuthenticationRepositoryImpl.kt

[warning] 78-78: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)

app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeMainViewModel.kt

[warning] 112-114: Empty catch block detected. If the exception can be safely ignored, name the exception according to one of the exemptions as per the configuration of this rule.

(detekt.empty-blocks.EmptyCatchBlock)


[warning] 112-112: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashViewModel.kt

[warning] 22-24: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

app/src/main/java/com/capstone/nongglenonggle/core/common/logger/AppResultLogger.kt

[warning] 6-6: A call to the default constructor of an exception was detected. Instead one of the constructor overloads should be called. This allows to provide more meaningful exceptions.

(detekt.exceptions.ThrowingExceptionsWithoutMessageOrCause)

🔇 Additional comments (17)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/component/NonggleDropDown.kt (2)

11-15: 상태 관리가 적절하게 구현되었습니다.

mutableStateOf를 사용한 상태 관리가 올바르게 구성되어 있습니다. selectedIndex의 초기값 -1은 "선택되지 않음" 상태를 나타냅니다.


16-25: 이전 리뷰 피드백이 올바르게 반영되었습니다.

getIcon()이 일반 함수로 변경되었고, onEnabled() 함수명 오타도 수정되었습니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/notice/notice_step1/NoticeStep1ViewModel.kt (3)

1-9: 이전 리뷰 이슈가 해결되었습니다.

이전 리뷰에서 지적되었던 잘못된 import(ResumeStep1Contract.Event)가 제거되었고, 현재 imports가 올바르게 구성되어 있습니다. Type alias 사용도 적절하여 코드 가독성을 높이고 있습니다.


10-14: Hilt DI 설정이 올바르게 구성되었습니다.

ViewModel이 @HiltViewModel로 정확히 annotate되어 있고, BaseViewModel을 MVU 패턴에 맞게 확장하고 있습니다. 초기 상태 설정도 적절합니다.


18-24: 이벤트 처리 로직이 올바릅니다.

SetUserNameClearUserName 이벤트가 정확히 처리되고 있으며, updateState를 통해 상태를 올바르게 업데이트하고 있습니다. 이전 리뷰에서 언급된 FIXME 메서드도 제거되어 깔끔한 구현이 되었습니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/farmer/home/FarmerhomeFragment.kt (1)

19-19: Compose 마이그레이션 범위 명확히 해주세요
PR 제목이 "Feat/migration compose UI"인데 FarmerhomeFragment가 여전히 XML 레이아웃(View Binding) 기반입니다. 단계적 마이그레이션인지, 이 화면도 Compose로 전환할 계획인지 알려주세요.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashViewModel.kt (2)

18-20: 초기화 로직이 올바르게 구현되었습니다.

init 블록에서 사용자 로그인 타입을 가져오는 것은 좋은 접근 방식입니다.


40-42: 실패 처리가 적절합니다.

onFailure에서 로그인 화면으로 내비게이션하는 것은 올바른 폴백 동작입니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (2)

38-46: LaunchedEffect 로직이 올바르게 개선되었습니다.

LaunchedEffect(Unit)를 사용하여 2초 지연 후 이펙트를 수집하는 로직이 하나의 블록으로 통합되었습니다. 이는 이전 리뷰 코멘트의 제안을 적절히 반영한 것입니다.


49-86: 스플래시 화면 UI가 잘 구현되었습니다.

로고, 앱 이름, 태그라인을 포함한 스플래시 화면 레이아웃이 깔끔하고 적절하게 구현되었습니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt (1)

35-39: Event에 람다 파라미터를 포함하는 설계를 검증하세요.

MVU 패턴에서 Event는 일반적으로 순수 데이터를 담아야 하는데, OpenGallery는 실행 가능한 람다를 포함하고 있습니다. 이는 이벤트의 직렬화 및 테스트를 어렵게 만들 수 있습니다. ViewModel에서 직접 launcher를 호출하는 방식으로 리팩토링하는 것이 더 적절할 수 있습니다.

app/src/main/java/com/capstone/nongglenonggle/data/model/worker/RegionListModel.kt (1)

3-6: LGTM!

Region 데이터를 위한 명확하고 간결한 모델입니다.

app/src/main/java/com/capstone/nongglenonggle/core/common/appbar/NonggleAppBar.kt (1)

38-38: LGTM!

람다로 래핑하는 것이 Compose의 일반적인 패턴과 일치합니다.

app/src/main/java/com/capstone/nongglenonggle/domain/usecase/sign_up/SetUserSignUpUseCase.kt (1)

4-16: LGTM!

Result에서 AppResult로의 마이그레이션이 올바르게 적용되었습니다. 이는 PR 전반의 통합 에러 핸들링 전략과 일치합니다.

app/src/main/java/com/capstone/nongglenonggle/domain/repository/WorkerResumeRepository.kt (1)

8-11: LGTM!

AppResult로의 마이그레이션이 올바르게 적용되었으며, 인터페이스 시그니처가 명확합니다.

app/src/main/java/com/capstone/nongglenonggle/domain/usecase/worker/SetWorkerProfileImageUseCase.kt (1)

1-18: LGTM!

클래스명 오타 수정 (SetWorkderProfileImageUseCaseSetWorkerProfileImageUseCase) 및 AppResult 마이그레이션이 올바르게 적용되었습니다.

app/src/main/java/com/capstone/nongglenonggle/data/model/worker/UserCareerListItemModel.kt (1)

3-3: java.time.Period 사용 허용됨 (minSdk 26 확인됨)
app/build.gradle에서 minSdk가 26으로 설정되어 있어 java.time.Period를 안전하게 사용할 수 있습니다.

Comment on lines +197 to +201
is AppResult.Failure -> {
val errorMsg = AppResultMessageProvider.message(context,result)
postEffect(SignUpEffect.SetToastMessage(errorMsg))
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

실패 후에도 submitState가 Loading에 고정됩니다.

서버 오류가 나면 submitStateisLoading이 해제되지 않아 이후 SaveUserInfo 이벤트가 바로 return 되어 가입을 다시 시도할 수 없습니다. 실패 분기에서 로딩 상태를 끝내고, submitState를 재시도 가능한 상태(예: Idle·Failure 등)로 되돌려주세요. 또한 필요 시 UI가 재시도를 안내할 수 있도록 오류 메시지를 상태로 반영해 주세요.

🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/signup/SignupViewModel.kt
around lines 197-201, the failure branch leaves submitState and isLoading stuck
in Loading; update the failure handling to reset loading flags and set
submitState to a retryable state (e.g., SubmitState.Failure or SubmitState.Idle)
and include the error message in that state so the UI can show retry guidance;
ensure isLoading is set to false and any relevant state flows/LiveData are
updated before posting the toast effect.

Comment on lines +43 to +128
viewModelScope.launch {
saveResume(
name = event.name,
birthDate = event.birthDate,
gender = event.gender,
certificate = event.certificate,
certificateList = event.certificateList,
regionList = event.regionList,
categoryList = event.categoryList,
careerList = event.careerList,
imageUri = event.imageUri
)
postEffect(effect = MainEffect.ShowToastMessage("저장을 완료했습니다."))
postEffect(effect = MainEffect.NavigateToHomeScreen)
}
}
}
}
}

private fun submitValidate(
name: String,
birthDate: String,
gender: String,
certificate: Boolean,
certificateList: List<String>,
regionList: List<String>,
categoryList: List<String>,
): Boolean {
if (name.isEmpty()) return false
if (birthDate.isEmpty()) return false
if (gender.isEmpty()) return false

// 자격증 정보 검사
if (certificate && certificateList.isEmpty()) return false

// 지역 및 품목 검사
if (regionList.isEmpty()) return false
if (categoryList.isEmpty()) return false

// 모든 항목이 조건 충족 시
return true
}

private suspend fun saveResume(
name: String,
birthDate: String,
gender: String,
certificate: Boolean,
certificateList: List<String>,
careerList: List<UserCareerListItemModel>,
regionList: List<String>,
categoryList: List<String>,
imageUri: Uri?,
) {
try {
saveUserImage(imageUri)
setWorkerResumeUseCase.invoke(
UserResumeModel(
name = name,
birthDate = birthDate,
gender = gender,
certificate = certificate,
certificateList = certificateList,
careerList = careerList,
regionList = regionList,
categoryList = categoryList,
)
)
} catch (e: Exception) {

}
}

private suspend fun saveUserImage(imageUri: Uri?) {
if(imageUri == null) return
val result = setWorkerProfileImageUseCase.invoke(imageUri)
when(result) {
is AppResult.Success -> {

}
is AppResult.Failure -> {

}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

저장 실패 시에도 성공 처리되는 치명적 버그가 있습니다.

saveResume/saveUserImage에서 실패를 전혀 반환하지 않고 예외도 빈 catch로 삼켜 버려, 업로드나 Firestore 저장이 실패해도 항상 “저장을 완료했습니다” 토스트와 홈 네비게이션이 발생합니다. 사용자에게 성공으로 오인시키고 데이터가 저장되지 않는 심각한 문제입니다. 성공/실패를 AppResult로 전달하고, 실패 시에는 즉시 사용자에게 안내하고 성공 이펙트를 발행하지 않도록 수정해 주세요.

-                    viewModelScope.launch {
-                        saveResume(
+                    viewModelScope.launch {
+                        when (val result = saveResume(
                             name = event.name,
                             birthDate = event.birthDate,
                             gender = event.gender,
                             certificate = event.certificate,
                             certificateList = event.certificateList,
                             regionList = event.regionList,
                             categoryList = event.categoryList,
                             careerList = event.careerList,
                             imageUri = event.imageUri
-                        )
-                        postEffect(effect = MainEffect.ShowToastMessage("저장을 완료했습니다."))
-                        postEffect(effect = MainEffect.NavigateToHomeScreen)
+                        )) {
+                            is AppResult.Success -> {
+                                postEffect(MainEffect.ShowToastMessage("저장을 완료했습니다."))
+                                postEffect(MainEffect.NavigateToHomeScreen)
+                            }
+                            is AppResult.Failure -> {
+                                postEffect(MainEffect.ShowToastMessage("저장에 실패했습니다. 다시 시도해주세요."))
+                            }
+                        }
                     }
@@
-    private suspend fun saveResume(
+    private suspend fun saveResume(
         name: String,
         birthDate: String,
         gender: String,
         certificate: Boolean,
         certificateList: List<String>,
         careerList: List<UserCareerListItemModel>,
         regionList: List<String>,
         categoryList: List<String>,
         imageUri: Uri?,
-    ) {
-        try {
-            saveUserImage(imageUri)
-            setWorkerResumeUseCase.invoke(
+    ): AppResult<Unit> {
+        val imageResult = saveUserImage(imageUri)
+        if (imageResult is AppResult.Failure) return imageResult
+
+        return setWorkerResumeUseCase(
             UserResumeModel(
                 name = name,
                 birthDate = birthDate,
                 gender = gender,
                 certificate = certificate,
                 certificateList = certificateList,
                 careerList = careerList,
                 regionList = regionList,
                 categoryList = categoryList,
             )
         )
-        } catch (e: Exception) {
-
-        }
     }
 
-    private suspend fun saveUserImage(imageUri: Uri?) {
-        if(imageUri == null) return
-        val result = setWorkerProfileImageUseCase.invoke(imageUri)
-        when(result) {
-            is AppResult.Success -> {
-
-            }
-            is AppResult.Failure -> {
-
-            }
-        }
-    }
+    private suspend fun saveUserImage(imageUri: Uri?): AppResult<Unit> {
+        if (imageUri == null) return AppResult.success(Unit)
+        return when (val result = setWorkerProfileImageUseCase.invoke(imageUri)) {
+            is AppResult.Success -> AppResult.success(Unit)
+            is AppResult.Failure -> result
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewModelScope.launch {
saveResume(
name = event.name,
birthDate = event.birthDate,
gender = event.gender,
certificate = event.certificate,
certificateList = event.certificateList,
regionList = event.regionList,
categoryList = event.categoryList,
careerList = event.careerList,
imageUri = event.imageUri
)
postEffect(effect = MainEffect.ShowToastMessage("저장을 완료했습니다."))
postEffect(effect = MainEffect.NavigateToHomeScreen)
}
}
}
}
}
private fun submitValidate(
name: String,
birthDate: String,
gender: String,
certificate: Boolean,
certificateList: List<String>,
regionList: List<String>,
categoryList: List<String>,
): Boolean {
if (name.isEmpty()) return false
if (birthDate.isEmpty()) return false
if (gender.isEmpty()) return false
// 자격증 정보 검사
if (certificate && certificateList.isEmpty()) return false
// 지역 및 품목 검사
if (regionList.isEmpty()) return false
if (categoryList.isEmpty()) return false
// 모든 항목이 조건 충족 시
return true
}
private suspend fun saveResume(
name: String,
birthDate: String,
gender: String,
certificate: Boolean,
certificateList: List<String>,
careerList: List<UserCareerListItemModel>,
regionList: List<String>,
categoryList: List<String>,
imageUri: Uri?,
) {
try {
saveUserImage(imageUri)
setWorkerResumeUseCase.invoke(
UserResumeModel(
name = name,
birthDate = birthDate,
gender = gender,
certificate = certificate,
certificateList = certificateList,
careerList = careerList,
regionList = regionList,
categoryList = categoryList,
)
)
} catch (e: Exception) {
}
}
private suspend fun saveUserImage(imageUri: Uri?) {
if(imageUri == null) return
val result = setWorkerProfileImageUseCase.invoke(imageUri)
when(result) {
is AppResult.Success -> {
}
is AppResult.Failure -> {
}
}
}
viewModelScope.launch {
when (val result = saveResume(
name = event.name,
birthDate = event.birthDate,
gender = event.gender,
certificate = event.certificate,
certificateList = event.certificateList,
regionList = event.regionList,
categoryList = event.categoryList,
careerList = event.careerList,
imageUri = event.imageUri
)) {
is AppResult.Success -> {
postEffect(MainEffect.ShowToastMessage("저장을 완료했습니다."))
postEffect(MainEffect.NavigateToHomeScreen)
}
is AppResult.Failure -> {
postEffect(MainEffect.ShowToastMessage("저장에 실패했습니다. 다시 시도해주세요."))
}
}
}
private fun submitValidate(
name: String,
birthDate: String,
gender: String,
certificate: Boolean,
certificateList: List<String>,
regionList: List<String>,
categoryList: List<String>,
): Boolean {
if (name.isEmpty()) return false
if (birthDate.isEmpty()) return false
if (gender.isEmpty()) return false
// 자격증 정보 검사
if (certificate && certificateList.isEmpty()) return false
// 지역 및 품목 검사
if (regionList.isEmpty()) return false
if (categoryList.isEmpty()) return false
// 모든 항목이 조건 충족 시
return true
}
private suspend fun saveResume(
name: String,
birthDate: String,
gender: String,
certificate: Boolean,
certificateList: List<String>,
careerList: List<UserCareerListItemModel>,
regionList: List<String>,
categoryList: List<String>,
imageUri: Uri?
): AppResult<Unit> {
val imageResult = saveUserImage(imageUri)
if (imageResult is AppResult.Failure) return imageResult
return setWorkerResumeUseCase(
UserResumeModel(
name = name,
birthDate = birthDate,
gender = gender,
certificate = certificate,
certificateList = certificateList,
careerList = careerList,
regionList = regionList,
categoryList = categoryList
)
)
}
private suspend fun saveUserImage(imageUri: Uri?): AppResult<Unit> {
if (imageUri == null) return AppResult.success(Unit)
return when (val result = setWorkerProfileImageUseCase.invoke(imageUri)) {
is AppResult.Success -> AppResult.success(Unit)
is AppResult.Failure -> result
}
}
🧰 Tools
🪛 detekt (1.23.8)

[warning] 112-114: Empty catch block detected. If the exception can be safely ignored, name the exception according to one of the exemptions as per the configuration of this rule.

(detekt.empty-blocks.EmptyCatchBlock)


[warning] 112-112: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeMainViewModel.kt
around lines 43–128, the saveResume/saveUserImage functions currently swallow
exceptions and never return failure, causing the UI to always show success;
change saveUserImage to return an AppResult (propagate the AppResult from
setWorkerProfileImageUseCase) and make saveResume return AppResult as well (call
saveUserImage and the setWorkerResumeUseCase and return Failure immediately if
either returns Failure or throws), remove the empty catch blocks (or log and
return Failure), and update the caller (the viewModelScope.launch block) to
check the returned AppResult: only post the success toast and
NavigateToHomeScreen on Success, and post an appropriate failure effect/toast on
Failure so failures are surfaced to the user.

Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Repository owner deleted a comment from coderabbitai bot Oct 12, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (1)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/component/LoginUiComponent.kt (1)

65-66: 이전 오타 이슈 해결 확인

Google 버튼이 올바른 문자열 리소스를 사용합니다(start_with_google). LGTM.

🧹 Nitpick comments (14)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt (1)

54-56: Effect 클래스 이름의 일관성을 개선하세요.

UnAvailableToastmessage의 명명 규칙 문제:

  • "UnAvailable" → "Unavailable" (소문자 'n')
  • "Toastmessage" → "ToastMessage" (PascalCase)
 sealed interface Effect : UiEffect {
-    data class UnAvailableToastmessage(val message: String) : Effect
+    data class UnavailableToastMessage(val message: String) : Effect
 }

더 명확한 이름도 고려해보세요:

data class ShowToast(val message: String) : Effect
// 또는
data class ShowErrorToast(val message: String) : Effect
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt (5)

43-49: Compose에서 Context 파라미터 대신 stringResource()를 사용하세요.

Compose에서는 Context를 파라미터로 전달하여 문자열 리소스를 가져오는 대신, stringResource() composable 함수를 직접 사용하는 것이 권장됩니다. 이는 재구성 시 더 효율적이며 Compose의 관용적인 패턴입니다.

다음 diff를 적용하여 리팩토링하세요:

 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun SelectRegionBottomSheet(
     modifier: Modifier = Modifier,
-    context: Context,
     onDismissRequest: () -> Unit,
     state: Step3State,
     onEvent: (Step3Event) -> Unit
 ) {

그리고 Line 63과 166에서 context.getString()stringResource()로 변경하세요:

-                text = context.getString(R.string.희망근무지역),
+                text = stringResource(R.string.희망근무지역),
-            titleText = context.getString(R.string.확인),
+            titleText = stringResource(R.string.확인),

stringResource를 import해야 합니다:

import androidx.compose.ui.res.stringResource

95-110: LazyList 항목에 key 파라미터를 추가하세요.

LazyVerticalGridLazyColumnitems() 호출에 key 파라미터가 지정되지 않았습니다. 키를 제공하면 재구성 시 성능이 향상되고 항목의 올바른 애니메이션 및 상태 유지가 보장됩니다.

다음 diff를 적용하여 키를 추가하세요:

LazyVerticalGrid (preferred locations):

                         items(
                             count = state.preferLocationList.size,
+                            key = { index -> state.preferLocationList[index] }
                         ) { index ->

첫 번째 LazyColumn (regions):

                             items(
                                 count = state.regionList.size,
+                                key = { index -> state.regionList[index] }
                             ) { index ->

두 번째 LazyColumn (subregions):

                             items(
                                 count = state.subRegionList.size,
+                                key = { index -> state.subRegionList[index] }
                             ) { index ->

Also applies to: 118-133, 136-151


113-117: 하드코딩된 높이를 고려하세요.

지역 선택 영역의 높이가 310.dp로 하드코딩되어 있습니다. 다양한 화면 크기나 컨텐츠 길이에 따라 적응하지 못할 수 있습니다. 동적 높이 계산이나 wrapContentHeight()heightIn()의 조합을 고려하세요.


158-169: 푸터 버튼의 상단 패딩을 줄이는 것을 고려하세요.

FullButtontop = 40.dp 패딩이 적용되어 있어 과도한 여백을 만들 수 있습니다. 일반적으로 20dp 정도가 더 적절합니다.

             FullButton(
                 modifier = Modifier
                     .fillMaxWidth()
-                    .padding(top = 40.dp, bottom = 20.dp),
+                    .padding(top = 20.dp, bottom = 20.dp),
                 enabled = true,

173-182: Preview에 샘플 데이터를 추가하는 것을 고려하세요.

Preview가 비어있는 Step3State()를 사용하므로 실제 UI가 어떻게 보이는지 확인하기 어렵습니다. 샘플 데이터를 포함한 상태를 제공하면 디자인 타임에 컴포넌트를 더 잘 시각화할 수 있습니다.

 @Preview
 @Composable
 fun SelectRegionPreviewScreen() {
     SelectRegionBottomSheet(
         context = LocalContext.current,
-        state = Step3State(),
+        state = Step3State(
+            regionList = listOf("서울", "경기", "인천"),
+            subRegionList = listOf("강남구", "서초구", "송파구"),
+            preferLocationList = listOf("서울 강남구", "경기 수원시"),
+            selectedParentRegion = "서울",
+            isLocationBottomSheetLoading = false
+        ),
         onEvent = {},
         onDismissRequest = {}
     )
app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (2)

52-52: LocalContext 사용이 불필요합니다.

Line 52의 LocalContext.current는 문자열 리소스 접근에만 사용되고 있습니다. Compose에서는 stringResource()를 직접 사용하는 것이 더 적절하며, 이는 프리뷰 호환성과 테스트 가능성을 향상시킵니다.

Lines 67, 75에서 stringResource()를 사용하도록 수정하면 이 변수를 제거할 수 있습니다. 아래 주석을 참고하세요.


67-67: stringResource()를 사용하세요.

context.getString()보다 stringResource(R.string.농글)을 사용하는 것이 Compose 관례이며, 리컴포지션을 올바르게 처리합니다.

다음 diff를 적용하세요:

+import androidx.compose.ui.res.stringResource
+
 @Composable
 fun SplashScreen() {
-    val context = LocalContext.current
-
     Column(
         modifier = Modifier
             .fillMaxSize(),
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
         Spacer(modifier = Modifier.weight(weight = 1f))
         Image(
             modifier = Modifier.size(width = 130.dp, height = 130.dp),
             painter = painterResource(id = R.drawable.logo),
             contentDescription = null,
         )
         Text(
-            context.getString(R.string.농글),
+            stringResource(R.string.농글),
             fontSize = 24.sp,
             fontFamily = soYo,
             fontWeight = FontWeight.Bold,
             color = NonggleTheme.colors.m1
         )
         Spacer(modifier = Modifier.weight(weight = 1f))
         Text(
-            context.getString(R.string.함께_가꾸어가는_씨앗에서_웃음을_맺기까지),
+            stringResource(R.string.함께_가꾸어가는_씨앗에서_웃음을_맺기까지),
             modifier = Modifier
                 .fillMaxWidth()
                 .padding(bottom = 60.dp),
             textAlign = TextAlign.Center,
             fontSize = 14.sp,
             fontFamily = soYo,
             fontWeight = FontWeight.Normal,
             color = NonggleTheme.colors.m1
         )
     }
 }
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/component/LoginUiComponent.kt (3)

37-53: Context prop 제거하고 stringResource 사용으로 단순화

Composable에 Context를 전달해 getString만 사용하는 패턴은 Compose 권장사항과 어긋납니다. stringResource로 교체하고 파라미터에서 Context를 제거해 주세요. 호출부(LoginScreen)도 함께 정리 필요합니다.

아래 diff를 적용해 주세요(상단 import 추가 포함):

 package com.capstone.nongglenonggle.presentation.view.login.component

 import android.content.Context
+import androidx.compose.ui.res.stringResource
@@
 @Composable
-fun KakaoLoginButton(
-    onClick: () -> Unit,
-    context: Context
-) {
+fun KakaoLoginButton(
+    onClick: () -> Unit
+) {
     ImageButton(
@@
-        titleText = context.getString(R.string.start_with_kakao),
+        titleText = stringResource(R.string.start_with_kakao),
         contentColor = NonggleTheme.colors.g1,
         backgroundColor = Color(0xFFF9E000),
         titleTextStyle = NonggleTheme.typography.b4_btn,
         imageResource = R.drawable.kakaobtn
     )
 }
@@
 @Composable
-fun GoogleLoginButton(
-    onClick: () -> Unit,
-    context: Context
-) {
+fun GoogleLoginButton(
+    onClick: () -> Unit
+) {
     ImageButton(
@@
-        titleText = context.getString(R.string.start_with_google),
+        titleText = stringResource(R.string.start_with_google),
         contentColor = NonggleTheme.colors.g2,
         backgroundColor = NonggleTheme.colors.g4,
         titleTextStyle = NonggleTheme.typography.b4_btn,
         imageResource = R.drawable.googleimg
     )
 }

Also applies to: 55-71


22-34: 로고 텍스트 하드코딩 제거(지역화 대비)

"농글" 리터럴은 리소스로 분리하는 것이 좋습니다. 예: app_name 또는 전용 key.

+import androidx.compose.ui.res.stringResource
@@
-        text = "농글",
+        text = stringResource(R.string.app_name),

리소스 키가 없다면 strings.xml에 추가해 주세요.


49-50: 색상 상수 테마로 승격 고려

카카오 색상 상수(0xFFF9E000)는 디자인 시스템 색 팔레트로 승격해 일관성과 다크모드 대응을 확보하는 것을 권장합니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (3)

36-52: LaunchedEffect 키를 Unit으로 명시

한 번만 수집하려는 의도는 Unit 키가 더 명확합니다.

-    LaunchedEffect(true) {
+    LaunchedEffect(Unit) {
         effectFlow.collectLatest { effect ->
             when (effect) {
                 is LoginEffect.NavigateToEnrollUser -> navigateToEnrollUser()
                 is LoginEffect.NavigateToWorkerHome -> navigateToWorkerHome()
                 is LoginEffect.ShowToastMessage -> {
                     Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
                 }
                 is LoginEffect.LaunchGoogleSignIn -> {
                     onLaunchGoogleSignIn(effect.intentSender)
                 }
             }
         }
     }

54-60: signInError 토스트 로직 간소화

null/empty 이중 체크를 줄여 가독성과 안전성을 높일 수 있습니다.

-    LaunchedEffect(key1 = uiState.signInState.signInError) {
-        uiState.signInState.signInError?.let { error ->
-            if (!uiState.signInState.signInError.isNullOrEmpty()) {
-                Toast.makeText(context, error, Toast.LENGTH_LONG).show()
-            }
-        }
-    }
+    LaunchedEffect(uiState.signInState.signInError) {
+        uiState.signInState.signInError
+            ?.takeIf { it.isNotEmpty() }
+            ?.let { msg -> Toast.makeText(context, msg, Toast.LENGTH_LONG).show() }
+    }

81-87: 컴포넌트 변경에 맞춘 버튼 호출부 정리(Context 제거)

상위에서 전달하던 Context는 불필요합니다. 컴포넌트 내부 stringResource 사용으로 대체하세요.

-            KakaoLoginButton(onClick = {
-                onEvent(LoginEvent.KakaoLoginButtonClick)
-            }, context)
+            KakaoLoginButton(onClick = {
+                onEvent(LoginEvent.KakaoLoginButtonClick)
+            })
@@
-            GoogleLoginButton(onClick = {
-                onEvent(LoginEvent.GoogleLoginButtonClick)
-            }, context)
+            GoogleLoginButton(onClick = {
+                onEvent(LoginEvent.GoogleLoginButtonClick)
+            })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d2236d and e23ac60.

📒 Files selected for processing (9)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (3 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/component/LoginUiComponent.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeMainViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2ViewModel.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3Screen.kt (1 hunks)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/ResumeStep3Screen.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step2/ResumeStep2ViewModel.kt
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/main_screen/ResumeMainViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (5)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt (3)
app/src/main/java/com/capstone/nongglenonggle/core/common/dialog/NonggleBottomSheet.kt (1)
  • NonggleBottomSheet (18-56)
app/src/main/java/com/capstone/nongglenonggle/core/common/date_spinner/DateWidget.kt (1)
  • DateSpinner (24-90)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt (3)
app/src/main/java/com/capstone/nongglenonggle/core/common/dialog/NonggleBottomSheet.kt (1)
  • NonggleBottomSheet (18-56)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/ResumeStep3UiComponent.kt (4)
  • selectedChipItem (55-95)
  • itemHorizontalDivider (106-113)
  • regionListItem (115-139)
  • itemVerticalDivider (97-104)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • FullButton (78-103)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/component/LoginUiComponent.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/common/button/NonggleButton.kt (1)
  • ImageButton (105-142)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (1)
app/src/main/java/com/capstone/nongglenonggle/core/design_system/NonggleTheme.kt (1)
  • NonggleTheme (7-12)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (2)
app/src/main/java/com/capstone/nongglenonggle/core/design_system/Theme.kt (1)
  • NongleTheme (20-49)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/component/LoginUiComponent.kt (3)
  • AppLogoForLogin (20-35)
  • KakaoLoginButton (37-53)
  • GoogleLoginButton (55-71)
🔇 Additional comments (4)
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt (1)

7-7: review_comment의 우려 사항이 실제로는 적용되지 않습니다.

<citindex="2-1">프로젝트의 minSdk가 26이므로 java.time.LocalDate를 안전하게 사용할 수 있습니다. 검토 의견에서 제시한 런타임 크래시 위험은 minSdk가 26 미만일 때만 발생합니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt (2)

51-70: 상태 관리와 유효성 검증 로직이 잘 구현되었습니다.

rememberSaveable을 사용하여 구성 변경 시에도 선택 상태를 유지하고, coerceIn을 통해 날짜 범위를 동적으로 제한하는 로직이 정확합니다. 이를 통해 minDate/maxDate 경계에서도 유효한 날짜만 선택되도록 보장합니다.


123-139: 날짜 생성 로직이 올바르게 수정되었습니다.

이전 리뷰에서 지적된 Date 생성자 문제가 LocalDate.of(year, month, day)를 사용하여 정확하게 수정되었습니다. 선택된 날짜가 올바른 값으로 생성되고 전달됩니다.

app/src/main/java/com/capstone/nongglenonggle/presentation/view/splash/SplashScreen.kt (1)

89-95: LGTM!

프리뷰 함수가 올바르게 구현되어 있으며, NonggleTheme으로 적절히 래핑되어 있습니다.

import com.capstone.nongglenonggle.R
import com.capstone.nongglenonggle.core.common.button.ImageButton
import com.capstone.nongglenonggle.core.design_system.NonggleTheme
import com.capstone.nongglenonggle.core.design_system.NongleTheme
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Theme 명칭 일관성 점검(NongleTheme vs NonggleTheme)

본 파일은 NongleTheme(함수)를, 컴포넌트 파일은 NonggleTheme(객체)를 사용합니다. 오타/혼용 가능성이 있어 팀 차원에서 네이밍을 통일하는 것을 권장합니다.

다음 스크립트로 혼용 현황을 확인해 주세요. 결과를 바탕으로 일괄 리네이밍을 제안드립니다.


🏁 Script executed:

#!/bin/bash
# Theme 명칭 사용 현황 스캔
rg -nP '\bNongg?leTheme\b' -C2

# 심화: 선언부/호출부 분리 확인
rg -nP 'fun\s+Nongg?leTheme\b' -C2
rg -nP '\bNongg?leTheme\.(colors|typography)\b' -C2

Length of output: 214661


Theme 명칭 혼용 확인 — 일괄 리네이밍 권장

검증 결과, 코드베이스에서 NongleTheme(단일 'g')과 NonggleTheme(이중 'g')이 혼용되고 있습니다.

혼용 현황:

  • LoginScreen.kt, ResumeActivity.kt 등 Activity 파일: NongleTheme 함수 사용
  • LoginUiComponent.kt를 포함한 대다수 Component/Screen 파일: NonggleTheme 객체(.colors, .typography) 사용

대부분의 파일(40개 이상)에서 NonggleTheme을 사용하고 있으므로, 소수의 NongleTheme 사용 파일들을 다음 위치에서 NonggleTheme으로 일괄 변경할 것을 강력히 권장합니다:

  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt (line 16, 74)
  • app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeActivity.kt (line 7, 15)
🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt
(lines 16 and 74) and
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/ResumeActivity.kt
(lines 7 and 15), the theme name is inconsistent: replace the incorrect single-g
usage "NongleTheme" with the project-standard "NonggleTheme"; update the import
line(s) to import com.capstone.nongglenonggle.core.design_system.NonggleTheme
and change all usages on the specified lines accordingly, then run a
project-wide search to ensure no other files use the wrong name and rebuild to
verify.

Comment on lines 74 to 90
NongleTheme {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.weight(1f))
AppLogoForLogin()
Spacer(modifier = Modifier.weight(1f))
kakaoLoginButton(onClick = {
viewModel.setEvent(LoginContract.Event.KakaoLoginButtonClick)
KakaoLoginButton(onClick = {
onEvent(LoginEvent.KakaoLoginButtonClick)
}, context)
Spacer(modifier = Modifier.height(16.dp))
googleLoginButton(onClick = {
viewModel.setEvent(LoginContract.Event.GoogleLoginButtonClick)
GoogleLoginButton(onClick = {
onEvent(LoginEvent.GoogleLoginButtonClick)
}, context)
Spacer(modifier = Modifier.height(20.dp))
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

weight 사용을 위해 Column에 fillMaxSize 추가

현재 Column이 wrapContent면 Spacer(weight)가 의도대로 동작하지 않을 수 있습니다.

-    NongleTheme {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally
-        ) {
+    NongleTheme {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
             Spacer(modifier = Modifier.weight(1f))
             AppLogoForLogin()
             Spacer(modifier = Modifier.weight(1f))
             KakaoLoginButton(onClick = {
                 onEvent(LoginEvent.KakaoLoginButtonClick)
-            }, context)
+            })
             Spacer(modifier = Modifier.height(16.dp))
             GoogleLoginButton(onClick = {
                 onEvent(LoginEvent.GoogleLoginButtonClick)
-            }, context)
+            })
             Spacer(modifier = Modifier.height(20.dp))
         }
     }

참고: 버튼 호출부 수정은 아래 코멘트와 연동됩니다.

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

🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/login/LoginScreen.kt
around lines 74 to 90, the Column is missing fillMaxSize(), so Spacer(modifier =
Modifier.weight(...)) may not behave as intended; update the Column to use
Modifier.fillMaxSize() (e.g., Column(modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally) { ... }) and ensure the
appropriate import for fillMaxSize is present; this will allow Spacer weights to
work correctly and align with the related button-call changes referenced in the
other comment.

Comment on lines +38 to +46
LaunchedEffect(Unit) {
delay(2000L)
effectFlow.collectLatest { effect ->
when (effect) {
is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
is SplashEffect.NavigateToLogin -> navigateToLogin()
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Effect 수집 전 지연으로 인한 경쟁 조건을 수정하세요.

delay(2000L) 이후에 collectLatest가 시작되므로, 지연 시간 동안 ViewModel이 방출한 효과(Effect)는 손실됩니다. 사용자가 유효한 토큰을 가지고 있어 ViewModel이 즉시 네비게이션 효과를 방출하는 경우, 불필요하게 2초를 기다리게 되거나 네비게이션이 실행되지 않을 수 있습니다.

다음 diff를 적용하여 효과 수집과 지연을 병렬로 처리하세요:

     LaunchedEffect(Unit) {
-        delay(2000L)
-        effectFlow.collectLatest { effect ->
+        launch {
+            delay(2000L)
+        }
+        effectFlow.collect { effect ->
             when (effect) {
                 is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
                 is SplashEffect.NavigateToLogin -> navigateToLogin()
             }
         }
     }

또는 최소 지연 시간을 보장하면서 효과에 즉시 반응하려면:

     LaunchedEffect(Unit) {
-        delay(2000L)
-        effectFlow.collectLatest { effect ->
+        val minDisplayTime = async { delay(2000L) }
+        effectFlow.collectLatest { effect ->
+            minDisplayTime.await()
             when (effect) {
                 is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
                 is SplashEffect.NavigateToLogin -> navigateToLogin()
             }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LaunchedEffect(Unit) {
delay(2000L)
effectFlow.collectLatest { effect ->
when (effect) {
is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
is SplashEffect.NavigateToLogin -> navigateToLogin()
}
}
}
LaunchedEffect(Unit) {
val minDisplayTime = async { delay(2000L) }
effectFlow.collectLatest { effect ->
minDisplayTime.await()
when (effect) {
is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
is SplashEffect.NavigateToLogin -> navigateToLogin()
}
}
}
Suggested change
LaunchedEffect(Unit) {
delay(2000L)
effectFlow.collectLatest { effect ->
when (effect) {
is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
is SplashEffect.NavigateToLogin -> navigateToLogin()
}
}
}
LaunchedEffect(Unit) {
launch {
delay(2000L)
}
effectFlow.collect { effect ->
when (effect) {
is SplashEffect.NavigateToWorkerHome -> navigateToWorkerHome()
is SplashEffect.NavigateToLogin -> navigateToLogin()
}
}
}

Comment on lines +43 to +50
fun DateSpinnerBottomSheet(
onConfirm: (LocalDate) -> Unit,
onDismissRequest: () -> Unit,
context: Context,
initialDate: LocalDate = LocalDate.now(),
minDate: LocalDate = LocalDate.of(1900, 1, 1),
maxDate: LocalDate = LocalDate.of(2100, 12, 31),
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Context 파라미터 대신 LocalContext와 stringResource를 사용하세요.

Compose에서는 함수 파라미터로 Context를 받는 대신 LocalContext.current를 내부에서 사용하는 것이 관례입니다. 또한 문자열 리소스는 context.getString() 대신 stringResource()를 사용해야 합니다.

다음 diff를 적용하여 수정하세요:

 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun DateSpinnerBottomSheet(
     onConfirm: (LocalDate) -> Unit,
     onDismissRequest: () -> Unit,
-    context: Context,
     initialDate: LocalDate = LocalDate.now(),
     minDate: LocalDate = LocalDate.of(1900, 1, 1),
     maxDate: LocalDate = LocalDate.of(2100, 12, 31),
 ) {
+    val context = LocalContext.current
     var year by rememberSaveable(initialDate) { mutableStateOf(initialDate.year) }

그리고 문자열 리소스 사용 부분을 변경하세요:

+import androidx.compose.ui.res.stringResource
+
                 Text(
-                    text = context.getString(R.string.날짜),
+                    text = stringResource(R.string.날짜),
                     style = TextStyle(
                 FullButton(
                     modifier = Modifier.fillMaxWidth(),
                     onClick = {
                         val selectedDate: LocalDate = LocalDate.of(year, month, day)
                         onConfirm(selectedDate)
                         onDismissRequest()
                     },
-                    titleText = context.getString(R.string.확인),
+                    titleText = stringResource(R.string.확인),
                     titleTextStyle = TextStyle(

Preview 함수에서도 context 파라미터를 제거하세요:

     DateSpinnerBottomSheet(
         onConfirm = {},
         onDismissRequest = {},
-        context = LocalContext.current,
     )

Also applies to: 82-82, 130-130

Comment on lines +102 to +107
Column(
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 20.dp)
.imePadding()
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

중복된 imePadding을 제거하세요.

NonggleBottomSheet가 이미 전체 컨텐츠에 imePadding을 적용하고 있으므로(NonggleBottomSheet.kt 25번 라인), 여기서 다시 적용할 필요가 없습니다.

다음 diff를 적용하세요:

             Column(
                 modifier = Modifier
                     .wrapContentHeight()
                     .padding(horizontal = 20.dp)
-                    .imePadding()
             ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Column(
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 20.dp)
.imePadding()
) {
Column(
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 20.dp)
) {
🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/component/DateSpinnerBottomSheet.kt
around lines 102 to 107, the Column is redundantly calling imePadding() even
though NonggleBottomSheet already applies imePadding to the whole content;
remove the imePadding() call from this Column's modifier so the Column uses
.wrapContentHeight().padding(horizontal = 20.dp) only.

Comment on lines +15 to +22
val genderSelectedMap: LinkedHashMap<String, Boolean> = linkedMapOf(
"여" to false,
"남" to false
),
val certificationPossessionSelectedMap: LinkedHashMap<String, Boolean> = linkedMapOf(
"있음" to false,
"없음" to false
),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

문자열 리터럴을 리소스로 추출하고 불변 Map을 사용하세요.

genderSelectedMapcertificationPossessionSelectedMap에서 발견된 문제점들:

  1. 하드코딩된 한국어 문자열: "여", "남", "있음", "없음"이 코드에 직접 포함되어 있어 지역화가 불가능하고 유지보수가 어렵습니다.
  2. 가변 LinkedHashMap: linkedMapOf는 가변 Map을 반환하므로 State의 불변성 원칙을 위반합니다.
  3. 타입 안전성 부족: 문자열 키 사용으로 타입 오류 가능성이 있습니다.

권장 해결책: Enum과 불변 Map 사용

+enum class Gender { FEMALE, MALE }
+enum class CertificationPossession { YES, NO }
+
 data class State(
     val isLoading: Boolean = false,
     val userName: String = "",
     val showDatePickerSheet: Boolean = false,
-    val genderSelectedMap: LinkedHashMap<String, Boolean> = linkedMapOf(
-        "여" to false,
-        "남" to false
-    ),
-    val certificationPossessionSelectedMap: LinkedHashMap<String, Boolean> = linkedMapOf(
-        "있음" to false,
-        "없음" to false
-    ),
+    val selectedGender: Gender? = null,
+    val hasCertification: CertificationPossession? = null,

또는 Map을 유지해야 한다면:

val genderSelectedMap: Map<String, Boolean> = mapOf(
    "" to false,
    "" to false
)

Comment on lines +34 to +38
data class OpenGallery(
val isPhotoPickerAvailable: Boolean,
val getContentLauncher: () -> Unit,
val pickerLauncher: () -> Unit
) : Event
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Event에서 람다 파라미터를 제거하세요.

OpenGallery 이벤트가 람다 함수를 파라미터로 포함하고 있어 다음 문제가 발생합니다:

  1. MVU 패턴 위반: Event는 순수 데이터여야 하며, 동작(behavior)을 포함해서는 안 됩니다.
  2. 직렬화 불가: 람다는 직렬화할 수 없어 상태 복원이나 로깅에 문제가 생깁니다.
  3. 테스트 어려움: 람다 비교가 불가능하여 이벤트 검증이 어렵습니다.
  4. 메모리 누수 위험: 람다가 컨텍스트를 캡처할 수 있습니다.

권장 해결책: Effect나 ViewModel에서 처리

 sealed interface Event : UiEvent {
-    data class OpenGallery(
-        val isPhotoPickerAvailable: Boolean,
-        val getContentLauncher: () -> Unit,
-        val pickerLauncher: () -> Unit
-    ) : Event
+    data class OpenGallery(val isPhotoPickerAvailable: Boolean) : Event
 }

ViewModel이나 Screen에서 직접 launcher를 호출하도록 처리:

// In ViewModel or Screen
when (event) {
    is Event.OpenGallery -> {
        if (event.isPhotoPickerAvailable) {
            pickerLauncher()
        } else {
            getContentLauncher()
        }
    }
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step1/ResumeStep1Contract.kt
around lines 34-38, the OpenGallery Event currently carries lambda launchers
which violates MVU (events should be pure data), prevents serialization,
complicates testing, and can leak context; remove the lambda parameters and make
OpenGallery a pure-data event (e.g., keep only isPhotoPickerAvailable or replace
with an enum/flag like PickerType), then move the actual launcher invocation
into the ViewModel/Screen/effect handler where you can inspect the event and
call the appropriate launcher (update all call sites and tests to emit the new
pure event and perform launching logic in the UI layer or ViewModel effect).

Comment on lines +56 to +59
modifier = Modifier
.wrapContentHeight()
.wrapContentHeight()
.padding(horizontal = 20.dp, vertical = 16.dp),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

중복된 modifier를 제거하세요.

Line 58에 .wrapContentHeight()가 중복으로 호출되어 있습니다.

다음 diff를 적용하여 중복을 제거하세요:

             Row(
                 modifier = Modifier
                     .wrapContentHeight()
-                    .wrapContentHeight()
                     .padding(horizontal = 20.dp, vertical = 16.dp),
                 verticalAlignment = Alignment.CenterVertically
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modifier = Modifier
.wrapContentHeight()
.wrapContentHeight()
.padding(horizontal = 20.dp, vertical = 16.dp),
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 20.dp, vertical = 16.dp),
🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt
around lines 56 to 59, there is a duplicated modifier call
(.wrapContentHeight()) on line 58; remove the duplicate so the modifier chain
contains a single .wrapContentHeight() and keep the remaining .padding(...)
intact to preserve layout and formatting.

Comment on lines +85 to +87
if(state.isLocationBottomSheetLoading) {
CircularProgressIndicator()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

로딩 인디케이터를 중앙에 배치하세요.

CircularProgressIndicator가 정렬이나 위치 지정 없이 렌더링되어 좌측 상단에 나타납니다. 사용자 경험을 위해 중앙에 배치하고 적절한 패딩을 추가해야 합니다.

다음 diff를 적용하세요:

             if(state.isLocationBottomSheetLoading) {
-                CircularProgressIndicator()
+                Box(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(310.dp)
+                        .padding(vertical = 40.dp),
+                    contentAlignment = Alignment.Center
+                ) {
+                    CircularProgressIndicator()
+                }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(state.isLocationBottomSheetLoading) {
CircularProgressIndicator()
}
if(state.isLocationBottomSheetLoading) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(310.dp)
.padding(vertical = 40.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/capstone/nongglenonggle/presentation/view/worker/resume/resume_step3/component/SelectRegionBottomSheet.kt
around lines 85 to 87, the CircularProgressIndicator is rendered without
alignment so it appears at the top-left; wrap it in a container that fills the
available area and center the indicator (e.g., use a Box or Column with
Modifier.fillMaxSize() and contentAlignment = Alignment.Center) and add modest
padding around it (e.g., Modifier.padding(16.dp)) so the loader is visually
centered and not flush to the edges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant