Skip to content

Feat/#5 mypage screen구현#36

Merged
starshape7 merged 10 commits intodevelopfrom
feat/#5-mypage-screen구현
Jan 21, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/#5-mypage-screen\uad6c\ud604"
Merged

Feat/#5 mypage screen구현#36
starshape7 merged 10 commits intodevelopfrom
feat/#5-mypage-screen구현

Conversation

@starshape7
Copy link
Collaborator

@starshape7 starshape7 commented Jan 20, 2026

Related issue 🛠

  • closed #이슈넘버

Work Description 📝

  • 작업 내용

Screenshot 📸

image image image image image

Uncompleted Tasks 😅

  • 앱 버전 출력

PR Point 📌

트러블 슈팅 💥

Summary by CodeRabbit

  • 새로운 기능

    • 마이페이지 정식 추가 — 프로필, 참여/모집/신청 통계, 선호 카테고리·지역, 앱 버전 표시 및 로그아웃/탈퇴 기능 제공
    • 선호 카테고리·지역 조회 연동 및 마이페이지에서 즉시 확인 가능
  • UI/UX 개선

    • 디자인 시스템에 콘텐츠 정렬 옵션 추가로 버튼·쉐이프 내 정렬 유연성 향상
    • 버튼 시각 상태 확장으로 일관된 표현 제공
  • 기타

    • 네비게이션 및 화면 정리로 마이페이지 경로·호출 흐름 개선

✏️ Tip: You can customize this high-level summary in your review settings.

@starshape7 starshape7 linked an issue Jan 20, 2026 that may be closed by this pull request
12 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Walkthrough

마이페이지 기능을 새로 추가하고 관련 디자인 시스템, 위치 유틸리티, 데이터/도메인/서비스 계층을 확장했습니다. API 엔드포인트·DTO·매퍼·리포지토리 메서드·ViewModel·Compose UI·네비게이션과 일부 디자인 컴포넌트/유틸 함수가 추가되었습니다.

Changes

