Conversation
…extra-api # Conflicts: # app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt
…tra-api # Conflicts: # app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt
# Conflicts: # app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt # app/src/main/java/com/example/findu/di/RepositoryModule.kt # app/src/main/java/com/example/findu/di/UseCaseModule.kt # app/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt # app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt # app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt # app/src/main/java/com/example/findu/presentation/ui/login/viewmodel/LoginViewModel.kt
# Conflicts: # app/src/main/java/com/example/findu/data/mapper/todomain/SearchResponseDtoMapper.kt # app/src/main/java/com/example/findu/di/UseCaseModule.kt
Walkthrough홈 추가화면(자원봉사자·보호센터·부서·행정구역) 관련 API·DTO·서비스·리포지토리·유스케이스·매퍼와 DI 바인딩을 추가하고, Compose UI(지도·드롭다운·material3) 및 HomeExtraViewModel의 상태/이벤트 로직을 확장했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant FR as HomeExtraFragment
participant VM as HomeExtraViewModel
participant UC as Get*UseCase
participant REPO as InformationRepository
participant RDS as InformationRemoteDataSource
participant SVC as InformationService
participant API as Remote API
FR->>VM: 사용자 액션 / 시작 이벤트
VM->>UC: invoke(params)
UC->>REPO: get*(params)
REPO->>RDS: get*(params)
RDS->>SVC: Retrofit call
SVC->>API: HTTP GET
API-->>SVC: BaseResponse<DTO>
SVC-->>RDS: BaseResponse<DTO>
RDS-->>REPO: BaseResponse<DTO>
REPO->>REPO: handleBaseResponse() / toDomain()
REPO-->>UC: Result<PagedResult/ List>
UC-->>VM: Result
VM->>VM: 상태 업데이트 (sido/sigungu/center 위치 등)
VM-->>FR: HomeExtraUiState
FR->>FR: UI 렌더(지도/드롭다운/리스트)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes (다계층 변경: API→데이터소스→리포지토리→유스케이스→ViewModel→UI(지도, 권한, 상태) 및 DI/매퍼/DTO 추가로 통합 검토 필요) Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/example/findu/domain/usecase/auth/PostSignupUseCase.kt (1)
12-25: 디바이스 아이디 검증 필수 - 빈 문자열 전송 위험검증 결과 실제 문제가 확인되었습니다.
DeviceLocalDataSourceImpl.kt에서INITIAL_VALUE = ""(빈 문자열)로 정의되어 있으며, Splash에서setDeviceId()를 호출하기 전에 signup 플로우가 실행될 경우getDeviceId()는 빈 문자열을 반환합니다.현재
PostSignupUseCaseline 24의userInfoRepository.getDeviceId()는 검증 없이 API에 전달되므로, 초기화되지 않은 빈 deviceId가 백엔드로 전송될 수 있습니다.필수 조치:
- PostSignupUseCase에서 deviceId 빈 문자열/null 검증 추가
- 또는 Splash 초기화 완료 전까지 signup 플로우 차단 보장
app/src/main/java/com/example/findu/domain/usecase/SetNicknameUseCase.kt (1)
1-11: 불필요한 import 제거 필요 - 현재 코드의 @Inject, @singleton 어노테이션은 제거되었으나 import는 미정리 상태입니다현재 상황:
SetNicknameUseCase는 이미UseCaseModule의provideSetNicknameUseCase()함수(lines 267-269)에서@Provides @Singleton으로 DI 그래프에 제공되고 있습니다- 클래스 정의에서 어노테이션을 제거하신 것이 맞는데, lines 4-5의 import 문이 정말로 사용되지 않고 있습니다
package com.example.findu.domain.usecase import com.example.findu.domain.repository.UserInfoRepository -import javax.inject.Inject -import javax.inject.Singleton class SetNicknameUseCase( private val userInfoRepository: UserInfoRepository ) { operator fun invoke(nickname: String) = userInfoRepository.setNickname(nickname = nickname) }DI 구성은 이미 제대로 되어 있으므로, 사용하지 않는 import만 정리하시면 됩니다.
🧹 Nitpick comments (32)
app/src/main/res/drawable/ic_extra_district_dropdown_24.xml (1)
1-13: 드롭다운 아이콘 구현이 깔끔합니다.Android 명명 규칙도 잘 따르고, 24x24 표준 크기에 투명한 배경과 스트로크만 사용한 깔끔한 설계네요. 행정구역 선택 UI에 잘 어울릴 것 같습니다.
한 가지, Line 7의
12.001값을12로 단순화하면 더 읽기 쉬울 수 있습니다. 설계 도구에서 생긴 반올림 아티팩트로 보이는데, 기능상 차이는 없으니 선택사항입니다.- android:pathData="M7,10L12.001,14.58L17,10" + android:pathData="M7,10L12,14.58L17,10"app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt (2)
72-80: 주소 텍스트 정렬 방식 재검토 필요.
textAlign = TextAlign.End와overflow = TextOverflow.Ellipsis를 함께 사용하면 긴 주소가 "...동물보호센터" 처럼 오른쪽 정렬된 상태로 잘릴 수 있어서 UX가 다소 어색할 수 있습니다.주소는 보통 앞부분(시/구 정보)이 더 중요하므로,
textAlign을 제거하거나 다른 레이아웃 방식을 고려해보세요.다음과 같이 수정을 고려해보세요:
Text( text = volunteerWork.address, style = FindUTheme.typography.body2SemiBold14, color = FindUTheme.colors.gray5, - textAlign = TextAlign.End, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f) )
61-65: 다른 텍스트 필드도 오버플로우 보호 고려해보세요.모집 기간, 봉사 날짜, 봉사 시간 텍스트 필드들에도
maxLines와overflow속성을 추가하면 예상치 못한 긴 데이터로부터 UI를 보호할 수 있습니다.날짜/시간 형식이라 실제로 문제가 될 가능성은 낮지만, 일관성과 방어적 코딩 측면에서 고려해볼 만합니다.
예시:
Text( text = volunteerWork.recruitmentPeriod, style = FindUTheme.typography.body2SemiBold14, - color = FindUTheme.colors.gray5 + color = FindUTheme.colors.gray5, + maxLines = 1, + overflow = TextOverflow.Ellipsis )Also applies to: 88-92, 100-104
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
8-9: Material3 전환 좋습니다 — i18n/접근성 소소 개선 제안
contentDescription하드코딩("Navigation Icon"/"Action Icon") 대신stringResource로 지역화 권장.- M3 컴포넌트 사용이 늘어난다면
CenterAlignedTopAppBar등 M3 TopAppBar로의 통합도 고려해 보세요(동일 디자인이면 접근성/semantics 이득).예)
contentDescription = stringResource(R.string.cd_navigation_icon) contentDescription = stringResource(R.string.cd_action_icon)app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt (1)
9-12: 불필요한 map 제거 및 파라미터 포맷 정리동일 반환이므로
map { it }제거하고 가독성 향상을 위해 공백 정리하면 깔끔합니다.- suspend operator fun invoke(lat: Double?=null,lon:Double?=null): Result<HomeData> = - homeRepository.getHome(lat,lon).map { homeData -> - homeData - } + suspend operator fun invoke( + lat: Double? = null, + lon: Double? = null + ): Result<HomeData> = homeRepository.getHome(lat, lon)추가로,
lat/lon이 null일 때 레포지토리의 기본 동작(예: 디바이스 위치 미설정 시 처리)이 명확한지 확인해 주세요.app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt (1)
4-4: 확장함수 이름 충돌 위험 확인됨 — alias 사용을 권장합니다검색 결과
toDomain확장함수가 30개 이상 정의되어 있으며, 패키지별로 다양한 타입에 대해 정의되어 있습니다. 현재 라인 4의import com.example.findu.data.mapper.todomain.home.toDomain은 home 패키지의 여러toDomain확장함수를 임포트하고 있는데, 라인 10의userInfo?.toDomain()은UserInfoDto타입에 대한 확장함수를 호출합니다.
UserInfoDto.toDomain()은UserInfoDtoMapper.kt에 정의되어 있으므로, 명확한 의도를 드러내기 위해 alias import를 사용하는 것이 좋습니다.-import com.example.findu.data.mapper.todomain.home.toDomain +import com.example.findu.data.mapper.todomain.home.toDomain as toHomeDomainapp/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt (1)
25-28: 좌표를 값 객체로 래핑하면 확장성과 타입 안전성이 향상됩니다현재 구조에서는
latitude와longitude를 개별 필드로 관리하고 있는데, 다음과 같이 값 객체로 묶으면 좋습니다:data class Coordinates(val latitude: Double, val longitude: Double) data class Center( val jurisdiction: List<String>, val centerName: String, val phoneNumber: String, val address: String, val location: Coordinates ) : HomeExtraDataType이렇게 하면 좌표 전달·복사 시 오류 방지, 좌표 관련 로직 확장(좌표계 변환, 거리 계산 등)이 용이하며, 다른 도메인 모델에서도 재사용할 수 있습니다. 매퍼에서도 명확하게 관심사를 분리할 수 있습니다.
다만
PagedResult와Sido는 현재 구조가 적절하며, 이미 각 계층별로 적절히 분리되어 있습니다.app/src/main/java/com/example/findu/presentation/ui/main/MainActivity.kt (1)
17-28: MainActivity에서 디바이스 아이디 처리 제거 확인했어요.스플래시로 디바이스 아이디 설정을 옮기면서 여기선 제거하신 거 맞죠. MainActivity가 더 깔끔해졌네요!
혹시 빈 줄들(lines 17, 22-24)을 정리하시면 더 깔끔할 것 같아요:
@AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - - binding = ActivityMainBinding.inflate(layoutInflater)app/src/main/java/com/example/findu/domain/usecase/extra/GetVolunteersUseCase.kt (1)
8-9: lastId 기본값(Long.MAX_VALUE) 사용은 혼란/리스크가 큽니다. null과의 일관성 확인 필요다른 유즈케이스(예: Centers/Departments)는
lastId = null기본값을 사용합니다. 여기서만Long.MAX_VALUE를 쓰면 페이지네이션 의미가 뒤틀리거나 첫 페이지에서 빈 결과가 날 수 있어요. 가능하면 기본값을null로 통일하고, 호출부가 빈/초기 상태에서는null을 전달하도록 맞추는 걸 권장합니다.- suspend operator fun invoke(lastId: Long? = Long.MAX_VALUE) = + suspend operator fun invoke(lastId: Long? = null) = repository.getVolunteers(lastId)호출/레포지토리/서비스까지
null의미(“첫 페이지”)가 유지되는지 한번 확인 부탁드립니다.app/src/main/java/com/example/findu/domain/usecase/extra/GetDepartmentsUseCase.kt (1)
8-14: 빈 문자열 입력을 null로 정규화해 서버 필터링을 안정화하세요UI에서 선택 해제 시
""가 올 가능성이 있습니다. 이 값을 그대로 넘기면 쿼리 파라미터가 빈 문자열로 전달되어 의도치 않은 필터링이 될 수 있어요. 호출 직전에 blank를null로 바꿔 주세요.- ) = repository.getDepartments( - district = district, + ) = repository.getDepartments( + district = district?.takeUnless { it.isBlank() }, lastId = lastId )app/src/main/java/com/example/findu/domain/usecase/extra/GetCentersUseCase.kt (1)
16-22: 매개변수 명칭 일관성(lon vs long)과 동시 필터링 정책 확인
- 레포지토리 매개변수가
long이라 매핑이 살짝 혼동됩니다. 가능하면 전 구간에서lng로 통일하는 게 가독성에 좋아요. 지금 단계에서는 주석으로 의도를 남기는 것도 방법입니다.district와lat/lon을 동시에 넘길 때의 서버 동작 정책(AND/OR/우선순위)을 명확히 합의해주세요. 필요 시require로 상호배타를 강제하거나 KDoc에 규칙을 남겨두는 걸 추천합니다.app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (2)
60-64: LazyColumn에 key를 지정해 리스트 안정성을 높이세요아이템에 고유 키가 있다면
key를 지정해 재구성/스크롤 유지 품질을 개선해 주세요.- LazyColumn { - items(departments) { + LazyColumn { + items(departments, key = { it.id /* 없으면 다른 안정 식별자 */ }) { ExtraDepartmentItem(department = it) } }
51-56: 선택 해제 시 빈 문자열 대신 null 전달 정책 정리 권장
selectedSigungu가""일 수 있습니다. 하위 레이어(UseCase/Repository)에는null을 “필터 없음”으로 사용하는 게 일반적이므로, 이벤트에서 빈 문자열을null로 변환하거나 ViewModel에서 정규화해 주세요. (Departments 유즈케이스 코멘트와 동일 맥락)app/src/main/java/com/example/findu/data/dataremote/model/response/information/SidoListDto.kt (1)
7-10: 필드 누락 방어를 위해 기본값을 두면 디코딩이 더 견고해집니다서버가 일시적으로
sidoList를 누락/null로 보낼 경우를 대비해 기본값을 두는 걸 권장합니다. (선호에 따라 엄격 모드 유지도 OK)data class SidoListDto( - @SerialName("sidoList") - val sidoList: List<SidoItemDto> + @SerialName("sidoList") + val sidoList: List<SidoItemDto> = emptyList() )app/src/main/java/com/example/findu/data/dataremote/model/response/information/CentersResponseDto.kt (1)
7-16: 역직렬화 견고성 확보: 기본값 추가 제안백엔드 필드 누락/스키마 변경 시 크래시를 피하려면 기본값을 두는 게 안전합니다. 현재 전 필드가 non-null이라 미스매치에 취약합니다.
아래처럼 기본값을 권장합니다.
@Serializable data class CentersResponseDto( @SerialName("centers") - val centers: List<CenterDto>, + val centers: List<CenterDto> = emptyList(), @SerialName("lastId") - val lastId: Long?, + val lastId: Long? = null, @SerialName("isLast") - val isLast: Boolean + val isLast: Boolean = false ) @Serializable data class CenterDto( @SerialName("jurisdiction") - val jurisdiction: List<String>, + val jurisdiction: List<String> = emptyList(), @SerialName("centerName") - val centerName: String, + val centerName: String = "", @SerialName("phoneNumber") - val phoneNumber: String, + val phoneNumber: String = "", - @SerialName("address") val address: String, - @SerialName("latitude") val latitude: Double, - @SerialName("longitude") val longitude: Double + @SerialName("address") val address: String = "", + @SerialName("latitude") val latitude: Double = 0.0, + @SerialName("longitude") val longitude: Double = 0.0 )백엔드가 모든 필드를 항상 보장한다면 현 상태 유지도 가능하니, 실제 응답 샘플로 한 번만 확인 부탁드립니다.
Also applies to: 19-34
app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt (1)
12-26: 중복 제거로 간결화 제안세 매퍼가 동일한 PagedResult 래핑을 반복합니다. 헬퍼로 공통화하면 가독성과 변경 용이성이 좋아집니다.
-fun VolunteersResponseDto.toDomain(): PagedResult<VolunteerWork> = - PagedResult( - items = volunteerWorks.map { - VolunteerWork( - institution = it.institution, - recruitmentPeriod = it.recruitmentPeriod, - address = it.address, - workPeriod = it.workPeriod, - workTime = it.workTime, - webLink = it.webLink - ) - }, - lastId = lastId, - isLast = isLast - ) +fun VolunteersResponseDto.toDomain(): PagedResult<VolunteerWork> = + mapToPagedResult( + volunteerWorks.map { + VolunteerWork( + institution = it.institution, + recruitmentPeriod = it.recruitmentPeriod, + address = it.address, + workPeriod = it.workPeriod, + workTime = it.workTime, + webLink = it.webLink + ) + }, + lastId, + isLast + ) @@ -fun CentersResponseDto.toDomain(): PagedResult<Center> = - PagedResult( - items = centers.map { - Center( - jurisdiction = it.jurisdiction, - centerName = it.centerName, - phoneNumber = it.phoneNumber, - address = it.address, - latitude = it.latitude, - longitude = it.longitude - ) - }, - lastId = lastId, - isLast = isLast - ) +fun CentersResponseDto.toDomain(): PagedResult<Center> = + mapToPagedResult( + centers.map { + Center( + jurisdiction = it.jurisdiction, + centerName = it.centerName, + phoneNumber = it.phoneNumber, + address = it.address, + latitude = it.latitude, + longitude = it.longitude + ) + }, + lastId, + isLast + ) @@ -fun DepartmentsResponseDto.toDomain(): PagedResult<Department> = - PagedResult( - items = departments.map { - Department( - name = it.departmentName, - district = it.district, - phone = it.phoneNumber - ) - }, - lastId = lastId, - isLast = isLast - ) +fun DepartmentsResponseDto.toDomain(): PagedResult<Department> = + mapToPagedResult( + departments.map { + Department( + name = it.departmentName, + district = it.district, + phone = it.phoneNumber + ) + }, + lastId, + isLast + )추가 헬퍼(파일 하단 등)에 배치:
private inline fun <T> mapToPagedResult( items: List<T>, lastId: Long?, isLast: Boolean ): PagedResult<T> = PagedResult(items = items, lastId = lastId, isLast = isLast) ```<!-- As per coding guidelines --> Also applies to: 28-42, 44-55 </blockquote></details> <details> <summary>app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (2)</summary><blockquote> `70-72`: **자원봉사 화면 호출 변경 LGTM** popBackStack 전달 방식 자연스럽습니다. 성능 미세 최적화가 필요하면 `val popBack = remember { { findNavController().popBackStack() } }` 로 재생성 방지 가능하지만 현 상태도 충분합니다. --- `114-114`: **빈 컨텐츠 상태 UI 제안** `HomeExtraContent.None -> Unit` 대신 간단한 빈 화면(스켈레톤/플레이스홀더/재시도 버튼) 노출을 권장합니다. UX 측면에서 “아무 것도 안 보임”을 피할 수 있습니다. </blockquote></details> <details> <summary>app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt (3)</summary><blockquote> `27-38`: **파라미터 명확화: long → lng** 경도는 관례적으로 `lng`를 씁니다. `long`은 `Long`(타입)과 혼동 여지가 있어 변경을 권장합니다. 인터페이스/서비스까지 일괄 반영 필요. ```diff - override suspend fun getCenters( - lastId: Long?, - district: String?, - lat: Double?, - long: Double?, - size: Int? - ): Result<PagedResult<Center>> = runCatching { - remote.getCenters(lastId, district, lat, long, size) + override suspend fun getCenters( + lastId: Long?, + district: String?, + lat: Double?, + lng: Double?, + size: Int? + ): Result<PagedResult<Center>> = runCatching { + remote.getCenters(lastId, district, lat, lng, size) .handleBaseResponse() .getOrThrow() .toDomain() }동일 리네임을
InformationRemoteDataSource/InformationService/호출부 전반에 적용해 주세요.
18-26: Result 체이닝 단순화
runCatching { ... getOrThrow() ... }대신handleBaseResponse().map { toDomain() }체이닝이 더 간결합니다(예외 흐름 동일).-): Result<PagedResult<VolunteerWork>> = runCatching { - remote.getVolunteers(lastId) - .handleBaseResponse() - .getOrThrow() - .toDomain() -} +): Result<PagedResult<VolunteerWork>> = + remote.getVolunteers(lastId) + .handleBaseResponse() + .map { it.toDomain() }같은 패턴을 센터/부서/시도/시군구에도 적용 가능합니다.
Also applies to: 27-38, 40-48, 50-55, 57-62
50-62: 시도/시군구는 캐싱 고려자주 변하지 않는 데이터입니다. 최초 1회 로드 후 메모리/로컬(예: Room/Datastore) 캐싱과 만료 정책(TTL) 적용을 추천합니다. API 부하와 체감 속도가 좋아집니다.
app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt (1)
11-13: 첫 페이지 규약 정렬: lastId 기본값페이징 첫 호출은 보통
lastId = null을 사용합니다. 현재Long.MAX_VALUE기본값은 혼란을 줄 수 있어null권장합니다(레포/서비스와 규약 일치 필요).- suspend fun getVolunteers( - lastId: Long? = Long.MAX_VALUE, - ): BaseResponse<VolunteersResponseDto> + suspend fun getVolunteers( + lastId: Long? = null, + ): BaseResponse<VolunteersResponseDto>첫 페이지에서 서버가
null을 기대하는지 확인 부탁드립니다.app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt (1)
21-28: 위경도 파라미터 네이밍 통일 제안(lon/lng vs long)레이어마다
lon(VM/UseCase 추정)과long(DS/Service)이 혼재되어 가독성이 떨어집니다.lng또는lon으로 통일을 권장합니다. 서비스/DS/리포지토리 인터페이스까지 일괄 변경하면 혼동을 줄일 수 있습니다.app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (5)
77-90: 카메라 애니메이션 효과 중복으로 인한 점프 가능성위치 변경(LaunchedEffect(latitude, longitude))과 센터 목록 변경(LaunchedEffect(centers))이 모두 카메라를 이동시켜 사용자가 조작 중일 때 점프가 발생할 수 있습니다. 한쪽만 트리거하거나, 최초 로드/사용자 액션 시로 한정하는 방식으로 정리해주세요.
103-104: 지도 패딩(320.dp)과 바텀시트 높이(340.dp) 불일치NaverMap 하단 패딩(320.dp)과 하단 패널 높이(340.dp)가 달라, 리스트가 지도 위 요소를 가릴 수 있습니다. 단일 상수로 관리해 일치시키는 것을 권장합니다.
예:
- val bottomSheetHeight = 340.dp를 정의 후 NaverMap에
.padding(bottom = bottomSheetHeight)적용.Also applies to: 202-206
111-121: Marker 상태 안정성 향상 제안forEach 내에서
rememberMarkerState(position = …)를 직접 호출하면 항목 순서 변화 시 상태 재생성으로 깜빡임이 생길 수 있습니다.key(center.id)(또는 lat/long 조합)로 키를 부여하거나 리스트 기반 API로 키 안정성을 확보해주세요.
164-182: 접근성: 아이콘에 contentDescription 누락현재 위치 아이콘에 적절한
contentDescription을 추가해주세요. 스크린 리더 사용성을 높입니다.
예:contentDescription = stringResource(R.string.home_extra_move_to_my_location)
185-198: i18n: 하드코딩 문자열 제거"현 지도에서 검색" 문자열은 string 리소스로 이동해주세요. 테스트/현지화에 유리합니다.
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt (1)
95-101: 위치 업데이트 시 데이터 갱신 여부 확인
UpdateLocation은 좌표만 갱신하고 조회는 트리거하지 않습니다. 의도라면 OK이고, 아니라면 센터/부서 재조회(또는 사용자의 “현 지도에서 검색” 액션만 허용) 정책을 명확히 해주세요.app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt (1)
22-29: 파라미터 이름sido→district로 정정 권장애노테이션은
@Query("district")인데 파라미터 이름이sido라 혼동 소지가 큽니다(특히 named argument 사용 시).district로 일치시켜 주세요. 동시에long→lng(또는lon)로 통일하면 가독성이 좋아집니다.app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (2)
18-19: 파라미터 네이밍 개선 제안.
long파라미터명이 명확하긴 하지만, 더 명시적인 이름을 사용하면 가독성이 올라갈 것 같아요.lon또는longitude를 고려해보세요.suspend fun getCenters( lastId: Long? = null, district: String? = null, lat: Double? = null, - long: Double? = null, + lon: Double? = null, size: Int? = null ): Result<PagedResult<Center>>
9-32: KDoc 문서화를 추가하면 좋을 것 같아요.인터페이스에 각 메서드의 목적, 파라미터 설명, 반환값, 가능한 에러 케이스 등을 설명하는 KDoc을 추가하면 사용성이 크게 향상될 거예요. 특히
lastId,district,lat/long같은 파라미터들의 의미와PagedResult의 페이지네이션 동작 방식을 명확히 해주면 좋겠어요.예시:
/** * 정보 조회를 위한 리포지토리 인터페이스 */ interface InformationRepository { /** * 자원봉사 목록을 페이지네이션으로 조회합니다. * * @param lastId 마지막으로 조회한 항목의 ID (다음 페이지 조회 시 사용) * @return 자원봉사 목록을 담은 PagedResult */ suspend fun getVolunteers( lastId: Long? = Long.MAX_VALUE ): Result<PagedResult<VolunteerWork>> // ... 나머지 메서드들도 동일하게 문서화 }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (54)
app/src/main/java/com/example/findu/data/dataremote/datasource/HomeRemoteDataSource.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/HomeRemoteDataSourceImpl.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/home/HomeResponseDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/information/CentersResponseDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/information/DepartmentsResponseDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/information/SidoListDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/information/SigunguListDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/information/VolunteersResponseDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/service/HomeService.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/toDomain/extra/SidoListDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/repositoryimpl/HomeRepositoryImpl.kt(1 hunks)app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt(1 hunks)app/src/main/java/com/example/findu/di/DataSourceModule.kt(3 hunks)app/src/main/java/com/example/findu/di/RepositoryModule.kt(3 hunks)app/src/main/java/com/example/findu/di/ServiceModule.kt(2 hunks)app/src/main/java/com/example/findu/di/UseCaseModule.kt(4 hunks)app/src/main/java/com/example/findu/domain/model/extra/HomeExtraDataType.kt(1 hunks)app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/SetNicknameUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/auth/PostCheckNicknameUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/auth/PostGuestLoginUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/auth/PostLoginUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/auth/PostSignupUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/extra/GetCentersUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/extra/GetDepartmentsUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/extra/GetSidoUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/extra/GetSigunguUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/extra/GetVolunteersUseCase.kt(1 hunks)app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraCenterItem.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDepartmentItem.kt(3 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt(6 hunks)app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/login/viewmodel/LoginViewModel.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/main/MainActivity.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/splash/SplashActivity.kt(2 hunks)app/src/main/res/drawable/ic_extra_district_dropdown_24.xml(1 hunks)app/src/main/res/drawable/ic_place_now_24.xml(1 hunks)app/src/main/res/values/strings.xml(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-16T15:20:00.175Z
Learnt from: t1nm1ksun
PR: FindYou-Kuit/FindYou-Android#77
File: app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.kt:1-1
Timestamp: 2025-08-16T15:20:00.175Z
Learning: LoginResponseDtoMapper.kt와 관련된 패키지/디렉터리 구조는 이미 올바르게 com.example.findu.data.mapper.todomain으로 통일되어 있다. 초기 분석 시 더 신중하게 확인해야 한다.
Applied to files:
app/src/main/java/com/example/findu/data/mapper/todomain/LoginResponseDtoMapper.ktapp/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt
🧬 Code graph analysis (7)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
FindUTopAppBar(19-66)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
app/src/main/java/com/example/findu/ui/theme/Theme.kt (1)
FindUTheme(39-59)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
ExtraHomeVolunteerScreen(14-32)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (1)
ExtraHomeDepartmentScreen(22-66)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (1)
ExtraHomeCenterScreen(54-229)
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt (5)
app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt (6)
getSigungu(30-30)getSido(28-28)getCenters(15-21)getDepartments(23-26)getVolunteers(10-31)getVolunteers(11-13)app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/InformationRemoteDataSourceImpl.kt (5)
getSigungu(37-38)getSido(35-35)getCenters(21-27)getDepartments(30-33)getVolunteers(17-19)app/src/main/java/com/example/findu/data/dataremote/service/InformationService.kt (6)
getSigungu(40-42)getSido(37-38)getCenters(22-29)getDepartments(31-35)getVolunteers(16-43)getVolunteers(17-20)app/src/main/java/com/example/findu/data/repositoryimpl/InformationRepositoryImpl.kt (5)
getSigungu(57-62)getSido(50-55)getCenters(27-38)getDepartments(40-48)getVolunteers(18-25)app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (6)
getSigungu(30-30)getSido(28-28)getCenters(15-21)getDepartments(23-26)getVolunteers(9-32)getVolunteers(11-13)
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.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/extra/view/ExtraDepartmentScreen.kt (2)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
FindUTopAppBar(19-66)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
ExtraDistrictItem(32-86)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (4)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
FindUTopAppBar(19-66)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
ExtraDistrictItem(32-86)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/extra/component/ExtraCenterItem.kt (1)
ExtraCenterItem(30-92)
🔇 Additional comments (46)
app/src/main/res/drawable/ic_place_now_24.xml (1)
1-38: 좋네요! 위치 마커 아이콘이 깔끔하게 구현됐습니다.네이버 지도 통합과 어울리는 24dp 위치 아이콘으로, 외부 원형 테두리, 십자 선, 중앙 채워진 원이 일관성 있게 디자인되었어요. 모든 요소에서 일관된 스트로크 색상과 너비를 사용하고 있고, SVG 경로도 올바르게 형성되어 있습니다.
app/src/main/java/com/example/findu/domain/usecase/auth/PostCheckNicknameUseCase.kt (1)
1-10: 패키지 구조 개선 잘 하셨습니다! 👍인증 관련 유스케이스를
auth서브패키지로 분리한 것은 코드 구조 개선에 좋은 선택입니다. 로직 변경 없이 깔끔하게 리팩토링되었네요.app/src/main/java/com/example/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt (1)
8-9: 임포트 경로 업데이트 완료! ✅패키지 이동에 따른 임포트 경로 업데이트가 정확하게 되었습니다. 깔끔한 리팩토링이네요!
app/src/main/java/com/example/findu/domain/usecase/auth/PostSignupUseCase.kt (1)
1-1: 패키지 재정렬이 잘 정리됐네요.
auth서브패키지로의 이동이 전체 PR의 구조 정리와 일관성 있게 진행되고 있습니다. 기능적 변화가 없어서 마이그레이션도 깔끔합니다.app/src/main/java/com/example/findu/domain/usecase/auth/PostLoginUseCase.kt (2)
1-1: 패키지 구조 개선 👍인증 관련 use case들을
auth서브패키지로 분리한 것은 좋은 구조 개선입니다.
12-19: deviceId 초기화 검증 완료 - 문제 없음검증 결과, deviceId 초기화는 안전하게 진행되고 있습니다:
동기식 초기화:
setDeviceIdUseCase()는 suspend 함수가 아니며 synchronous하게 작동하므로, SplashActivity의onCreate()단계에서 즉시 완료됩니다 (라인 41).영구 저장:
DeviceLocalDataSource에 직접 저장되므로,PostLoginUseCase가 나중에 호출될 때getDeviceId()를 통해 이미 설정된 값을 안정적으로 조회할 수 있습니다.Fallback 보장: Android ID 조회 실패 시
"unknown_device_id"로 폴백되므로 null이 될 수 없습니다.코드는 현재 상태로 안전하며, 설정 및 조회 사이의 타이밍 문제는 없습니다.
app/src/main/java/com/example/findu/domain/usecase/auth/PostGuestLoginUseCase.kt (2)
1-1: 패키지 구조 개선 👍다른 인증 use case들과 일관되게
auth서브패키지로 이동되었습니다.
11-13: deviceId 초기화 검증 필요 (PostLoginUseCase와 동일)게스트 로그인에서도 동일하게 deviceId를 사용하므로, PostLoginUseCase에서 언급한 deviceId 초기화 검증이 여기에도 적용됩니다.
app/src/main/java/com/example/findu/presentation/ui/login/viewmodel/LoginViewModel.kt (1)
7-8: import 경로 업데이트 확인 완료Use case 패키지 구조 변경에 맞춰 import 경로가 올바르게 업데이트되었습니다.
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraVolunteerItem.kt (2)
42-48: 기관명 텍스트 오버플로우 처리 잘 되었습니다.긴 기관명이 UI를 깨뜨리지 않도록
maxLines=1,overflow=Ellipsis,weight=1f를 적절히 사용했네요. "상세보기" 버튼이 항상 보이도록 처리한 것도 좋습니다.
11-12: Material3 마이그레이션은 잘됐는데, 프로젝트 전체 상황을 확인해보니.. 🔍현재 파일(ExtraVolunteerItem.kt)은 Material3로 잘 마이그레이션됐습니다. 👍
다만 전체 코드베이스를 봤을 때:
- Material (구버전) 사용: ~40개 파일 (로그인, 리포트, 홈, 온보딩 등 대부분)
- Material3 마이그레이션: ~9개 파일만 (
extra패키지 중심)80% 이상이 아직 Material을 사용하고 있어서 상당히 불일치한 상태입니다.
이번 PR이 Material3 마이그레이션 시작이라면, 프로젝트 전체에서 일관된 마이그레이션 계획이 필요합니다. 혼용하면 나중에 유지보수 복잡도가 올라갈 수 있으니까요.
혹시 이번이 부분 마이그레이션인지, 아니면 전체 마이그레이션 계획의 일부인지 확인해주세요!
app/src/main/java/com/example/findu/domain/usecase/home/GetHomeUseCase.kt (1)
1-1: 패키지 이동 완벽하게 마무리됨 ✓검증 결과 패키지 이동이 완전히 이루어졌습니다:
- 구 패키지 경로(
com.example.findu.domain.usecase)로부터의 임포트 없음HomeViewModel.kt와UseCaseModule.kt에서 새 경로(com.example.findu.domain.usecase.home) 임포트 확인- DI 모듈에서
provideGetHomeUseCase()정상 작동- 모든 참조가 일관되게 업데이트됨
추가 조치 불필요합니다.
app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt (1)
24-26: <!-- [scratchpad_start]
[task review_file_2/5 verification_1/2]
- INFORMATION constant usage verification: COMPLETE
- Backend endpoint specification verification: INCOMPLETE (no docs in repo)
[observations]- INFORMATION = "informations" defined at Constraints.kt:25
- InformationService.kt uses $INFORMATION constant in 3 @get paths (lines 17, 22, 31):
- /$API/$VERSION/$INFORMATION/volunteer-works
- /$API/$VERSION/$INFORMATION/protection-centers
- /$API/$VERSION/$INFORMATION/departments
- Expands to: /api/v2/informations/volunteer-works|centers|departments
- Constant imported and used correctly in InformationRemoteDataSourceImpl
- No backend documentation found in repository
[analysis]- Client code is internally consistent (plural form used everywhere)
- All three endpoints consistently use the INFORMATION constant
- Cannot verify if backend actually supports /informations endpoint
- Risk: path mismatch would cause 404 errors, but this cannot be confirmed from codebase
[pending]- Manual backend specification verification from developer
[actions]- Flag for manual verification; note that client implementation is consistent
[done]- Confirmed constant usage is consistent across all related endpoints
[scratchpad_start] -->서버 스펙 검증 필요
코드상 일관성은 확인됐어요.
INFORMATION = "informations"이 InformationService의 3개 엔드포인트(/volunteer-works,/protection-centers,/departments)에서 모두 동일하게 사용 중입니다. 다만 저장소에 백엔드 API 문서가 없어서 서버가 실제로/api/v2/informations/를 제공하는지 확인이 불가능하네요.배포 전에 서버 스펙과 맞는지 한 번 더 확인하고, 만약 엔드포인트가
/information(단수)이라면 위험할 수 있으니 체크 부탁합니다.app/src/main/res/values/strings.xml (1)
515-516: 새 문자열 리소스 추가 확인했습니다!시/도와 시/군/구 선택을 위한 문자열이 잘 추가되었네요. 네이밍 규칙도 일관성 있게 유지되고 있습니다.
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDepartmentItem.kt (2)
11-12: Material3 마이그레이션 좋습니다!
androidx.compose.material에서androidx.compose.material3로 깔끔하게 전환하셨네요.
46-49: 긴 부서명 처리 잘하셨어요!
maxLines = 1과TextOverflow.Ellipsis를 추가해서 긴 부서명이 UI를 깨지 않도록 잘 처리하셨습니다.weight(1f, fill = false)조합도 적절하네요.app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
17-24: 뒷걸음질 기능 추가 좋습니다!
popBackStack콜백을 추가해서 네비게이션 아이콘 클릭 시 뒤로가기가 동작하도록 하셨네요. 기본값도 안전하게 빈 람다로 설정되어 있어요.app/src/main/java/com/example/findu/presentation/ui/splash/SplashActivity.kt (1)
31-41: 스플래시에서 디바이스 아이디 설정하도록 변경하셨네요.PR 노트에서 언급하신 대로 디바이스 아이디를 스플래시 시점에 설정하도록 옮기셨습니다.
Settings.Secure.ANDROID_ID가 null일 경우"unknown_device_id"로 폴백하는 처리도 되어있어요.정상 디바이스에서는
ANDROID_ID가 null이 되는 경우가 거의 없지만, 작성자님 환경에서 정상 동작 확인하셨다고 하시니 괜찮을 것 같습니다. 혹시 다른 분들 테스트에서 빈값이 들어가는지 확인 부탁드려요!app/src/main/java/com/example/findu/data/dataremote/model/response/information/VolunteersResponseDto.kt (2)
6-14: 페이지네이션 구조가 잘 갖춰져 있어요.
lastId와isLast를 이용한 페이지네이션 패턴이 잘 적용되어 있습니다. 다만lastId가 non-nullableLong인데, 마지막 페이지에서도 항상 값이 있는지 백엔드 API 스펙을 확인해보세요. 경우에 따라Long?이 더 안전할 수 있어요.
16-30: 백엔드 API 명세 확인 필요리뷰 코멘트의 제안은 타당합니다. 코드베이스 검증 결과:
- VolunteerWorkDto의 모든 필드가 현재 non-nullable String으로 선언됨
- 코드베이스 내 다른 DTO들(SearchAnimalCard, CentersResponseDto 등)에서는 실제로 nullable 필드(
String?,Long?)를 사용하여 null 반환 가능성을 처리 중- 백엔드 API 명세를 확인해서 volunteerWorks 배열 내 각 필드가 null을 반환할 수 있다면 해당 필드를 nullable로 변경 권장
app/src/main/java/com/example/findu/data/dataremote/model/response/information/SigunguListDto.kt (1)
7-10: 코드 안정성을 위해 sigunguList를 nullable로 변경하거나 기본값 제공 권장현재 코드는
List<String>으로 선언되어 있는데,InformationRepositoryImpl.kt의 61번 줄에서 직접 접근하고 있습니다.runCatching래퍼가 있어서 역직렬화 실패 시 예외는 잡히지만, 더욱 견고하게 하려면:
- SigunguListDto에서
val sigunguList: List<String>?또는val sigunguList: List<String> = emptyList()로 변경- 백엔드 API 스펙 확인해서 실제로 null을 반환할 가능성이 있는지 검증
현재는 에러 처리가 있어서 앱이 깨지지는 않지만, 명시적 방어가 더 좋습니다.
app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (2)
32-86: 드롭다운 구현이 깔끔하네요!제네릭을 사용한 설계가 좋습니다. 한 가지 확인할 점이 있는데, Line 72에서
ExposedDropdownMenu에Color.Transparent배경을 설정했는데, 의도한 디자인이 맞나요? 배경이 복잡한 화면에서 메뉴 가독성이 떨어질 수 있습니다.
89-108: 프리뷰 구현 잘 되어 있습니다!로컬 상태 관리와 샘플 데이터를 통해 컴포넌트 사용법을 명확하게 보여주고 있습니다.
app/src/main/java/com/example/findu/data/dataremote/model/response/information/DepartmentsResponseDto.kt (1)
1-29: 부서 목록 DTO 구조가 잘 정의되어 있습니다!페이지네이션을 위한
lastId와isLast필드가 적절하게 포함되어 있고, 직렬화 어노테이션도 올바르게 적용되어 있습니다.app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraCenterItem.kt (2)
11-12: Material3로 마이그레이션 굿!
Divider와Text를 Material3로 변경한 것이 다른 파일들과 일관성 있게 잘 적용되었습니다.
98-105: 프리뷰 데이터에 좌표 추가 확인!
latitude와longitude필드 추가가 DTO 변경사항과 잘 맞아떨어집니다. 네이버 지도 연동을 위한 준비가 되어 있네요.app/src/main/java/com/example/findu/data/mapper/todomain/home/HomeResponseDtoMapper.kt (1)
1-7: 패키지 정리 잘 하셨습니다!
HomeResponseDto를response.home서브패키지로 이동한 것이 구조적으로 더 깔끔해 보입니다. 매퍼 로직은 그대로 유지되어 안전한 리팩토링입니다.app/src/main/java/com/example/findu/presentation/ui/home/viewmodel/HomeViewModel.kt (1)
9-9: UseCase 패키지 이동 확인!
GetHomeUseCase를domain.usecase.home서브패키지로 정리한 것이 일관성 있게 적용되었습니다.app/src/main/java/com/example/findu/data/mapper/toDomain/extra/SidoListDtoMapper.kt (1)
1-12: 시도 목록 매퍼 깔끔합니다!간단하고 명확한 DTO to Domain 매핑입니다. 확장 함수 패턴을 잘 활용했습니다.
app/src/main/java/com/example/findu/data/repositoryimpl/HomeRepositoryImpl.kt (1)
5-5: 매퍼 import 경로 업데이트 확인!
toDomain확장 함수를todomain.home패키지로 정리한 것이 잘 반영되었습니다.app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/HomeRemoteDataSourceImpl.kt (1)
5-5: DTO import 경로 정리 완료!
HomeResponseDto를response.home패키지로 이동한 변경사항이 일관성 있게 적용되었습니다. 전체적인 패키지 정리가 깔끔하게 마무리되었네요.app/src/main/java/com/example/findu/data/dataremote/datasource/HomeRemoteDataSource.kt (1)
4-4: 패키지 리팩토링 확인 완료
HomeResponseDto의 패키지 이동에 맞춰 import 경로가 잘 업데이트되었습니다.app/src/main/java/com/example/findu/di/RepositoryModule.kt (1)
80-82: InformationRepository DI 바인딩 추가 확인
InformationRepository의 DI 바인딩이 기존 패턴대로 잘 추가되었습니다.app/src/main/java/com/example/findu/data/dataremote/model/response/home/HomeResponseDto.kt (1)
1-1: 패키지 구조 개선 확인Home 관련 DTO를 별도 서브패키지로 분리하여 구조가 더 명확해졌습니다.
app/src/main/java/com/example/findu/data/dataremote/service/HomeService.kt (1)
4-4: Import 경로 업데이트 확인
HomeResponseDto의 새 패키지 경로로 import가 정상 업데이트되었습니다.app/src/main/java/com/example/findu/di/ServiceModule.kt (1)
74-77: InformationService 프로바이더 추가 확인
InformationService의 Retrofit 서비스 프로바이더가 기존 패턴대로 잘 추가되었습니다.app/src/main/java/com/example/findu/di/DataSourceModule.kt (1)
100-102: InformationRemoteDataSource 바인딩 추가 확인데이터 소스 바인딩이 기존 패턴대로 잘 추가되었습니다.
app/src/main/java/com/example/findu/domain/usecase/extra/GetSidoUseCase.kt (1)
8-12: GetSidoUseCase 구현 확인
@Singleton과@Inject어노테이션이 잘 적용되어 있고, 표준 UseCase 패턴을 따르고 있습니다.app/src/main/java/com/example/findu/data/dataremote/model/response/information/SidoListDto.kt (1)
1-18: 불필요한 검증 요청입니다. 수정 불필요kotlinx.serialization은 기본적으로 라이브러리와 함께 ProGuard 규칙을 제공하며, 이 규칙들이 축소 후 유지되는 모든 직렬화 가능한 클래스의 직렬화기를 보존하므로 추가 설정이 필요하지 않습니다. 명명된 companion 객체가 있는 클래스를 직렬화하려는 경우에만 추가 규칙이 필요합니다—이는 매우 드문 경우입니다.
현재 코드베이스에서 SidoListDto와 SidoItemDto는 표준 @serializable 데이터 클래스이므로 명시적인 ProGuard 규칙 파일이 필요하지 않습니다. 라이브러리가 모든 필요한 보호를 처리합니다.
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (1)
20-21: ExposedDropdownMenu API는 현재 Material3 1.3.2에서 여전히 Experimental 상태입니다프로젝트에서 사용 중인 Material3 버전은 1.3.2이며, ExposedDropdownMenuBox와 관련 API들이 현재 @ExperimentalMaterial3Api로 표기되어 있습니다. ExtraDistrictItem.kt의
@OptIn(ExperimentalMaterial3Api::class)주석이 적절하게 적용되어 있고, 호출 측(ExtraDepartmentScreen, ExtraCenterScreen)에서는 이미 stabilized된 컴포넌트를 사용하므로 추가 opt-in이 필요하지 않습니다.현재 구현이 올바르므로 별도 조치는 불필요합니다.
app/src/main/java/com/example/findu/data/mapper/todomain/extra/InformationResponseDtoMapper.kt (1)
12-26: 매핑 로직 전반 LGTM필드 매핑 정확하고, 페이징 메타(lastId/isLast) 전달도 적절합니다. 추가 이슈 없습니다.
도메인 모델 nullable 제약과 DTO가 일치하는지만 한 번만 점검해주세요(예: phoneNumber 공백/누락 케이스).
Also applies to: 28-42, 44-55
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (2)
76-89: <!-- [scratchpad_start]
[task review_comment verification 1/1][observations]
- File: HomeExtraViewModel.kt lines 85-88 shows SidoSelected event handler
- Line 86:
_uiState.update { it.copy(selectedSido = event.selectedSido, selectedSigungu = EMPTY_STRING) }- selectedSigungu reset confirmed- Line 87:
getSigungu(event.selectedSido.id)- calls sigunguList reload- File: HomeExtraViewModel.kt lines 230-236 shows getSigungu() implementation
- Line 233:
_uiState.update { it.copy(sigunguList = result) }- sigunguList updated with new result- File: HomeExtraFragment.kt lines 79-81 passes uiState values to ExtraHomeDepartmentScreen
- File: ExtraDepartmentScreen.kt lines 52-53 receives and displays selectedSigungu and sigunguList in UI
- popBackStack callback at line 88 of HomeExtraFragment.kt:
{ findNavController().popBackStack() }- created inline each render[analysis]
- Synchronization concern: When Sido selected, selectedSigungu is reset to "" AND getSigungu() is called immediately in same event handler
- Flow is correct: user selects Sido → SidoSelected event → selectedSigungu set to empty + getSigungu() fetches new list → UI receives both updated values
- Dropdown mismatch risk: MITIGATED - empty selectedSigungu prevents stale selection, new sigunguList arrives before next user interaction
- popBackStack optimization: Creating lambda each time is minor inefficiency; remember would prevent recreation but not critical for functionality
[analysis_complete]
The reviewer's concern about synchronization is already properly handled in the current implementation. The optional refactor suggestion is valid but not essential.
[scratchpad_end]
-->시/군구 동기화는 이미 올바르게 구현되어 있습니다.
시도 선택 시 동기화 흐름을 확인했습니다:
SidoSelected이벤트에서selectedSigungu를 즉시 빈 문자열로 초기화하고getSigungu()를 호출 (HomeExtraViewModel.kt:86-87)getSigungu()는 비동기로 새 시군구 목록을 받아서sigunguList업데이트 (HomeExtraViewModel.kt:233)- UI는 두 상태 변화를 모두 감지하므로 드롭다운 불일치 없음
popBackStack 콜백의
remember최적화는 선택사항입니다. 현재 구현도 함수적으로 문제없지만, 불필요한 람다 재생성을 피하고 싶으면 고려할 수 있습니다.
93-111: SearchCenterFocusedLatLng 연속 호출 방어 추가 필요 + 디바이스 ID 초기화 타이밍 확인 권장
- 클릭 난사 시
SearchCenterFocusedLatLng연속 호출 방지: 현재 VM 레벨에서 throttle/debounce가 없어 API 폭주 위험. 500ms 정도의 짧은 debounce 추가 권장.- 위치 기본값: 이미 37.56, 127.00 (서울 중심) 기본값이 설정되어 있으므로 0.0 우려는 낮음. 다만 GPS 권한 미획득 시 명시적 폴백 로직 추가 검토.
- 디바이스 ID 초기화 타이밍:
SplashActivity.onCreate()에서 설정되는데, HomeExtraFragment 진입 시점에 아직 설정되지 않았다면 빈 문자열 상태로 남을 수 있음. 특히 auth 요청 헤더/파라미터 사용 시 주의 필요.app/src/main/java/com/example/findu/di/UseCaseModule.kt (2)
241-258: 정보 UseCase 바인딩 LGTMDI 스코프/의존성 모두 정상입니다. 별도 이슈 없어 보입니다.
277-287: 시·군·구 조회 UseCase 추가 LGTM초기 화면 진입 시 Sido 프리로드 시나리오에 유용합니다. 별도 문제 없습니다.
app/src/main/java/com/example/findu/domain/repository/InformationRepository.kt (1)
11-13: getVolunteers의 lastId 기본값이 다른 메서드들과 실제로 일관성이 없습니다.코드 검증 결과,
getVolunteers는 전체 계층(도메인, 원격 데이터소스, 서비스, UseCase)에서Long.MAX_VALUE를 기본값으로 사용하고 있는 반면,getCenters와getDepartments는null을 사용하고 있습니다. 이 차이는 일관성 있는 설계가 아닙니다.다만 변경하기 전에 확인이 필요합니다:
- 백엔드 API가 volunteer-works 엔드포인트에서
Long.MAX_VALUE를 "첫 페이지"로 특별 처리하는지 확인하세요- 만약 백엔드가
Long.MAX_VALUE와null을 동일하게 처리한다면, 일관성을 위해null로 변경하는 것이 좋습니다- 만약 백엔드가
Long.MAX_VALUE를 특정 로직으로 처리하고 있다면, 현재 설계는 의도적일 수 있습니다
| suspend fun getCenters( | ||
| lastId: Long? = null, | ||
| district: String? = null, | ||
| lat: Double? = null, | ||
| long: Double? = null, | ||
| size: Int? = null | ||
| ): BaseResponse<CentersResponseDto> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
경도 파라미터 명확화 + 페이지 크기 기본값
long→lng로 리네임을 권장합니다(타입 Long과의 혼동 방지, 지도 도메인 컨벤션 일치).size는 서버 기본 페이지 크기 상수(예:Constraints.DEFAULT_PAGE_SIZE)가 있다면 기본값을 지정해 주는 것이 좋습니다.
suspend fun getCenters(
lastId: Long? = null,
district: String? = null,
lat: Double? = null,
- long: Double? = null,
+ lng: Double? = null,
size: Int? = null
): BaseResponse<CentersResponseDto>호출부/서비스 인터페이스까지 일괄 리네임 필요 여부를 점검해 주세요.
검사용 스크립트:
🏁 Script executed:
#!/bin/bash
rg -nP -C2 '\bgetCenters\s*\(' --type=kotlin
rg -nP -C2 '\blong\s*:\s*Double\?' --type=kotlinLength of output: 10106
파라미터 이름 표준화 필요 — 현재 lon / long 혼용 상태
스크립트 결과에서 파라미터 이름이 계층별로 불일치함을 확인했습니다:
GetCentersUseCase.kt:lon사용InformationRepository/RepositoryImpl/Service/RemoteDataSourceImpl/RemoteDataSource:long사용HomeExtraViewModel호출부:lon사용
이름 변경 시 다음 파일들을 모두 함께 수정해야 합니다:
InformationRemoteDataSource.kt(line 19)InformationRemoteDataSourceImpl.kt(line 25)InformationService.kt(line 27)InformationRepository.kt(line 19)InformationRepositoryImpl.kt(line 31)
추가로 InformationService.kt 의 @Query("long")는 백엔드 API 요구사항인지 확인 후, 필요하면 @Query("lng")로도 함께 변경해야 합니다.
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/data/dataremote/datasource/InformationRemoteDataSource.kt
around lines 15–21, the longitude parameter name is inconsistent with other
layers (uses "long" here but other files use "lon"); rename the parameter from
long to lon in this file's function signature and update all call sites and
overrides accordingly (also update InformationRemoteDataSourceImpl.kt line 25,
InformationService.kt line 27, InformationRepository.kt line 19, and
InformationRepositoryImpl.kt line 31 to use the same name), and when updating
InformationService.kt confirm the backend query parameter name—if backend
expects "lng" change the @Query value to "lng" while keeping the parameter name
consistent as lon in Kotlin signatures.
| suspend operator fun invoke( | ||
| lastId: Long? = null, | ||
| district: String? = null, | ||
| lat: Double? = null, | ||
| lon: Double? = null, | ||
| size: Int? = null |
There was a problem hiding this comment.
좌표는 둘 다 null이거나 둘 다 값이 있어야 합니다 (부분 입력 방지)
lat만(or lon만) 전달되는 케이스를 선제 차단하지 않으면 요청이 깨질 수 있어요. 간단한 입력 검증을 추가해 주세요.
suspend operator fun invoke(
lastId: Long? = null,
district: String? = null,
lat: Double? = null,
lon: Double? = null,
size: Int? = null
): Result<PagedResult<Center>> = repository.getCenters(
+ // 입력 검증 예시: 좌표는 둘 다 null 또는 둘 다 non-null
+ // require((lat == null) == (lon == null)) { "lat/lon은 함께 설정되거나 함께 비워져야 합니다." }필요하다면 size 범위 검증(예: 1..100)도 함께 고려해 주세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/usecase/extra/GetCentersUseCase.kt
around lines 10 to 15, add input validation so latitude and longitude are either
both null or both non-null (reject cases where only one is provided) and
return/throw a clear validation error; implement this by checking (lat == null)
xor (lon == null) and handling it appropriately before proceeding. Also
optionally validate size bounds (e.g., require size in 1..100) and normalize or
reject out-of-range values with a clear error message.
| class GetSigunguUseCase ( | ||
| private val repository: InformationRepository | ||
| ) { | ||
| suspend operator fun invoke(sidoId: Long): Result<List<String>> = repository.getSigungu(sidoId = sidoId) | ||
| } No newline at end of file |
There was a problem hiding this comment.
의존성 주입 어노테이션 누락
GetSigunguUseCase에 @Inject와 @Singleton 어노테이션이 누락되어 Hilt가 이 클래스를 주입할 수 없습니다. 같은 패키지의 GetSidoUseCase와 달리 DI 어노테이션이 빠져있어 런타임 시 주입 실패가 발생합니다.
다음 diff를 적용하여 DI 어노테이션을 추가하세요:
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
-class GetSigunguUseCase (
+class GetSigunguUseCase @Inject constructor(
private val repository: InformationRepository
) {
suspend operator fun invoke(sidoId: Long): Result<List<String>> = repository.getSigungu(sidoId = sidoId)
}🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/usecase/extra/GetSigunguUseCase.kt
around lines 5 to 9, the class is missing Hilt DI annotations so it cannot be
injected; add the @Singleton annotation to the class and annotate its
constructor with @Inject (i.e., make it an @Singleton class with an @Inject
constructor that takes the InformationRepository) and add the necessary imports
for javax.inject.Singleton and javax.inject.Inject (or dagger.hilt annotations
if your project uses those) so Hilt can provide this use case.
| private fun getCenters( | ||
| sido: String = uiState.value.selectedSido.name, | ||
| sigungu : String = uiState.value.selectedSigungu, | ||
| lat : Double = uiState.value.latitude, | ||
| lon:Double = uiState.value.longitude | ||
| ) { | ||
| viewModelScope.launch { | ||
| getCentersUseCase( | ||
| district = sido + sigungu, | ||
| lat = lat, | ||
| lon = lon | ||
| ).fold( |
There was a problem hiding this comment.
버그: 행정구역(district) 문자열 공백 없이 결합됨
district = sido + sigungu는 "서울특별시강남구"처럼 공백이 사라져 서버 필터가 실패할 수 있습니다. 공백 포함 결합(필요 시 null 처리)을 적용하세요.
제안 패치:
- getCentersUseCase(
- district = sido + sigungu,
- lat = lat,
- lon = lon
- ).fold(
+ getCentersUseCase(
+ district = listOf(sido, sigungu).filter { it.isNotBlank() }.joinToString(" ").ifEmpty { null },
+ lat = lat,
+ lon = lon
+ ).fold(보조 함수로 재사용도 가능합니다:
private fun buildDistrict(sido: String, sigungu: String): String? =
listOf(sido, sigungu).filter { it.isNotBlank() }.joinToString(" ").ifBlank { null }🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt
around lines 138 to 149, the district is built by concatenating sido + sigungu
which removes the separating space (e.g., "서울특별시강남구") and can break server
filtering; change to build a district string that joins non-blank sido and
sigungu with a single space and returns null if the result is blank (or handle
null where used), ideally by creating a small helper like buildDistrict(sido,
sigungu) that filters blank parts, joins with " ", and maps blank to null, then
use that helper when calling getCentersUseCase.
| private fun getDepartments() { | ||
| viewModelScope.launch { | ||
| getDepartmentsUseCase(district = uiState.value.selectedSido.name + uiState.value.selectedSigungu).fold( | ||
| onSuccess = { list -> | ||
| _uiState.update { | ||
| it.copy( |
There was a problem hiding this comment.
동일 이슈: 부서 조회 district 결합 로직
여기도 공백 없이 결합됩니다. 위와 동일한 방식으로 수정해주세요.
제안 패치:
- getDepartmentsUseCase(district = uiState.value.selectedSido.name + uiState.value.selectedSigungu).fold(
+ getDepartmentsUseCase(
+ district = listOf(uiState.value.selectedSido.name, uiState.value.selectedSigungu)
+ .filter { it.isNotBlank() }
+ .joinToString(" ")
+ .ifEmpty { null }
+ ).fold(🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/extra/viewmodel/HomeExtraViewModel.kt
around lines 171-176, the district string is currently formed by concatenating
selectedSido.name and selectedSigungu without a separator; change the
concatenation to include a space (or appropriate separator) between the two
values (e.g., selectedSido.name + " " + selectedSigungu or use string
interpolation), ensuring the same null-safety/format used elsewhere in the
codebase.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (3)
40-43: fusedLocationProvider 초기화 컨텍스트 일관화 제안
requireActivity()대신requireContext()를 쓰면 액티비티 참조를 불필요하게 붙잡지 않고도 동일 동작을 얻습니다. lazy 자체는 OK.다음처럼 변경을 고려해 주세요:
- LocationServices.getFusedLocationProviderClient(requireActivity()) + LocationServices.getFusedLocationProviderClient(requireContext())
156-188: lastLocation 단일 경로는 NULL/STALE 가능 — 현재 위치 Fallback 권장
lastLocation이 null이거나 오래된 값일 수 있어 신뢰도가 떨어집니다. 실패/NULL 시getCurrentLocation(Priority…)로 한 번 더 시도하고, 그래도 실패하면LoadData로 폴백하는 흐름을 권장합니다. 토스트 문자열은 string 리소스로 이동해 주세요.예시(핵심 로직만):
fusedLocationClient.lastLocation .addOnSuccessListener { location -> if (location != null) { homeExtraViewModel.handleEvent(HomeExtraUiEvent.UpdateLocation(location.latitude, location.longitude)) homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData) } else { val cts = CancellationTokenSource() fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, cts.token) .addOnSuccessListener { current -> current?.let { homeExtraViewModel.handleEvent(HomeExtraUiEvent.UpdateLocation(it.latitude, it.longitude)) } homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData) } .addOnFailureListener { homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData) } } } .addOnFailureListener { homeExtraViewModel.handleEvent(HomeExtraUiEvent.LoadData) }추가 필요 import:
import com.google.android.gms.location.Priority
import com.google.android.gms.tasks.CancellationTokenSource
84-90: StateFlow에 distinctUntilChanged 적용 권장 - 중복 이벤트 트리거 방지검증 결과,
_uiState = MutableStateFlow(HomeExtraUiState())에distinctUntilChanged가 미적용되어 있습니다. 같은homeExtraButtonType값이 반복 업데이트되어도LaunchedEffect(uiState.homeExtraButtonType)(84-90줄)가 매번 재실행되어requestLocationAndLoadData()중복 호출 가능성이 실제로 존재합니다.HomeExtraViewModel의
uiState노출 시점(109-114줄)에.distinctUntilChanged()를 추가하면 불필요한 중복 트리거를 방지할 수 있습니다.app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (4)
99-106: 센터 도착 시 자동 카메라 점프 조건부 처리 권장센터 목록이 갱신될 때마다 첫 센터로 카메라가 이동하면 사용자 제스처를 방해할 수 있습니다. 최초 1회만 이동하거나, 사용자가 직접 이동했는지 플래그로 구분하세요.
예:
var autoCentered by rememberSaveable { mutableStateOf(false) } LaunchedEffect(centers, autoCentered) { if (!autoCentered && centers.isNotEmpty()) { cameraPositionState.animate(CameraUpdate.scrollTo(...)) autoCentered = true } }
127-138: Marker에 안정적 key 부여로 상태 보존루프 안
rememberMarkerState는 키 없이 재구성 시 상태 매칭이 흔들릴 수 있습니다. 각 마커에 안정적인 key를 부여하세요.- visibleCenters.forEach { center -> - Marker( - state = rememberMarkerState(position = LatLng(center.latitude, center.longitude)), - icon = OverlayImage.fromResource(R.drawable.ic_home_extra_volunteer_gps_20) - ) - } + visibleCenters.forEach { center -> + key("${center.latitude},${center.longitude},${center.centerName}") { + Marker( + state = rememberMarkerState(position = LatLng(center.latitude, center.longitude)), + icon = OverlayImage.fromResource(R.drawable.ic_home_extra_volunteer_gps_20) + ) + } + }(필요 시
import androidx.compose.runtime.key)
215-228: 하드코딩 텍스트/접근성
- "현 지도에서 검색 " 문자열과 토스트 문구는 string 리소스로 이동 권장.
- 위치 아이콘에
contentDescription추가해 접근성 보완.- BaseVectorIcon( + BaseVectorIcon( vectorResource = R.drawable.ic_place_now_24, modifier = ..., + contentDescription = stringResource(R.string.cd_move_to_my_location) ) - Text(text = "현 지도에서 검색 ", ...) + Text(text = stringResource(R.string.home_extra_search_here), ...)(주의:
R.string.cd_move_to_my_location,R.string.home_extra_search_here추가 필요)
118-121: 320dp/340dp 매직 넘버로 레이아웃 간섭 가능맵 패딩(320dp)과 바텀 시트 높이(340dp)가 고정/상이하여 기기별 레이아웃 겹침 위험이 있습니다. 시트 실제 높이를
onGloballyPositioned로 측정해 맵 패딩에 반영하거나, ModalBottomSheet 등 컴포저블을 사용해 자동 레이아웃 조정을 맡기는 방식을 권장합니다.예(개념):
var sheetHeightPx by remember { mutableIntStateOf(0) } val sheetHeightDp = with(LocalDensity.current) { sheetHeightPx.toDp() } NaverMap(modifier = Modifier.padding(bottom = sheetHeightDp).fillMaxSize(), ...) Column(modifier = Modifier.onGloballyPositioned { sheetHeightPx = it.size.height } /* ... */) { ... }Also applies to: 144-146, 232-256
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt(5 hunks)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (3)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraVolunteerScreen.kt (1)
ExtraHomeVolunteerScreen(14-32)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraDepartmentScreen.kt (1)
ExtraHomeDepartmentScreen(22-66)app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (1)
ExtraHomeCenterScreen(61-259)
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (4)
app/src/main/java/com/example/findu/presentation/ui/base/TopAppBar.kt (1)
FindUTopAppBar(19-66)app/src/main/java/com/example/findu/presentation/ui/extra/component/ExtraDistrictItem.kt (1)
ExtraDistrictItem(32-86)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/extra/component/ExtraCenterItem.kt (1)
ExtraCenterItem(30-92)
🔇 Additional comments (2)
app/src/main/java/com/example/findu/presentation/ui/extra/HomeExtraFragment.kt (1)
121-143: 스크린 파라미터 전달 구조는 명확합니다선택 상태와 핸들러, 좌표 전달 방식 모두 일관적입니다. 👍
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt (1)
261-302: 프리뷰 구성 좋습니다API 변경 사항을 반영한 파라미터/더미 데이터가 일관적입니다. 👍
| val permissionLauncher = rememberLauncherForActivityResult( | ||
| contract = ActivityResultContracts.RequestPermission(), | ||
| onResult = { isGranted -> | ||
| if (!isGranted) { | ||
| Toast.makeText(context, "위치 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show() | ||
| } | ||
| } | ||
| ) |
There was a problem hiding this comment.
권한 런처를 복수 권한으로 통일 + 문자열 리소스화
Fragment는 FINE/COARSE 동시 요청인데, 여기서는 단일(FINE)만 요청합니다. COARSE만 허용된 기기에서 반복 프롬프트가 발생할 수 있어 UX 저하입니다. 복수 권한으로 통일하고 토스트 문자열은 리소스로 이동해 주세요.
- val permissionLauncher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.RequestPermission(),
- onResult = { isGranted ->
- if (!isGranted) {
- Toast.makeText(context, "위치 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show()
- }
- }
- )
+ val permissionLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestMultiplePermissions(),
+ onResult = { perms ->
+ val granted = perms[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
+ perms[Manifest.permission.ACCESS_COARSE_LOCATION] == true
+ if (!granted) {
+ Toast.makeText(context, context.getString(R.string.permission_location_denied), Toast.LENGTH_SHORT).show()
+ }
+ }
+ )(주의: R.string.permission_location_denied 추가 필요)
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/extra/view/ExtraCenterScreen.kt
around lines 80 to 87, the permission launcher currently requests only a single
fine location permission causing inconsistent behavior versus the Fragment
(which requests both FINE and COARSE) and using a hardcoded toast string; change
the launcher to rememberLauncherForActivityResult with
ActivityResultContracts.RequestMultiplePermissions(), handle the Map<String,
Boolean> result to treat either ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION
being true as granted (and show the denied toast only if both are false), and
replace the hardcoded message with
context.getString(R.string.permission_location_denied) (add
R.string.permission_location_denied to resources if not present).
| BaseVectorIcon( | ||
| vectorResource = R.drawable.ic_place_now_24, | ||
| modifier = Modifier | ||
| .roundedBackgroundWithPadding( | ||
| backgroundColor = FindUTheme.colors.white, | ||
| cornerRadius = 12.dp, | ||
| padding = PaddingValues(12.dp) | ||
| ) | ||
| .noRippleClickable { | ||
| when (PackageManager.PERMISSION_GRANTED) { | ||
| ContextCompat.checkSelfPermission( | ||
| context, | ||
| Manifest.permission.ACCESS_FINE_LOCATION | ||
| ) -> { | ||
| scope.launch { | ||
| cameraPositionState.animate( | ||
| CameraUpdate.scrollAndZoomTo( | ||
| LatLng(latitude, longitude), INITIAL_ZOOM_LEVEL | ||
| ) | ||
| ) | ||
| } | ||
| searchCurrentLocation(LatLng(latitude, longitude)) | ||
| } | ||
|
|
||
| else -> { | ||
| Toast | ||
| .makeText(context, "현재 위치로 이동하려면 위치 권한이 필요합니다.", Toast.LENGTH_SHORT) | ||
| .show() | ||
| permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) | ||
| } | ||
| } | ||
| } | ||
| ) |
There was a problem hiding this comment.
현재 위치 버튼: 권한 판별/요청 로직 정합성
여기서는 FINE만 체크합니다. COARSE만 허용된 경우에도 계속 FINE을 재요청하게 됩니다. Fragment와 동일 기준(둘 중 하나 허용 시 진행)으로 맞춰 주세요. 또한 카메라 이동 자체는 권한 없이도 가능한 UI 동작이어서 “권한 없으면 이동 불가” UX는 굳이 필요 없습니다.
- when (PackageManager.PERMISSION_GRANTED) {
- ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) -> {
- // animate & search
- }
- else -> {
- Toast.makeText(context, "현재 위치로 이동하려면 위치 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
- permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
- }
- }
+ val hasLocation = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
+ ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ if (hasLocation) {
+ scope.launch {
+ cameraPositionState.animate(CameraUpdate.scrollAndZoomTo(LatLng(latitude, longitude), INITIAL_ZOOM_LEVEL))
+ }
+ searchCurrentLocation(LatLng(latitude, longitude))
+ } else {
+ Toast.makeText(context, context.getString(R.string.permission_location_required_for_current_place), Toast.LENGTH_SHORT).show()
+ permissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
+ }(주의: R.string.permission_location_required_for_current_place 추가 필요)
Committable suggestion skipped: line range outside the PR's diff.
ikseong00
left a comment
There was a problem hiding this comment.
좋습니다! 특별히 리뷰할 부분 없이 깔끔하게 잘 짜신 것 같아요!
Related issue 🛠
Work Description 📝
Screenshot 📸
Uncompleted Tasks 😅
To Reviewers 📢
디바이스 아이디 설정 시점을 스플래시로 수정했으니 아직도 빈값이 들어가는지 확인 부탁드려요!
제 환경에서는 정상적으로 들어가고 있습니당
Summary by CodeRabbit
릴리스 노트
새로운 기능
스타일
기타