Skip to content

feat: 온보딩 프로필 이미지 등록 및 UX 개선#207

Merged
kimjw2003 merged 5 commits into
developfrom
FLT-8-프로필-이미지-등록-기능
Jun 2, 2026

Hidden character warning

The head ref may contain hidden characters: "FLT-8-\ud504\ub85c\ud544-\uc774\ubbf8\uc9c0-\ub4f1\ub85d-\uae30\ub2a5"
Merged

feat: 온보딩 프로필 이미지 등록 및 UX 개선#207
kimjw2003 merged 5 commits into
developfrom
FLT-8-프로필-이미지-등록-기능

Conversation

@kimjw2003

@kimjw2003 kimjw2003 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 스플래시 화면 자동 로그인 기능 구현
  • 프로필 이미지 등록 기능 구현 (Presigned URL → S3 업로드)
  • 온보딩 순서 변경: 프로필 설정 → 약관 동의 → 약관 동의 → 프로필 설정
  • 닉네임 입력 UX 개선
    • 자음/모음 단독 입력 시 토스트 없이 확인/다음 버튼 비활성화
    • 한글 자모 중간 입력 허용하도록 정규식 수정
    • 키보드 올라올 때 다음 버튼도 함께 올라오도록 imePadding 적용
  • 약관 목록 스크롤 추가
  • API 스펙 업데이트
    • SearchApi genre → List<String>, mediaType 추가, cursor 타입 String?으로 변경
    • SearchContentsResponseDto Meta 필드 추가 (page, size, totalElements, totalPages)
    • StorageApi presigned URL 응답에서 BaseResponse 래핑 제거
    • S3 OkHttpClient에 로깅 인터셉터 추가

Test plan

  • 스플래시 → 자동 로그인 동작 확인
  • 온보딩 진입 시 약관 동의 화면이 첫 번째로 표시되는지 확인
  • 프로필 이미지 선택 → S3 업로드 → 회원가입 요청에 profileImageUrl 포함 확인
  • 닉네임 입력 중 자음/모음만 있을 때 확인/다음 버튼 비활성 및 토스트 미표시 확인
  • 키보드 올라올 때 다음 버튼이 키보드 위로 올라오는지 확인
  • 약관 목록이 많을 때 스크롤 가능한지 확인

🤖 Generated with Claude Code

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 회원가입 중 프로필 이미지 업로드 지원
    • 검색 필터 개선: 장르 다중 선택 및 미디어 타입 필터 추가
  • 개선사항

    • 토스트 메시지가 키보드에 가려지지 않도록 레이아웃 조정
    • 약관 화면 스크롤 기능 추가
    • 온보딩 흐름 및 자동 로그인 프로세스 개선

kimjw2003 and others added 4 commits May 17, 2026 03:48
- S3 업로드용 OkHttpClient 분리 및 qualifier 추가
- StorageRepository에 presigned URL 발급 및 S3 업로드 로직 추가
- SignupRequestModel/Dto에 profileImageUrl 필드 추가
- 온보딩 프로필 화면에서 이미지 선택 상태를 ViewModel로 끌어올림
- /contents/search 요청은 Authorization 헤더 제외

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 온보딩 순서 변경: 약관 동의 → 프로필 설정 순으로 변경
- 닉네임 자음/모음 단독 입력 시 토스트 없이 확인/다음 버튼 비활성화
- 닉네임 포맷 검증 정규식에 한글 자모(자음·모음) 중간 입력 허용
- 프로필 화면 키보드 위로 버튼 올라오도록 imePadding 적용
- 약관 목록 스크롤 추가
- SearchApi genre 파라미터를 List<String>으로 변경, mediaType/cursor 타입 업데이트
- SearchContentsResponseDto Meta 필드 추가 (page, size, totalElements, totalPages)
- StorageApi presigned URL 응답 BaseResponse 래핑 제거
- S3 OkHttpClient에 로깅 인터셉터 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@kimjw2003, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 26 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e10ca304-2b1c-4e26-9914-2337dcfb5c4c