Cohort / File(s) 요약
위치 유틸리티
core/common/.../location/Location.kt
List<LocationRow>.toCodeMap()mapRegionCodesToFullNames(regionCodes, locations) 유틸 함수 추가
디자인 시스템 — 버튼/모양/임포트
core/designsystem/.../component/button/ImageButton.kt, core/designsystem/.../shapes/Shapes.kt, core/designsystem/.../shapes/ShapePreview.kt
ImageButtonStateXOUTLINEB100State 추가, BlankButton·ShapeBoxalignment: Alignment 파라미터 추가(콘텐츠 정렬 제어), 와일드카드 임포트 명시화
글로벌 모델
core/model/.../Global.kt
StudyTheme.from(String)SocialLoginType.from(String) 동반 객체 함수 추가
데이터 계층 — API/DTO/매퍼
data/user/.../datasource/*.kt, .../dto/response/*.kt, .../mapper/UserMapper.kt, .../service/UserService.kt
getMyPageInfo(), getUserPreferredCategory() 시그니처·구현 추가, MyPage 및 UserPreferredCategory 응답 DTO 추가, DTO→도메인 매핑 확장
리포지토리 구현
data/user/.../repositoryimpl/UserRepositoryImpl.kt
@ApplicationContext 주입 추가, getUserPreferredRegionName(), getMyPageInfo(), getUserPreferredCategory() 구현(자산 기반 위치 매핑 포함)
도메인 모델/리포지토리 인터페이스
domain/user/.../model/*.kt, domain/user/.../repository/UserRepository.kt
MyPageResult, UserPreferredCategoryResult 추가, UserRepository에 3개 suspend 메서드 선언 추가
MyPage UI / ViewModel / 상태 / 내비게이션
feature/mypage/src/main/java/.../main/MyPageScreen.kt, MyPageViewModel.kt, MyScreenState.kt, main/navigation/MypageNavigation.kt, navigation/MypageNavigation.kt, MypageScreen.kt
기존 단순 화면 삭제, 완전한 MyPage Compose 화면(여러 public composable) 추가, ViewModel(StateFlow) 추가, 상태 클래스 추가, 내비게이션 엔트리 MyPage·navigateToMyPage()·mypageGraph(...)로 재구성
앱 메인 네비게이션/탭
feature/main/.../MainNavHost.kt, MainNavTab.kt, MainNavigator.kt, MainScreen.kt
라우트/호출명 MypageMyPage 변경, mypageGraph에 콜백 파라미터 연결
MyPage 모듈 설정
feature/mypage/build.gradle.kts
모듈 의존성(domain.study, domain.user, core.designsystem, core.common) 추가

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant UI as MyPageScreen
    participant VM as MyPageViewModel
    participant Repo as UserRepository
    participant DS as UserDataSource
    participant API as UserService
    participant Assets as Location Assets

    User->>UI: MyPage 진입
    UI->>VM: load()
    VM->>VM: 병렬 로드 시작

    par 회원 정보
        VM->>Repo: getMyPageInfo()
        Repo->>DS: getMyPageInfo()
        DS->>API: GET /api/members/info
        API-->>DS: MyPageResponseDto
        DS-->>Repo: BaseResponse<MyPageResponseDto>
        Repo->>Repo: mapper.toDomain() -> MyPageResult
        Repo-->>VM: Result<MyPageResult>
    and 선호 지역
        VM->>Repo: getUserPreferredRegionName()
        Repo->>Assets: load locations asset
        Repo->>Repo: mapRegionCodesToFullNames(codes, locations)
        Repo-->>VM: Result<List<String?>>
    and 선호 카테고리
        VM->>Repo: getUserPreferredCategory()
        Repo->>DS: getUserPreferredCategory()
        DS->>API: GET /api/members/prefer-categories
        API-->>DS: UserPreferredCategoryResponseDto
        DS-->>Repo: BaseResponse<UserPreferredCategoryResponseDto>
        Repo->>Repo: mapper.toDomain()
        Repo-->>VM: Result<UserPreferredCategoryResult>
    end

    VM-->>UI: uiState 업데이트
    UI->>User: 화면 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

🐶 종훈, 🍒 [FEAT]

Poem

🐰 마이페이지가 활짝 열렸네,
데이터 불러와 화면에 놓았지,
지역 코드 당근처럼 줄 세우고,
버튼과 모양은 반듯이 정렬했네,
코드 배포하니 당근 한 입 축하해! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 설명이 템플릿 구조를 따르고 있으나, Related issue 항목이 placeholder 상태이고 Work Description과 PR Point가 구체적인 내용 없이 비어있습니다. Related issue에 실제 이슈 번호를 입력하고, Work Description에서 구현 내용(location 유틸, 디자인시스템 변경, MyPage 화면 구현 등)을 구체적으로 작성해주세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 MyPage 화면 구현을 명확하게 나타내고 있어 변경사항과 관련성이 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

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

🤖 Fix all issues with AI agents
In `@core/common/src/main/java/com/umcspot/spot/common/location/Location.kt`:
- Around line 85-94: The current mapRegionCodesToFullNames uses mapNotNull which
drops any regionCodes not found in locations (via toCodeMap), causing length
mismatch; update mapRegionCodesToFullNames to iterate regionCodes with map (not
mapNotNull) and return a placeholder for missing entries (e.g., the original
code or an empty string) by using the lookup map[code]?.fullName ?: code (or
another agreed placeholder) so the output list preserves the same length and
order as the input.

In `@core/model/src/main/java/com/umcspot/spot/model/Global.kt`:
- Around line 98-105: The from(value: String) factory in Global.kt currently
lowercases the input with value.lowercase() and defaults unknown values to
KAKAO, causing misclassification; change it to use value.lowercase(Locale.ROOT)
and stop defaulting to KAKAO—either return a nullable SocialLoginType? or add an
UNKNOWN enum constant and return that; then update callers such as UserMapper.kt
(the loginType mapping) to handle the nullable/UNKNOWN case explicitly instead
of silently treating it as KAKAO.
- Around line 63-68: StudyTheme.from currently only matches enum.name exactly;
make it consistent with SocialLoginType.from by normalizing input and matching
both the enum name and its title case-insensitively. Update the StudyTheme.from
function to trim and case-normalize the incoming value and return the first
matching enum where value equals name (ignore case) or equals title (ignore
case) — keep the method on the StudyTheme companion object and follow the same
matching logic used in SocialLoginType.from.

In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`:
- Around line 491-499: In InterestedInfo, the nullable entries in interest can
produce "#null" when joined; update the logic that builds interestText to filter
out nulls (and optionally blank strings) before joining so only non-null values
are prefixed with "#" — for example, use interest.filterNotNull().filter {
it.isNotBlank() }.joinToString(" ") { "#$it" } inside the remember block so
InterestedInfo produces no "#null" tokens.
- Around line 186-231: The InterestedInfo composables are using empty lambdas so
the passed-in handlers aren't wired; replace the onClick = {} for the "관심 분야"
InterestedInfo with the provided onEditInterestClick and the onClick = {} for
the "관심 지역" InterestedInfo with onEditInterestLocationClick (using the same
parameter names from the parent) so the click handlers are invoked; ensure you
reference the InterestedInfo usage lines and preserve the existing data passed
as interest.
- Around line 686-705: The AppVersion Composable currently hardcodes "v1.0.0";
replace that with a dynamic version source. Prefer adding versionName to
BuildConfig via your existing BuildConfigFieldProvider pattern (follow other
modules) so AppVersion reads BuildConfig.VERSION_NAME; alternatively fetch at
runtime using Context.packageManager.getPackageInfo() (or
PackageManager.getPackageInfoCompat) inside AppVersion or its caller; or add
buildConfigField VERSION_NAME in build-logic/convention/BuildConfig.kt and
access it from the module. Update references in AppVersion to use the chosen
dynamic value instead of the literal "v1.0.0".

In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`:
- Around line 28-39: The onFailure branch in loadMemberInfo currently only logs
the error and leaves _uiState.memberInfo as UiState.Loading; update the
onFailure handler to set _uiState.update { it.copy(memberInfo =
UiState.Failure(e.message ?: "회원 정보 불러오기 실패")) } (or a localized message) so
memberInfo moves to Failure; modify the onFailure lambda where
userRepository.getMyPageInfo() is called to perform this _uiState update and
keep the Log.e call.
- Around line 43-71: In loadPrefer(), after calling
userRepository.getUserPreferredRegionName() and getUserPreferredCategory()
inside the viewModelScope.launch blocks, set the corresponding _uiState fields
(preferRegions and preferCategories) to UiState.Failure with a useful message in
each onFailure handler instead of only logging; update the onFailure for
getUserPreferredRegionName() to call _uiState.update { it.copy(preferRegions =
UiState.Failure(e.message ?: "선호 지역 불러오기 실패")) } and similarly update the
onFailure for getUserPreferredCategory() to call _uiState.update {
it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패")) } so
the UI leaves the Loading state on errors.
🧹 Nitpick comments (1)
data/user/src/main/java/com/umcspot/spot/user/mapper/UserMapper.kt (1)

54-57: 알 수 없는 카테고리 코드가 null로 남는 점 개선 권장.

Line 56에서 StudyTheme.from()가 null을 반환하면 리스트에 null이 포함되어 UI에서 빈 항목/예외로 이어질 수 있습니다. 최소한 원문 코드로 fallback 하거나 필터링하는 쪽이 안전합니다.

✅ 제안 수정안
 fun UserPreferredCategoryResponseDto.toDomain(): UserPreferredCategoryResult =
     UserPreferredCategoryResult(
-        categories = categories.map { StudyTheme.from(it)?.title },
+        categories = categories.map { StudyTheme.from(it)?.title ?: it },
         totalCount = totalCount
     )

Comment on lines +85 to +94
fun List<LocationRow>.toCodeMap(): Map<String, LocationRow> =
associateBy { it.code }

fun mapRegionCodesToFullNames(
regionCodes: List<String>,
locations: List<LocationRow>
): List<String> {
val map = locations.toCodeMap()
return regionCodes.mapNotNull { code -> map[code]?.fullName }
}
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

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the Location.kt file to understand the full context
find . -type f -name "Location.kt" | head -5

Repository: SPOTeam/Android-V2

Length of output: 134


🏁 Script executed:

# Search for usages of mapRegionCodesToFullNames to understand how it's used
rg "mapRegionCodesToFullNames" --type kotlin -B 3 -A 3

Repository: SPOTeam/Android-V2

Length of output: 1662


🏁 Script executed:

# Search for LocationRow definition to understand its structure
rg "class LocationRow|data class LocationRow" --type kotlin -B 2 -A 5

Repository: SPOTeam/Android-V2

Length of output: 794


🏁 Script executed:

# Look for any tests related to this function
rg "mapRegionCodesToFullNames|toCodeMap" --type kotlin -B 2 -A 5

Repository: SPOTeam/Android-V2

Length of output: 2273


🏁 Script executed:

# Read the full Location.kt file to see the complete LocationRow definition and any extensions
cat -n ./core/common/src/main/java/com/umcspot/spot/common/location/Location.kt

Repository: SPOTeam/Android-V2

Length of output: 3564


🏁 Script executed:

# Search for fullName in the codebase to understand where it comes from
rg "fullName" --type kotlin -B 2 -A 2

Repository: SPOTeam/Android-V2

Length of output: 4301


🏁 Script executed:

# Find usages of mapRegionCodesToFullNames in detail
rg "mapRegionCodesToFullNames" --type kotlin -B 5 -A 10

Repository: SPOTeam/Android-V2

Length of output: 3296


🏁 Script executed:

# Check UserRepositoryImpl for the specific usage context
cat -n ./data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt | grep -A 20 "mapRegionCodesToFullNames"

Repository: SPOTeam/Android-V2

Length of output: 1199


🏁 Script executed:

# Search for actual usage of mapRegionCodesToFullNames function call
rg "mapRegionCodesToFullNames\s*\(" --type kotlin -B 10 -A 5

Repository: SPOTeam/Android-V2

Length of output: 1645


🏁 Script executed:

# Let's check if mapRegionCodesToFullNames is actually being used anywhere
rg "mapRegionCodesToFullNames" --type kotlin | grep -v "fun mapRegionCodesToFullNames" | grep -v "import"

Repository: SPOTeam/Android-V2

Length of output: 44


🏁 Script executed:

# Check the full UserRepositoryImpl.kt file for any usage of mapRegionCodesToFullNames
wc -l ./data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt

Repository: SPOTeam/Android-V2

Length of output: 149


🏁 Script executed:

# Read the full file
cat -n ./data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt

Repository: SPOTeam/Android-V2

Length of output: 4086


mapNotNull로 인해 존재하지 않는 코드가 자동으로 필터링됩니다.

입력 regionCodes에 포함된 코드가 locations에 없으면 결과에서 제외되어 길이 불일치가 발생할 수 있습니다. UI가 동일 길이를 기대하거나 데이터 무결성이 중요하다면 누락된 코드를 보존하는 방식이 더 안전합니다.

길이 보존 예시
-    return regionCodes.mapNotNull { code -> map[code]?.fullName }
+    return regionCodes.map { code -> map[code]?.fullName ?: code }
🤖 Prompt for AI Agents
In `@core/common/src/main/java/com/umcspot/spot/common/location/Location.kt`
around lines 85 - 94, The current mapRegionCodesToFullNames uses mapNotNull
which drops any regionCodes not found in locations (via toCodeMap), causing
length mismatch; update mapRegionCodesToFullNames to iterate regionCodes with
map (not mapNotNull) and return a placeholder for missing entries (e.g., the
original code or an empty string) by using the lookup map[code]?.fullName ?:
code (or another agreed placeholder) so the output list preserves the same
length and order as the input.

Comment on lines +63 to +68
OTHER("기타");

companion object {
fun from(value: String): StudyTheme? =
values().firstOrNull { it.name == value }
}
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

🧩 Analysis chain

🏁 Script executed:

# Find and read the Global.kt file
fd "Global.kt" --type f

Repository: SPOTeam/Android-V2

Length of output: 119


🏁 Script executed:

# Also get size to determine how to read
wc -l core/model/src/main/java/com/umcspot/spot/model/Global.kt

Repository: SPOTeam/Android-V2

Length of output: 123


🏁 Script executed:

cat -n core/model/src/main/java/com/umcspot/spot/model/Global.kt

Repository: SPOTeam/Android-V2

Length of output: 3038


🏁 Script executed:

# Search for usages of StudyTheme.from
rg "StudyTheme\.from" --type kt -B 2 -A 2

Repository: SPOTeam/Android-V2

Length of output: 88


🏁 Script executed:

# Search for API models and responses that might use StudyTheme
rg "StudyTheme" --type kt -l

Repository: SPOTeam/Android-V2

Length of output: 88


🏁 Script executed:

# Search for StudyTheme.from usages without type filter
rg "StudyTheme\.from" -B 2 -A 2

Repository: SPOTeam/Android-V2

Length of output: 614


🏁 Script executed:

# Search for all StudyTheme references to understand usage patterns
rg "StudyTheme" --include="*.kt" -l

Repository: SPOTeam/Android-V2

Length of output: 503


🏁 Script executed:

# Search for all StudyTheme references
rg "StudyTheme"

Repository: SPOTeam/Android-V2

Length of output: 13899


🏁 Script executed:

# Find UserPreferredCategoryResponseDto to see API contract
rg "UserPreferredCategoryResponseDto" -B 5 -A 10

Repository: SPOTeam/Android-V2

Length of output: 12643


🏁 Script executed:

# Search for test files related to UserPreferredCategoryResponseDto or StudyTheme conversion
find . -name "*Test.kt" -o -name "*test.kt" | head -20

Repository: SPOTeam/Android-V2

Length of output: 188


🏁 Script executed:

# Search for test data or fixtures showing API response format
rg "UserPreferredCategoryResponseDto\|categories.*=" -A 3 -B 1

Repository: SPOTeam/Android-V2

Length of output: 44


🏁 Script executed:

# Search for API response examples or mock data
rg "categories.*LANGUAGE\|categories.*Language\|categories.*language" -B 2 -A 2

Repository: SPOTeam/Android-V2

Length of output: 44


🏁 Script executed:

# Check how other enums handle from() conversions
rg "fun from\(" --include="*.kt" -B 2 -A 5

Repository: SPOTeam/Android-V2

Length of output: 503


🏁 Script executed:

# Search for from() function patterns in all files
rg "fun from\(" -B 2 -A 5

Repository: SPOTeam/Android-V2

Length of output: 1377


🏁 Script executed:

# Let's check the actual API response DTO more carefully
cat -n data/user/src/main/java/com/umcspot/spot/user/dto/response/UserPreferredCategoryResponseDto.kt

Repository: SPOTeam/Android-V2

Length of output: 551


StudyTheme.from() 매핑 방식을 SocialLoginType.from() 패턴과 일관성 있게 정정하세요.

현재 enum name 완전 일치 방식만 사용하는데, 같은 파일의 SocialLoginType은 소문자 정규화와 title 필드 매칭을 적용하고 있습니다. API가 소문자나 한글 title 값을 보내면 null이 되는 위험이 있으므로, 다음과 같이 case-insensitive와 title 필드 매칭을 추가하세요.

🔧 매핑 범위 확장 예시
    companion object {
        fun from(value: String): StudyTheme? =
            values().firstOrNull { it.name == value }
    }
    companion object {
        fun from(value: String): StudyTheme? {
            val normalized = value.trim()
            return values().firstOrNull {
                it.name.equals(normalized, ignoreCase = true) || it.title == normalized
            }
        }
    }
📝 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
OTHER("기타");
companion object {
fun from(value: String): StudyTheme? =
values().firstOrNull { it.name == value }
}
OTHER("기타");
companion object {
fun from(value: String): StudyTheme? {
val normalized = value.trim()
return values().firstOrNull {
it.name.equals(normalized, ignoreCase = true) || it.title == normalized
}
}
}
🤖 Prompt for AI Agents
In `@core/model/src/main/java/com/umcspot/spot/model/Global.kt` around lines 63 -
68, StudyTheme.from currently only matches enum.name exactly; make it consistent
with SocialLoginType.from by normalizing input and matching both the enum name
and its title case-insensitively. Update the StudyTheme.from function to trim
and case-normalize the incoming value and return the first matching enum where
value equals name (ignore case) or equals title (ignore case) — keep the method
on the StudyTheme companion object and follow the same matching logic used in
SocialLoginType.from.

Comment on lines +98 to +105
companion object {
fun from(value: String): SocialLoginType {
val normalized = value.lowercase()

return values().firstOrNull {
it.title == normalized
} ?: KAKAO
}
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

🧩 Analysis chain

🏁 Script executed:

fd "Global.kt" --type f

Repository: SPOTeam/Android-V2

Length of output: 119


🏁 Script executed:

cat -n core/model/src/main/java/com/umcspot/spot/model/Global.kt | head -120

Repository: SPOTeam/Android-V2

Length of output: 3038


🏁 Script executed:

rg "SocialLoginType\.from\(" --type kotlin --type java -B 2 -A 2

Repository: SPOTeam/Android-V2

Length of output: 632


🏁 Script executed:

rg "SocialLoginType" --type kotlin --type java | head -30

Repository: SPOTeam/Android-V2

Length of output: 3859


🏁 Script executed:

rg "\.from\(" core/model/src/main/java/com/umcspot/spot/model/Global.kt -A 10 -B 2

Repository: SPOTeam/Android-V2

Length of output: 44


🏁 Script executed:

cat -n data/user/src/main/java/com/umcspot/spot/user/mapper/UserMapper.kt | head -50

Repository: SPOTeam/Android-V2

Length of output: 2350


알 수 없는 로그인 타입을 KAKAO로 기본 처리하면 오분류됩니다.

신규/오타 값이 들어오면 잘못된 타입으로 저장될 수 있습니다. UserMapper.kt에서 API 응답의 loginType을 매핑할 때 예상치 못한 값이 오면 KAKAO로 처리되어 데이터 불일치가 발생합니다. UNKNOWN 추가 또는 nullable 반환을 고려해 주세요. 또한 lowercase()는 Locale 영향을 받으니 Locale.ROOT 권장입니다.

🔧 안전한 기본값/로케일 처리 예시
+import java.util.Locale
 
 enum class SocialLoginType(
     val title: String
 ) {
+    UNKNOWN("unknown"),
     KAKAO("kakao"),
     NAVER("naver");
 
     companion object {
         fun from(value: String): SocialLoginType {
-            val normalized = value.lowercase()
+            val normalized = value.lowercase(Locale.ROOT)
             return values().firstOrNull {
                 it.title == normalized
-            } ?: KAKAO
+            } ?: UNKNOWN
         }
     }
 }
🤖 Prompt for AI Agents
In `@core/model/src/main/java/com/umcspot/spot/model/Global.kt` around lines 98 -
105, The from(value: String) factory in Global.kt currently lowercases the input
with value.lowercase() and defaults unknown values to KAKAO, causing
misclassification; change it to use value.lowercase(Locale.ROOT) and stop
defaulting to KAKAO—either return a nullable SocialLoginType? or add an UNKNOWN
enum constant and return that; then update callers such as UserMapper.kt (the
loginType mapping) to handle the nullable/UNKNOWN case explicitly instead of
silently treating it as KAKAO.

Comment on lines +108 to +115
mypageGraph(
contentPadding = contentPadding,
onParticipatingClick = { /*navigator.navigateToRecruitingStudy()*/ },
onMyRecruitingClick = { /*navigator.navigateToRecruitingStudy()*/ },
onMyAppliedClick = { /*navigator.navigateToRecruitingStudy()*/ },
onEditInterestClick = { /*navigator.navigateToCheckList*/ },
onEditInterestLocationClick = { }
)
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

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "MainNavHost.kt" | head -20

Repository: SPOTeam/Android-V2

Length of output: 127


🏁 Script executed:

wc -l feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt

Repository: SPOTeam/Android-V2

Length of output: 129


🏁 Script executed:

sed -n '100,120p' feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt

Repository: SPOTeam/Android-V2

Length of output: 942


🏁 Script executed:

sed -n '1,130p' feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt | tail -50

Repository: SPOTeam/Android-V2

Length of output: 2028


🏁 Script executed:

grep -n -i "TODO\|FIXME\|WIP" feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt

Repository: SPOTeam/Android-V2

Length of output: 124


🏁 Script executed:

sed -n '100,120p' feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt

Repository: SPOTeam/Android-V2

Length of output: 942


마이페이지 그래프의 클릭 콜백이 모두 비활성 상태입니다.

mypageGraph의 5개 콜백(onParticipatingClick, onMyRecruitingClick, onMyAppliedClick, onEditInterestClick, onEditInterestLocationClick)이 모두 주석 처리되거나 빈 람다식입니다. 사용자 클릭 시 네비게이션이 작동하지 않으므로 각 콜백에 의도된 동작을 연결해주세요.

Comment on lines +186 to +231
InterestedInfo(
title = "관심 분야",
icon = painterResource(id = R.drawable.search_prefer),
interest = data,
onClick = {}
)