📥 Commits

Reviewing files that changed from the base of the PR and between eda5175 and 7ad5a73.

📒 Files selected for processing (4)
  • app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt
  • app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt
  • app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt
  • app/src/main/java/com/flint/presentation/splash/SplashViewModel.kt
📝 Walkthrough

Walkthrough

온보딩 프로필 이미지 업로드 기능을 S3와 연동하고, 닉네임 검증 로직을 강화하며, 자동 로그인 상태 관리를 명시적인 상태 머신으로 재구성합니다. 검색 API 계약도 다중 장르 필터링을 지원하도록 확장합니다.

Changes

온보딩 프로필 업로드 플로우

Layer / File(s) Summary
S3 DI 인프라 구축
app/src/main/java/com/flint/data/di/qualifier/S3OkHttpClient.kt, app/src/main/java/com/flint/data/di/NetworkModule.kt, app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt
S3 전용 @S3OkHttpClient 한정자와 provideS3OkHttpClient() DI 제공자를 추가하여 S3 업로드용 클라이언트를 분리하고, 검색 요청(/contents/search)에서 Authorization 헤더 주입을 제외합니다.
StorageApi 및 S3 업로드 구현
app/src/main/java/com/flint/data/api/StorageApi.kt, app/src/main/java/com/flint/domain/repository/StorageRepository.kt
StorageApi의 응답 타입을 BaseResponse<PresignedUrlResponseDto>에서 PresignedUrlResponseDto로 단순화하고, StorageRepository에 S3 클라이언트를 주입하여 uploadToS3(uploadUrl, imageBytes, mimeType) 메서드를 구현합니다.
온보딩 라우트 및 네비게이션
app/src/main/java/com/flint/core/navigation/Route.kt, app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt
OnboardingProfiletempToken 파라미터를 가진 data class에서 매개변수 없는 data object로 변경하고, 온보딩 흐름을 Terms → Profile → Content 순서로 개편합니다.
온보딩 UI 상태 및 검증
app/src/main/java/com/flint/presentation/onboarding/OnboardingUiState.kt
profileImageUri: Uri? 필드를 추가하고, 한글 자모 독립 문자를 감지하는 hasStandaloneKorean 파생 속성을 도입하며, canCheckNicknamecanProceed 검증 조건을 세분화합니다.
온보딩 프로필 화면 UI
app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt, app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt
프로필 이미지 선택/삭제 콜백을 상위 계층으로 전파하고, canCheckNickname 기반 버튼 활성화로 전환하며, IME 및 window inset 안전 여백(imePadding, consumeWindowInsets)을 적용합니다. 약관 목록 영역을 수직 스크롤 가능하도록 변경합니다.
온보딩 ViewModel 및 회원가입 통합
app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt, app/src/main/java/com/flint/domain/model/auth/SignupModel.kt, app/src/main/java/com/flint/data/dto/auth/request/SignupRequestDto.kt, app/src/main/java/com/flint/domain/mapper/auth/SignupMapper.kt
StorageRepositoryContext를 주입받아 updateProfileImage(uri) 메서드를 추가하고, uploadProfileImageIfNeeded()mimeTypeToFileExtension() 유틸 함수를 구현하여 이미지 업로드 결과를 회원가입 요청에 포함합니다.

검색 기능 업데이트 및 자동 로그인 상태

Layer / File(s) Summary
검색 API 및 Repository
app/src/main/java/com/flint/data/api/SearchApi.kt, app/src/main/java/com/flint/domain/repository/SearchRepository.kt, app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt
SearchApi.getSearchContentList()genreList<String>?로 변경하고 mediaType 쿼리를 추가하며, cursorString?로 타입 변경합니다. SearchContentsResponseDto.Metapage, size, totalElements, totalPages 페이징 필드를 추가합니다.
자동 로그인 상태 관리
app/src/main/java/com/flint/presentation/splash/SplashViewModel.kt, app/src/main/java/com/flint/presentation/splash/SplashScreen.kt
SplashViewModelsealed interface AutoLoginState (Loading, NavigateToHome, NavigateToLogin)를 도입하고 checkAutoLogin() 로직을 구현합니다. SplashScreen에서 애니메이션 완료 상태와 autoLoginState를 결합하여 네비게이션을 수행합니다.

지원 기능 및 API 업데이트

Layer / File(s) Summary
Auth API 및 UI 개선
app/src/main/java/com/flint/data/api/AuthApi.kt, app/src/main/java/com/flint/core/designsystem/component/toast/ShowToast.kt
AuthApi.withdraw() 엔드포인트를 DELETE에서 POST로 변경합니다. ShowToastimePadding()을 추가하여 키보드 등장 시 토스트가 가려지지 않도록 합니다.

Sequence Diagram(s)

sequenceDiagram
  participant User as 사용자
  participant OnboardingScreen as 온보딩 화면
  participant ViewModel as OnboardingViewModel
  participant Storage as StorageRepository
  participant S3 as S3 저장소
  participant API as SignupApi

  User->>OnboardingScreen: 프로필 이미지 선택
  OnboardingScreen->>ViewModel: updateProfileImage(uri)
  ViewModel->>ViewModel: uploadProfileImageIfNeeded()
  ViewModel->>Storage: getPresignedUrl(pathType, extension)
  Storage->>API: presigned URL 요청
  API-->>Storage: presignedUrl 응답
  Storage->>S3: PUT 요청 (imageBytes)
  S3-->>Storage: 업로드 완료
  Storage-->>ViewModel: 업로드 URL 반환
  
  User->>OnboardingScreen: 닉네임 입력 및 확인
  OnboardingScreen->>ViewModel: 닉네임 중복 확인
  ViewModel->>API: signup(profileImageUrl 포함)
  API-->>ViewModel: 회원가입 완료
Loading
sequenceDiagram
  participant App as 앱 시작
  participant SplashViewModel
  participant PreferencesManager as 저장소
  participant SplashScreen as 스플래시 화면

  App->>SplashViewModel: checkAutoLogin()
  SplashViewModel->>PreferencesManager: ACCESS_TOKEN 조회
  PreferencesManager-->>SplashViewModel: 토큰 값
  
  alt 토큰 존재
    SplashViewModel->>SplashViewModel: state = NavigateToHome
  else 토큰 없음
    SplashViewModel->>SplashViewModel: state = NavigateToLogin
  end
  
  SplashScreen->>SplashScreen: 애니메이션 시작
  SplashScreen->>SplashScreen: 애니메이션 완료 (isAnimationFinished = true)
  SplashScreen->>SplashScreen: isAnimationFinished && autoLoginState 조건 확인
  SplashScreen->>App: 홈 또는 로그인 화면 네비게이션
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • imflint/Flint-Android#206: SearchApi.getSearchContentList() 검색 파라미터 구조(genre 타입, mediaType 추가, cursor 변경) 변경이 동일하게 적용되어 검색 연동 코드 흐름이 일치합니다.
  • imflint/Flint-Android#152: 두 PR 모두 TokenInterceptor의 Authorization 헤더 조건 로직을 수정하므로 인터셉터 코드 레벨에서 직접 연관됩니다.
  • imflint/Flint-Android#122: 본 PR이 Route.OnboardingProfiletempToken 파라미터를 제거(data classdata object)하는 변경은 카카오 로그인 흐름에서 tempToken을 전파하는 검색 PR #122와 직접 충돌합니다.

Suggested labels

Feat ✨, 🔖 API, 📱 UI

Suggested reviewers

  • chanmi1125

Poem