Spacer(modifier = Modifier.height(screenHeightDp(7.dp)))

HorizontalDivider(
color = SpotTheme.colors.G300,
thickness = 1.dp,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = screenWidthDp(4.dp))
)
}
}
}

item {
when(preferRegion) {
is UiState.Loading, is UiState.Empty, is UiState.Failure -> {
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
contentAlignment = Alignment.Center
) {
SpotSpinner()
}

Spacer(modifier = Modifier.height(screenHeightDp(20.dp)))
}

is UiState.Success -> {
val data = preferRegion.data

Spacer(modifier = Modifier.height(screenHeightDp(7.dp)))

InterestedInfo(
title = "관심 지역",
icon = painterResource(id = R.drawable.location_outline),
interest = data,
onClick = {}
)
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

관심 편집 클릭 핸들러가 연결되지 않음.

상위에서 전달받은 onEditInterestClick/onEditInterestLocationClick이 실제 버튼에 연결되지 않아 클릭이 동작하지 않습니다.

🛠️ 제안 수정
-                        onClick = {}
+                        onClick = onEditInterestClick
...
-                        onClick = {}
+                        onClick = onEditInterestLocationClick
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`
around lines 186 - 231, The InterestedInfo composables are using empty lambdas
so the passed-in handlers aren't wired; replace the onClick = {} for the "관심 분야"
InterestedInfo with the provided onEditInterestClick and the onClick = {} for
the "관심 지역" InterestedInfo with onEditInterestLocationClick (using the same
parameter names from the parent) so the click handlers are invoked; ensure you
reference the InterestedInfo usage lines and preserve the existing data passed
as interest.

Comment on lines +491 to +499
fun InterestedInfo(
title : String,
icon : Painter,
interest : List<String?>,
onClick : () -> Unit
) {
val interestText = remember(interest) {
interest.joinToString(separator = " ") { "#$it" }
}
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

관심 목록에 null이 있으면 null 문자열이 노출됩니다.

nullable 리스트를 그대로 join 하면 #null이 표시될 수 있습니다.

🛠️ 제안 수정
-    val interestText = remember(interest) {
-        interest.joinToString(separator = " ") { "#$it" }
-    }
+    val interestText = remember(interest) {
+        interest
+            .filterNotNull()
+            .filter { it.isNotBlank() }
+            .joinToString(separator = " ") { "#$it" }
+    }
📝 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
fun InterestedInfo(
title : String,
icon : Painter,
interest : List<String?>,
onClick : () -> Unit
) {
val interestText = remember(interest) {
interest.joinToString(separator = " ") { "#$it" }
}
fun InterestedInfo(
title : String,
icon : Painter,
interest : List<String?>,
onClick : () -> Unit
) {
val interestText = remember(interest) {
interest
.filterNotNull()
.filter { it.isNotBlank() }
.joinToString(separator = " ") { "#$it" }
}
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`
around lines 491 - 499, In InterestedInfo, the nullable entries in interest can
produce "#null" when joined; update the logic that builds interestText to filter
out nulls (and optionally blank strings) before joining so only non-null values
are prefixed with "#" — for example, use interest.filterNotNull().filter {
it.isNotBlank() }.joinToString(" ") { "#$it" } inside the remember block so
InterestedInfo produces no "#null" tokens.

Comment on lines +28 to +39
fun loadMemberInfo() {
_uiState.update { it.copy(memberInfo = UiState.Loading) }

viewModelScope.launch {
userRepository.getMyPageInfo()
.onSuccess { info ->
_uiState.update { it.copy(memberInfo = UiState.Success(info)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadMyPageInfo error", e)
// _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) }
}
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

실패 시 memberInfo가 Loading에 고착됩니다.

onFailure에서 Failure로 갱신하지 않아 UI가 계속 로딩 상태로 남을 수 있습니다.

✅ 수정 제안
                 .onFailure { e ->
                     Log.e("HomeViewModel", "loadMyPageInfo error", e)
-//                    _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) }
+                    _uiState.update {
+                        it.copy(memberInfo = UiState.Failure(e.message ?: "내 정보 불러오기 실패"))
+                    }
                 }
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`
around lines 28 - 39, The onFailure branch in loadMemberInfo currently only logs
the error and leaves _uiState.memberInfo as UiState.Loading; update the
onFailure handler to set _uiState.update { it.copy(memberInfo =
UiState.Failure(e.message ?: "회원 정보 불러오기 실패")) } (or a localized message) so
memberInfo moves to Failure; modify the onFailure lambda where
userRepository.getMyPageInfo() is called to perform this _uiState update and
keep the Log.e call.

Comment on lines +43 to +71
fun loadPrefer() {
_uiState.update {
it.copy(
preferRegions = UiState.Loading,
preferCategories = UiState.Loading
)
}

viewModelScope.launch {
userRepository.getUserPreferredRegionName()
.onSuccess { info ->
_uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferredRegion error", e)
// _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) }
}
}

viewModelScope.launch {
userRepository.getUserPreferredCategory()
.onSuccess { info ->
_uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferCategories error", e)
// _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) }
}
}
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

선호 지역/카테고리도 실패 시 Loading에 고착됩니다.

각 onFailure에서 Failure 상태로 전환하지 않으면 사용자에게 무한 로딩으로 보입니다.

✅ 수정 제안
             userRepository.getUserPreferredRegionName()
                 .onSuccess { info ->
                     _uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) }
                 }
                 .onFailure { e ->
                     Log.e("HomeViewModel", "loadPreferredRegion error", e)
-//                    _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) }
+                    _uiState.update {
+                        it.copy(preferRegions = UiState.Failure(e.message ?: "선호 지역 불러오기 실패"))
+                    }
                 }
@@
             userRepository.getUserPreferredCategory()
                 .onSuccess { info ->
                     _uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) }
                 }
                 .onFailure { e ->
                     Log.e("HomeViewModel", "loadPreferCategories error", e)
-//                    _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) }
+                    _uiState.update {
+                        it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패"))
+                    }
                 }
📝 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
fun loadPrefer() {
_uiState.update {
it.copy(
preferRegions = UiState.Loading,
preferCategories = UiState.Loading
)
}
viewModelScope.launch {
userRepository.getUserPreferredRegionName()
.onSuccess { info ->
_uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferredRegion error", e)
// _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) }
}
}
viewModelScope.launch {
userRepository.getUserPreferredCategory()
.onSuccess { info ->
_uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferCategories error", e)
// _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) }
}
}
fun loadPrefer() {
_uiState.update {
it.copy(
preferRegions = UiState.Loading,
preferCategories = UiState.Loading
)
}
viewModelScope.launch {
userRepository.getUserPreferredRegionName()
.onSuccess { info ->
_uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferredRegion error", e)
_uiState.update {
it.copy(preferRegions = UiState.Failure(e.message ?: "선호 지역 불러오기 실패"))
}
}
}
viewModelScope.launch {
userRepository.getUserPreferredCategory()
.onSuccess { info ->
_uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferCategories error", e)
_uiState.update {
it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패"))
}
}
}
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`
around lines 43 - 71, In loadPrefer(), after calling
userRepository.getUserPreferredRegionName() and getUserPreferredCategory()
inside the viewModelScope.launch blocks, set the corresponding _uiState fields
(preferRegions and preferCategories) to UiState.Failure with a useful message in
each onFailure handler instead of only logging; update the onFailure for
getUserPreferredRegionName() to call _uiState.update { it.copy(preferRegions =
UiState.Failure(e.message ?: "선호 지역 불러오기 실패")) } and similarly update the
onFailure for getUserPreferredCategory() to call _uiState.update {
it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패")) } so
the UI leaves the Loading state on errors.

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: 1

🤖 Fix all issues with AI agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`:
- Around line 101-104: The catch block for "catch (e: Exception)" swallows the
exception making debugging difficult; update the handler to log the exception
(including stacktrace and message) using the project's logger (e.g., Timber/Log)
and include the error message in the UiState.Failure payload so the UI/state
contains the failure reason—specifically modify the code around _uiState.update
and UiState.Failure to pass e.message (or e.localizedMessage) and call the
logger with the exception.
♻️ Duplicate comments (4)
feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt (2)

32-43: memberInfo 실패 시 Loading에 고착됩니다.

onFailure에서 Failure로 전환하지 않으면 로딩 스피너가 계속 노출될 수 있습니다.

✅ 수정 제안
                 .onFailure { e ->
                     Log.e("HomeViewModel", "loadMyPageInfo error", e)
-//                    _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) }
+                    _uiState.update {
+                        it.copy(memberInfo = UiState.Failure(e.message ?: "내 정보 불러오기 실패"))
+                    }
                 }

55-74: 선호 정보도 실패 시 Failure 전환이 필요합니다.

현재는 로그만 남아 UI가 로딩에 머물 수 있습니다.

✅ 수정 제안
             userRepository.getUserPreferredRegionName()
                 .onSuccess { info ->
                     _uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) }
                 }
                 .onFailure { e ->
                     Log.e("HomeViewModel", "loadPreferredRegion error", e)
-//                    _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) }
+                    _uiState.update {
+                        it.copy(preferRegions = UiState.Failure(e.message ?: "선호 지역 불러오기 실패"))
+                    }
                 }
@@
             userRepository.getUserPreferredCategory()
                 .onSuccess { info ->
                     _uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) }
                 }
                 .onFailure { e ->
                     Log.e("HomeViewModel", "loadPreferCategories error", e)
-//                    _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) }
+                    _uiState.update {
+                        it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패"))
+                    }
                 }
feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt (2)

189-234: 관심 편집 클릭이 빈 람다로 막혀 있습니다.

전달받은 핸들러가 연결되지 않아 클릭이 동작하지 않습니다.

✅ 수정 제안
                     InterestedInfo(
                         title = "관심 분야",
                         icon = painterResource(id = R.drawable.search_prefer),
                         interest = data,
-                        onClick = {}
+                        onClick = onEditInterestClick
                     )
@@
                     InterestedInfo(
                         title = "관심 지역",
                         icon = painterResource(id = R.drawable.location_outline),
                         interest = data,
-                        onClick = {}
+                        onClick = onEditInterestLocationClick
                     )

519-520: 관심 목록에 null이 있으면 ‘#null’이 노출됩니다.

UI 노이즈 방지를 위해 null/blank 필터링을 권장합니다.

✅ 수정 제안
     val interestText = remember(interest) {
-        interest.joinToString(separator = " ") { "#$it" }
+        interest
+            .filterNotNull()
+            .filter { it.isNotBlank() }
+            .joinToString(separator = " ") { "#$it" }
     }

Comment on lines 101 to 104
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

예외 로그 없이 삼켜 디버깅이 어렵습니다.

실패 원인을 추적할 수 있도록 로그와 메시지 포함을 권장합니다.

✅ 수정 제안
         } catch (e: Exception) {
+            Log.e("MyPageViewModel", "loadAppVersion error", e)
             _uiState.update {
-                it.copy(appVersion = UiState.Failure("앱 버전 조회 실패"))
+                it.copy(appVersion = UiState.Failure(e.message ?: "앱 버전 조회 실패"))
             }
         }
🧰 Tools
🪛 detekt (1.23.8)

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

(detekt.exceptions.SwallowedException)

🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`
around lines 101 - 104, The catch block for "catch (e: Exception)" swallows the
exception making debugging difficult; update the handler to log the exception
(including stacktrace and message) using the project's logger (e.g., Timber/Log)
and include the error message in the UiState.Failure payload so the UI/state
contains the failure reason—specifically modify the code around _uiState.update
and UiState.Failure to pass e.message (or e.localizedMessage) and call the
logger with the exception.

@starshape7 starshape7 merged commit ee60108 into develop Jan 21, 2026
1 check passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 2, 2026
1 task
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.

[FEAT/#8]MyPageScreen 구현

1 participant

Comments