🐰 프로필 사진을 S3에 올리고,
닉네임을 검증하는 토끼의 손길,
온보딩의 길을 다시 그으며,
로그인 상태를 명확히 밝히네.
검색도 더욱 유연해지고,
토스트는 키보드 아래 숨지 않으리! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명에는 변경사항 요약, 테스트 계획, API 스펙 업데이트 등이 포함되어 있으나, 템플릿의 필수 섹션이 부분적으로 누락되어 있습니다. 관련 이슈 번호(#번호), 스크린샷, 그리고 리뷰어에게 전달할 추가 정보 섹션을 템플릿 형식에 맞춰 추가하면 더 완전해질 것입니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경사항인 온보딩 프로필 이미지 등록과 UX 개선을 명확하게 요약하고 있으며, 변경 파일 목록의 핵심 내용과 일치합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch FLT-8-프로필-이미지-등록-기능

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kimjw2003 kimjw2003 self-assigned this Jun 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt (1)

61-61: ⚡ Quick win

오해를 부르는 파라미터 연결: 약관 다음 단계가 프로필인데 이름은 navigateToOnboardingContent입니다.

새 온보딩 순서(약관 → 프로필 → 콘텐츠)에 맞춰 동작 자체는 올바르나, OnboardingTermsRoute의 콜백 파라미터 이름이 실제 목적지(프로필)와 어긋나 유지보수 시 혼동을 유발합니다. OnboardingTermsScreen.kt의 선언부와 함께 이름을 정정하는 것을 권장합니다.

♻️ 제안: 파라미터 이름 정정
-                navigateToOnboardingContent = navController::navigateToOnboardingProfile,
+                navigateToOnboardingProfile = navController::navigateToOnboardingProfile,

OnboardingTermsRoute/OnboardingTermsScreen.kt의 파라미터 선언과 호출부도 함께 navigateToOnboardingProfile(또는 onNext)로 변경하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt`
at line 61, The parameter name navigateToOnboardingContent is misleading because
the Terms screen actually navigates to the profile; update the parameter name in
OnboardingTermsRoute and OnboardingTermsScreen to navigateToOnboardingProfile
(or a neutral onNext) and adjust all call sites (e.g., where you pass
navController::navigateToOnboardingProfile) to use the new name so declaration
and invocation match (refer to symbols: OnboardingTermsRoute,
OnboardingTermsScreen, navigateToOnboardingContent,
navigateToOnboardingProfile).
app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt (1)

165-193: ⚡ Quick win

스크롤 Column에 weight(1f) 추가를 권장합니다.

Loading/Failure 분기는 .weight(1f)로 남은 공간을 채우는 반면 이 스크롤 분기에는 높이 제약이 없어, 약관이 길 때 스크롤 영역 경계가 의도와 다르게 잡히거나 하단 버튼과의 레이아웃이 불안정해질 수 있습니다. 다른 분기와 동일하게 제약을 맞추는 것이 안전합니다.

♻️ 제안
                     Column(
                         modifier = Modifier
+                            .weight(1f)
                             .verticalScroll(rememberScrollState())
                             .padding(horizontal = 16.dp),
                     ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt`
around lines 165 - 193, The scrollable Column in the else branch of
OnboardingTermsScreen lacks a height constraint and should be given
Modifier.weight(1f) so it matches the Loading/Failure branches; update the
Column's modifier (the one using
.verticalScroll(rememberScrollState()).padding(...)) to include .weight(1f)
(e.g., Modifier.weight(1f).verticalScroll(...).padding(...)) so the scroll area
is constrained and layout with the bottom buttons remains stable; keep the
existing verticalScroll and padding and adjust checkedStates/expandedStates
handling unchanged.
app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt (1)

26-29: 💤 Low value

검색 경로 매칭을 정확 일치로 좁히는 편이 안전합니다.

endsWith("/contents/search")/api/v2/contents/search 같은 다른 버전 경로뿐 아니라 의도치 않게 끝부분만 일치하는 경로에도 모두 매칭됩니다. 인증 헤더 누락 대상을 명확히 하려면 정확한 경로(예: 정규화된 전체 path 비교 또는 상수화)로 좁히는 것을 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt` around
lines 26 - 29, The current isSearchRequest check in TokenInterceptor.kt uses
originalRequest.url.encodedPath.endsWith("/contents/search"), which can falsely
match other paths; change it to an exact path comparison or constant-based
match: compute the normalized full path from originalRequest.url (or use a
predefined constant like CONTENTS_SEARCH_PATH) and compare equality (e.g., path
== "/api/v2/contents/search") when setting isSearchRequest, then keep the
existing logic that skips adding the Authorization header via
requestBuilder.header("Authorization", "Bearer $accessToken") only when
accessToken is non-empty and isSearchRequest is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt`:
- Around line 39-40: The property totalElements in SearchContentsResponseDto
(inside the Meta/data class) is declared as String? while page/size/totalPages
are Int?; change totalElements to Int? to match types and avoid Json decoding
errors, and if the backend may emit numbers as strings ensure the serializer
configuration or a custom `@Serializable` converter is used for the totalElements
property to accept numeric strings (update the Meta class property named
totalElements accordingly).

In `@app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`:
- Around line 229-237: The signup flow silently proceeds when
uploadProfileImageIfNeeded() returns null after a user selected an image; update
OnboardingViewModel so that after calling uploadProfileImageIfNeeded() you check
if the user had a selected image (e.g., _uiState.value.profileImageUri or
equivalent) and the returned profileImageUrl is null, then emit a UI event
(toast/snackbar) or trigger a retry/confirmation flow instead of building
SignupRequestModel and continuing; modify the code around
uploadProfileImageIfNeeded() and the signup creation (where SignupRequestModel
is constructed) to either abort/signup-block on failure or present a retry
dialog/notification to the user so the missing upload isn’t silent.
- Around line 251-278: The profile image MIME/type lookup and byte reading in
uploadProfileImageIfNeeded() are performed on the main thread; wrap the blocking
calls (context.contentResolver.getType(uri) and
context.contentResolver.openInputStream(uri)?.use { it.readBytes() }) inside
withContext(Dispatchers.IO) so the I/O runs on Dispatchers.IO, and add the
necessary imports (kotlinx.coroutines.Dispatchers,
kotlinx.coroutines.withContext); preserve the existing error handling and return
behavior while moving only the blocking I/O into the IO context.

In `@app/src/main/java/com/flint/presentation/splash/SplashViewModel.kt`:
- Around line 33-40: Handle DataStore read failures in checkAutoLogin by
wrapping the preferencesManager.getString(ACCESS_TOKEN).first() call in a
try/catch inside the existing viewModelScope.launch; on any exception catch
(e.g., IOException) log/report it and set _autoLoginState.value =
AutoLoginState.NavigateToLogin so the splash doesn't hang, preserving the
existing NavigateToHome behavior when a non-empty token is read.

---

Nitpick comments:
In `@app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt`:
- Around line 26-29: The current isSearchRequest check in TokenInterceptor.kt
uses originalRequest.url.encodedPath.endsWith("/contents/search"), which can
falsely match other paths; change it to an exact path comparison or
constant-based match: compute the normalized full path from originalRequest.url
(or use a predefined constant like CONTENTS_SEARCH_PATH) and compare equality
(e.g., path == "/api/v2/contents/search") when setting isSearchRequest, then
keep the existing logic that skips adding the Authorization header via
requestBuilder.header("Authorization", "Bearer $accessToken") only when
accessToken is non-empty and isSearchRequest is true.

In
`@app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt`:
- Line 61: The parameter name navigateToOnboardingContent is misleading because
the Terms screen actually navigates to the profile; update the parameter name in
OnboardingTermsRoute and OnboardingTermsScreen to navigateToOnboardingProfile
(or a neutral onNext) and adjust all call sites (e.g., where you pass
navController::navigateToOnboardingProfile) to use the new name so declaration
and invocation match (refer to symbols: OnboardingTermsRoute,
OnboardingTermsScreen, navigateToOnboardingContent,
navigateToOnboardingProfile).

In
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt`:
- Around line 165-193: The scrollable Column in the else branch of
OnboardingTermsScreen lacks a height constraint and should be given
Modifier.weight(1f) so it matches the Loading/Failure branches; update the
Column's modifier (the one using
.verticalScroll(rememberScrollState()).padding(...)) to include .weight(1f)
(e.g., Modifier.weight(1f).verticalScroll(...).padding(...)) so the scroll area
is constrained and layout with the bottom buttons remains stable; keep the
existing verticalScroll and padding and adjust checkedStates/expandedStates
handling unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9eb55517-fc78-4835-9691-20f049f1a35d

📥 Commits

Reviewing files that changed from the base of the PR and between e10c479 and eda5175.

📒 Files selected for processing (21)
  • app/src/main/java/com/flint/core/designsystem/component/toast/ShowToast.kt
  • app/src/main/java/com/flint/core/navigation/Route.kt
  • app/src/main/java/com/flint/data/api/AuthApi.kt
  • app/src/main/java/com/flint/data/api/SearchApi.kt
  • app/src/main/java/com/flint/data/api/StorageApi.kt
  • app/src/main/java/com/flint/data/di/NetworkModule.kt
  • app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt
  • app/src/main/java/com/flint/data/di/qualifier/S3OkHttpClient.kt
  • app/src/main/java/com/flint/data/dto/auth/request/SignupRequestDto.kt
  • app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt
  • app/src/main/java/com/flint/domain/mapper/auth/SignupMapper.kt
  • app/src/main/java/com/flint/domain/model/auth/SignupModel.kt
  • app/src/main/java/com/flint/domain/repository/SearchRepository.kt
  • app/src/main/java/com/flint/domain/repository/StorageRepository.kt
  • app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt
  • app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt
  • app/src/main/java/com/flint/presentation/onboarding/OnboardingUiState.kt
  • app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt
  • app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt
  • app/src/main/java/com/flint/presentation/splash/SplashScreen.kt
  • app/src/main/java/com/flint/presentation/splash/SplashViewModel.kt

Comment on lines +39 to +40
@SerialName("totalElements")
val totalElements: String? = null,

@coderabbitai coderabbitai Bot Jun 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does kotlinx.serialization Json decode a JSON number into a Kotlin String property or throw by default?

💡 Result:

By default, kotlinx.serialization’s Json decoder expects a JSON string token (quoted) for properties of Kotlin type String; if the input provides a JSON number instead, decoding fails (throws a SerializationException/JsonDecodingException), rather than silently converting the number to a String. Evidence: - The Json configuration option that would allow coercing certain wrong types is coerceInputValues, and it is false by default. The docs state it only coerces specific cases (e.g., JSON null into a non-nullable property, unknown enum members) and do not mention number-to-string coercion [1]. - Json decoding is token/type-driven: the core Decoder API exposes decodeString() as “Decodes a string value. Corresponding kind is PrimitiveKind.STRING”, i.e., the deserializer expects the underlying input element to be a string kind [2]. - The kotlinx.serialization issue discussions show that when a decoder calls decodeString() but the underlying JSON token is numeric, decodeString() throws an exception (example shows “Expected string literal with quotes” after decoding a numeric token) [3]. So the answer to your question is: it does not decode a JSON number into a Kotlin String property by default; it throws by default.

Citations:


totalElements 타입을 Int?로 통일하세요

  • Meta에서 page/size/totalPagesInt?인데 totalElementsString?입니다.
  • kotlinx.serialization Json은 기본적으로 String 프로퍼티에 대해 JSON number를 문자열로 자동 변환하지 않으며, 타입이 맞지 않으면 디코딩 예외가 발생합니다(백엔드가 숫자로 내리는지/문자열로 내리는지와 현재 Json 설정을 확인 필요).
🐛 제안 수정
         `@SerialName`("totalElements")
-        val totalElements: String? = null,
+        val totalElements: Int? = null,
📝 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
@SerialName("totalElements")
val totalElements: String? = null,
`@SerialName`("totalElements")
val totalElements: Int? = null,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt`
around lines 39 - 40, The property totalElements in SearchContentsResponseDto
(inside the Meta/data class) is declared as String? while page/size/totalPages
are Int?; change totalElements to Int? to match types and avoid Json decoding
errors, and if the backend may emit numbers as strings ensure the serializer
configuration or a custom `@Serializable` converter is used for the totalElements
property to accept numeric strings (update the Meta class property named
totalElements accordingly).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

❌ 반영하지 않았습니다. Swagger 스펙에서 "totalElements": "123" 처럼 따옴표가 있는 문자열로 응답이 정의되어 있어 현재 String?이 올바른 타입입니다. 실제 서버 응답도 동일하게 문자열로 내려오는 것을 확인했습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@kimjw2003, 확인해 주셔서 감사합니다! Swagger 스펙과 실제 서버 응답이 모두 문자열로 내려온다면 String?이 올바른 타입이 맞습니다. 제 제안이 잘못되었습니다. 향후 리뷰에 반영해 두겠습니다.

(ᓀ‸ᓂ)


✏️ Learnings added
Learnt from: kimjw2003
Repo: imflint/Flint-Android PR: 207
File: app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt:39-40
Timestamp: 2026-06-02T12:11:59.612Z
Learning: In `app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt`, the `totalElements` field inside the `Meta` data class is intentionally declared as `String?` (not `Int?`) because the backend Swagger spec defines it as a quoted string (e.g., `"totalElements": "123"`) and the actual server response returns it as a string value, not a JSON number.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: nahy-512
Repo: imflint/Flint-Android PR: 135
File: app/src/main/java/com/flint/domain/repository/AuthRepository.kt:1-22
Timestamp: 2026-01-19T00:48:44.889Z
Learning: Architect the Android app following Google's recommended Android App Architecture for this repo: place the data layer innermost, the domain layer can depend on the data layer, and avoid circular dependencies with other layers. For Kotlin files under app/src/main/java, ensure accompanying architecture aligns with this guidance (e.g., domain interfaces depending on data layer implementations or abstractions, not the other way around).

Learnt from: giovannijunseokim
Repo: imflint/Flint-Android PR: 153
File: app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailViewModel.kt:98-105
Timestamp: 2026-01-21T08:37:42.767Z
Learning: Guideline: For APIs like toggleContentBookmark that do not return a bookmarkCount, perform optimistic updates by updating the isBookmarked state on the client and compute bookmarkCount locally if needed. Do not rely on the server for the count; ensure the server response only conveys the boolean bookmarked state and synchronize this state accordingly. This applies to Kotlin Android ViewModels and related UI state management across files that handle similar bookmark toggle endpoints.

Comment on lines +229 to 237
val profileImageUrl = uploadProfileImageIfNeeded()

val signupRequest = SignupRequestModel(
tempToken = tempToken,
nickname = _uiState.value.nickname,
favoriteContentIds = _contentUiState.value.selectedContents.map { it.id },
agreedTermsIds = _termsUiState.value.agreedTermsIds,
profileImageUrl = profileImageUrl,
)

@coderabbitai coderabbitai Bot Jun 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

이미지 업로드 실패 시 무음 처리로 인한 UX 혼동 가능성.

사용자가 프로필 이미지를 선택했더라도 presigned URL 발급이나 S3 업로드가 실패하면 uploadProfileImageIfNeeded()null을 반환하고, 회원가입은 이미지 없이 그대로 진행됩니다. 프로필 이미지가 선택 항목이라면 의도된 동작일 수 있으나, 사용자가 분명히 이미지를 골랐는데 결과물에 반영되지 않으면 혼란을 줄 수 있습니다. 업로드 실패 시 토스트 등으로 안내하거나 재시도 흐름을 두는 것을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`
around lines 229 - 237, The signup flow silently proceeds when
uploadProfileImageIfNeeded() returns null after a user selected an image; update
OnboardingViewModel so that after calling uploadProfileImageIfNeeded() you check
if the user had a selected image (e.g., _uiState.value.profileImageUri or
equivalent) and the returned profileImageUrl is null, then emit a UI event
(toast/snackbar) or trigger a retry/confirmation flow instead of building
SignupRequestModel and continuing; modify the code around
uploadProfileImageIfNeeded() and the signup creation (where SignupRequestModel
is constructed) to either abort/signup-block on failure or present a retry
dialog/notification to the user so the missing upload isn’t silent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

❌ 반영하지 않았습니다. 프로필 이미지는 선택 항목(optional)이므로 업로드 실패 시 이미지 없이 회원가입을 진행하는 것은 의도된 동작입니다. 업로드 실패가 회원가입 자체를 막아서는 안 된다고 판단했습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@kimjw2003, 이해했습니다. 프로필 이미지가 선택 항목이라면 업로드 실패 시 이미지 없이 회원가입을 진행하는 현재 동작은 완전히 합리적입니다. 설명해 주셔서 감사합니다.

🐇 (´• ω •`)


✏️ Learnings added
Learnt from: kimjw2003
Repo: imflint/Flint-Android PR: 207
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt:231-239
Timestamp: 2026-06-02T12:12:02.548Z
Learning: In the Flint Android app (imflint/Flint-Android), the profile image in the onboarding signup flow (`OnboardingViewModel.signup()` in `app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`) is optional. When `uploadProfileImageIfNeeded()` returns null due to upload failure (presigned URL error or S3 upload error), the signup proceeds without a profile image — this is intentional and by design. Do not flag this as a UX issue.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: nahy-512
Repo: imflint/Flint-Android PR: 135
File: app/src/main/java/com/flint/domain/repository/AuthRepository.kt:1-22
Timestamp: 2026-01-19T00:48:44.889Z
Learning: Architect the Android app following Google's recommended Android App Architecture for this repo: place the data layer innermost, the domain layer can depend on the data layer, and avoid circular dependencies with other layers. For Kotlin files under app/src/main/java, ensure accompanying architecture aligns with this guidance (e.g., domain interfaces depending on data layer implementations or abstractions, not the other way around).

Learnt from: giovannijunseokim
Repo: imflint/Flint-Android PR: 153
File: app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailViewModel.kt:98-105
Timestamp: 2026-01-21T08:37:42.767Z
Learning: Guideline: For APIs like toggleContentBookmark that do not return a bookmarkCount, perform optimistic updates by updating the isBookmarked state on the client and compute bookmarkCount locally if needed. Do not rely on the server for the count; ensure the server response only conveys the boolean bookmarked state and synchronize this state accordingly. This applies to Kotlin Android ViewModels and related UI state management across files that handle similar bookmark toggle endpoints.

Comment thread app/src/main/java/com/flint/presentation/splash/SplashViewModel.kt
- ContentResolver 블로킹 I/O를 Dispatchers.IO로 이동하여 ANR 방지
- checkAutoLogin() DataStore 읽기 실패 시 로그인 화면으로 안전하게 이동
- OnboardingTermsRoute 파라미터명 navigateToOnboardingProfile로 명확화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@ckals413 ckals413 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

굿굿.ᐟ

@kimjw2003 kimjw2003 merged commit 16fa5f0 into develop Jun 2, 2026
2 checks passed
@kimjw2003 kimjw2003 deleted the FLT-8-프로필-이미지-등록-기능 branch June 2, 2026 12:56
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.

2 participants