Skip to content

Conversation

@ImHyungsuk
Copy link
Contributor

@ImHyungsuk ImHyungsuk commented Oct 6, 2025

📌 PR 요약

🌱 작업한 내용

  • 프로필 수정 ui
  • 닉네임 수정 ui
  • 페르소나 선택 ui
  • api 연결 및 상태 관리

🌱 PR 포인트

📸 스크린샷

스크린샷
파일첨부바람

📮 관련 이슈

Summary by CodeRabbit

  • New Features
    • 마이페이지에 사용자 정보와 저장 장소 표시(빈 상태 UI 포함)
    • 로그아웃 확인 다이얼로그 추가
    • 프로필 수정 화면 추가: 닉네임 중복 검증, 페르소나 드롭다운 선택, 완료 버튼 상태 연동
    • 저장 장소 가로 카드 리스트 컴포넌트 도입
  • Refactor
    • 마이페이지 상태·인텐트 구조 개편 및 사이드이펙트 정비, 화면 간 네비게이션 연결 개선
  • Style
    • 경미한 레이아웃/포맷팅 수정 및 클릭 효과 조정
    • 다국어 문자열 리소스 추가 및 기존 하드코딩 문구 치환

@ImHyungsuk ImHyungsuk requested a review from a team October 6, 2025 08:49
@ImHyungsuk ImHyungsuk self-assigned this Oct 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Walkthrough

마이페이지/프로필 수정 기능을 위한 모델·DTO 추가와 데이터 흐름(서비스→리모트DS→레포지토리→뷰모델→UI) 구현 및 연결. Persona enum과 관련 엔티티/DTO 도입. 마이페이지 화면/상태/뷰모델 구축, 프로필 수정 화면/상태/뷰모델 신설. 문자열 리소스 추가. 일부 포맷팅 정리.

Changes

Cohort / File(s) Summary
DesignSystem formatting
core/designsystem/.../SolplyBasicDropDown.kt
파라미터 리스트 트레일링 콤마 제거 등 포맷 수정(동작 불변).
Core Model: Persona
core/model/.../Persona.kt
직렬화 가능한 공개 enum Persona 추가(REST, EXPLORER, MOODING, NATURAL, ANYTHING).
Domain Models
domain/mypage/.../UserInfo.kt, .../PlaceInfoEntity.kt, .../PersonaEntity.kt
마이페이지 도메인 엔티티/모델 신설: UserInfo, SelectedTownInfo, PlaceInfoEntity, PersonaEntity.
Domain Repository API
domain/mypage/.../repository/MypageRepository.kt
getUserInfo, getPlaceList, getPersonaList, checkNicknameDuplicate 등 Result 기반 공개 인터페이스 정의.
Data DTOs
data/mypage/.../dto/response/GetUserInfoResponseDto.kt, .../PlaceListResponseDto.kt, .../GetPersonaListResponseDto.kt, .../NicknameDuplicateResponseDto.kt
사용자/장소/페르소나/닉네임중복 응답 DTO 추가 및 도메인 매핑 확장 함수(toDomain) 도입.
Data Remote DS API
data/mypage/.../datasource/MypageRemoteDataSource.kt
리모트 데이터소스 인터페이스에 4개 suspend 함수 정의.
Data Repository Impl
data/mypage/.../repository/MypageRepositoryImpl.kt
리모트 호출-DTO→도메인 매핑 구현. 장소/페르소나 리스트 변환 및 닉네임 중복 결과 반환.
Remote Service & DS Impl
remote/mypage/.../service/MypageService.kt, .../datasource/MypageRemoteDataSourceImpl.kt
Retrofit API 메서드 추가 및 DS 구현 연결.
Feature MyPage: Contract/ViewModel/Screen
feature/mypage/.../MypageContract.kt, .../MypageViewModel.kt, .../MypageScreen.kt
상태를 UserInfo+placeList로 재구성, 다양한 Intent/SideEffect 추가, 초기 로딩·로그아웃 다이얼로그·저장 장소 표시 로직 구현.
Feature MyPage: Components
feature/mypage/.../component/EmptyPlaceContainer.kt, .../component/MypageSettingItem.kt, .../component/SavedPlaceListContainer.kt, .../component/SolplyPersonaDropDown.kt
패딩 조정, customClickable 적용, 저장 장소 리스트 UI 신설, 드롭다운 아이템 타입을 PersonaEntity로 변경.
Feature Profile Edit: Contract/VM/Screen/Nav
feature/mypage/.../profile/ProfileEditContract.kt, .../profile/ProfileEditViewModel.kt, .../profile/ProfileEditScreen.kt, .../profile/navigation/ProfileNavigation.kt
프로필 수정 상태/인텐트/사이드이펙트 정의, ViewModel 신설(초기화·닉네임 검증·드롭다운 처리), 화면 API 확장 및 라우트 교체, 네비게이션 연결.
Feature MyPage: Navigation
feature/mypage/.../navigation/MypageNavigation.kt
파라미터 리스트 트레일링 콤마 제거(동작 불변).
Feature Main
feature/main/.../MainScreen.kt
불필요한 Profile import 제거.
Resources
feature/mypage/src/main/res/values/strings.xml
마이페이지/프로필 수정/로그아웃 관련 문자열 리소스 다수 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant UI as MypageScreen
  participant VM as MypageViewModel
  participant Repo as MypageRepository
  participant DS as MypageRemoteDataSource
  participant Svc as MypageService

  U->>UI: 화면 진입
  UI->>VM: Intent.Init
  VM->>Repo: getUserInfo()
  Repo->>DS: getUserInfo()
  DS->>Svc: GET /api/users
  Svc-->>DS: BaseResponse<GetUserInfoResponseDto>
  DS-->>Repo: GetUserInfoResponseDto
  Repo-->>VM: Result<UserInfo>
  VM->>Repo: getPlaceList(townId)
  Repo->>DS: getPlaceList(townId, isBookmarkSearch=true)
  DS->>Svc: GET /api/places
  Svc-->>DS: BaseResponse<PlaceListResponseDto>
  DS-->>Repo: PlaceListResponseDto
  Repo-->>VM: Result<List<PlaceInfoEntity>>
  VM-->>UI: state 업데이트(userInfo, placeList)
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant UI as ProfileEditScreen
  participant VM as ProfileEditViewModel
  participant Repo as MypageRepository
  participant DS as MypageRemoteDataSource
  participant Svc as MypageService

  U->>UI: 진입
  UI->>VM: Intent.Init
  VM->>Repo: getUserInfo(), getPersonaList()
  Repo->>DS: getUserInfo() / getPersonaList()
  DS->>Svc: GET /api/users / GET /api/users/persona
  Svc-->>DS: User / Persona 리스트
  DS-->>Repo: DTO들
  Repo-->>VM: Result<UserInfo>, Result<List<PersonaEntity>>
  VM-->>UI: 초기 상태 설정

  U->>UI: 닉네임 입력
  UI->>VM: ChangeInputNickname(nickname)
  VM->>Repo: checkNicknameDuplicate(nickname) (디바운스 후)
  Repo->>DS: checkNicknameDuplicate(nickname)
  DS->>Svc: GET /api/users/check-nickname
  Svc-->>DS: Dup 여부
  DS-->>Repo: NicknameDuplicateResponseDto
  Repo-->>VM: Result<Boolean>
  VM-->>UI: isNicknameDuplicate 갱신
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

형석 🐧🍀

Suggested reviewers

  • leeseokchan00
  • 88guri

Poem

튀는 수풀 아래 코드 톡톡, 깡충!
페르소나 다섯 빛, 목록은 둥둥—
서비스 타고, 리모트 지나, 레포로 쏙!
뷰모델 품에 안겨 화면으로 쭉—
닉네임 검증 OK? 토끼는 끄덕 툭. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning PR 제목이 “프로필 수정 구현 완료”라고 하나 실제 변경사항에는 마이페이지 상태관리와 UI 개선, API 연결 등 프로필 수정 외에도 광범위한 기능 구현이 포함되어 있어 주요 변경사항을 충분히 요약하지 못하고 있습니다. 제목을 “마이페이지 및 프로필 상태관리 및 UI 구현 완료” 등으로 수정하여 PR 범위에 맞게 주요 변경사항을 명확히 반영해 주세요.
Out of Scope Changes Check ⚠️ Warning PR에 디자인 시스템의 SolplyBasicDropDown.kt에서 단순 콤마 제거와 MainScreen.kt의 불필요한 import 삭제 같은 프로필 수정 및 마이페이지 상태관리와 직접 연관 없는 형식 변경이 포함되어 이슈 범위를 벗어나는 변경이 확인됩니다. 이러한 형식 위주의 변경은 별도 PR로 분리하거나 제거하여 주요 기능 구현과 구분되도록 해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues Check ✅ Passed PR이 연결된 이슈 #147에서 요구된 마이페이지와 프로필 수정의 상태관리 및 UI 구현을 모두 반영하여 MypageContract, MypageViewModel, ProfileEditContract, ProfileEditViewModel 등 관련 코딩 요구사항을 충족했습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#147-mypage-profile-state

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

Caution

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

⚠️ Outside diff range comments (1)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/dropdown/SolplyBasicDropDown.kt (1)

73-88: 접근성: 클릭 가능한 아이콘에 라벨이 없습니다

빈 contentDescription("")은 스크린리더에서 조작 의도를 전달하지 못합니다. 접힘/펼침 상태에 따라 라벨을 제공해주세요. 문자열 리소스를 추가해 아래처럼 적용하는 것을 권장합니다.

 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
 ...
             Icon(
                 painter = painterResource(R.drawable.ic_arrow_down),
-                contentDescription = "",
+                contentDescription = if (isDropped)
+                    stringResource(R.string.a11y_dropdown_collapse)
+                else
+                    stringResource(R.string.a11y_dropdown_expand),
                 modifier = Modifier
  • 문자열 리소스(예: a11y_dropdown_expand, a11y_dropdown_collapse)를 추가해야 합니다.
  • 추가로, 탭 영역 확대를 원하면 Row 전체에 클릭을 위임하는 것도 고려해주세요.
🧹 Nitpick comments (17)
data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/NicknameDuplicateResponseDto.kt (1)

6-10: 동일 DTO 중복 정의 정리 제안

Onboarding 모듈에도 동일 구조의 NicknameDuplicateResponseDto가 있어 유지보수 중복이 발생합니다. 공용 모듈(예: data-common/core-network)로 이동하거나, 레포지토리에서 도메인 Boolean으로 즉시 매핑해 UI/도메인에선 DTO 노출을 피하는 방식을 권장합니다.

feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt (1)

20-35: LazyRow 성능/안정성 개선 및 이미지 URL 타입 확인

  • 리스트에 stable key를 부여해 재조합 성능과 상태 보존을 개선하세요.
  • SolplyPlaceCard.imgRes는 String입니다. PlaceInfoEntity.imageUrls가 List라면 첫 이미지를 선택하도록 보완이 필요합니다.

예시 수정안:

-    LazyRow(
-        modifier = modifier
-            .fillMaxWidth()
-    ) {
-        items(savedPlaceList) {
-            SolplyPlaceCard(
-                name = it.placeName,
-                placeType = it.placeType,
-                imgRes = it.imageUrls,
+    LazyRow(
+        modifier = modifier.fillMaxWidth(),
+        // 필요 시 아이템 간 간격을 부여
+        // horizontalArrangement = Arrangement.spacedBy(12.dp)
+    ) {
+        items(
+            items = savedPlaceList,
+            key = { it.placeId } // 실제 고유 키 필드명으로 교체
+        ) { place ->
+            SolplyPlaceCard(
+                name = place.placeName,
+                placeType = place.placeType,
+                // imageUrls 타입이 List<String>이면 아래처럼 첫 이미지를 사용
+                // 그렇지 않으면 기존 필드를 유지
+                imgRes = place.imageUrls.firstOrNull().orEmpty(),
                 selected = it.isSelected,
                 saved = it.isSaved,
                 touchable = false,
                 modifier = Modifier.size(height = 165.dp, width = 136.dp)
             )
         }
     }
  • imageUrls가 이미 String이면 위 firstOrNull().orEmpty() 대신 기존 전달을 유지하세요. 확인 부탁드립니다.
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SolplyPersonaDropDown.kt (1)

92-92: 프리뷰에 샘플 데이터 추가를 고려하세요.

프리뷰에서 빈 리스트를 사용하면 컴포넌트의 실제 동작을 확인하기 어렵습니다.

다음과 같이 샘플 데이터를 추가하는 것을 권장합니다:

-                dropDownContents = persistentListOf(),
+                dropDownContents = persistentListOf(
+                    PersonaEntity(Persona.REST, "조용한 공간에 오래 머물고 싶어요"),
+                    PersonaEntity(Persona.EXPLORER, "이곳저곳 둘러보고 싶어요")
+                ),

Also applies to: 105-105

feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt (1)

32-32: TODO 주석을 추적하세요.

로그아웃 API 구현이 필요합니다.

로그아웃 API 통합을 위한 이슈를 생성하거나 구현 코드를 생성하길 원하시나요?

feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditViewModel.kt (3)

3-3: 프로덕션 코드에서 Log 문을 제거하세요.

android.util.Log는 디버깅용으로만 사용해야 합니다. 프로덕션 코드에서는 적절한 로깅 프레임워크나 에러 처리를 사용하는 것이 좋습니다.

Log 문을 제거하거나 적절한 에러 처리로 교체하세요:

-                .onFailure {
-                    Log.i("getUser fail", "")
-                }
+                .onFailure { error ->
+                    // 적절한 에러 상태 업데이트
+                    reduce { copy(/* error state */) }
+                }
-                    Log.d("persona: ", personaList.toString())
                     reduce { copy(personaList = personaList) }

Also applies to: 103-103, 107-107


67-67: TODO 주석을 추적하세요.

프로필 수정 API와 로그아웃 API 구현이 필요합니다.

이러한 API 통합을 추적하기 위한 이슈를 생성하길 원하시나요?

Also applies to: 76-76


91-127: init 메서드의 구조를 개선하세요.

init 메서드가 여러 비동기 작업과 Flow 수집을 하나의 viewModelScope에서 처리하고 있어 복잡합니다. nicknameFlow 수집은 별도의 코루틴으로 분리하는 것이 좋습니다.

다음과 같이 리팩토링을 고려하세요:

private fun init() {
    viewModelScope.launch {
        loadUserInfoAndPersonas()
    }
    observeNicknameChanges()
}

private fun loadUserInfoAndPersonas() {
    viewModelScope.launch {
        // getUserInfo와 getPersonaList 로직
    }
}

private fun observeNicknameChanges() {
    viewModelScope.launch {
        nicknameFlow
            .debounce(500)
            .filter { it.isNotBlank() }
            .collect { /* ... */ }
    }
}
domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PlaceInfoEntity.kt (1)

9-9: 필드명과 타입의 불일치를 수정하세요.

imageUrls는 복수형 이름이지만 타입은 String(단수)입니다. 단일 URL이라면 imageUrl로, 여러 URL을 담는다면 List<String>로 변경하는 것이 명확합니다.

</review_comment_end -->

remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt (1)

18-18: 쿼리 파라미터 이름 불일치를 수정하세요.

@Query("isBookmarkSearch")로 선언했지만 실제 파라미터 이름은 isBookmarkedSearch입니다. Retrofit은 @Query 어노테이션의 값을 사용하므로 동작에는 문제가 없지만, 가독성을 위해 파라미터 이름을 isBookmarkSearch로 통일하는 것이 좋습니다.

 @GET("api/places")
 suspend fun getPlaceList(
     @Query("townId") townId: Long,
-    @Query("isBookmarkSearch") isBookmarkedSearch: Boolean = true
+    @Query("isBookmarkSearch") isBookmarkSearch: Boolean = true
 ): BaseResponse<PlaceListResponseDto>

</review_comment_end -->

remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt (1)

13-17: 프로덕션 코드에서 디버그 로그를 제거하세요.

Line 15의 Log.d 디버그 로그가 남아있습니다. 개발 단계에서는 유용하지만, 프로덕션 배포 전에 제거하거나 Timber 등의 로깅 라이브러리를 사용하여 릴리스 빌드에서 자동으로 제거되도록 하는 것이 좋습니다.

 override suspend fun getUserInfo(): GetUserInfoResponseDto {
-    val user = mypageService.getUserInfo().data
-    Log.d("getUser ", user.nickname)
-    return user
+    return mypageService.getUserInfo().data
 }

</review_comment_end -->

feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditContract.kt (1)

11-27: 기본값으로 하드코딩된 데이터를 제거하세요.

Lines 12-18의 UserInfo 기본값에 "숭이숭이숭이", townId 0 등의 하드코딩된 데이터가 포함되어 있습니다. 이는 Compose Preview나 테스트용으로 보이지만, 실제 상태 초기화 시 의도치 않게 사용될 수 있습니다. 기본값을 더 명확한 빈 상태나 null로 설정하고, Preview용 데이터는 별도의 함수로 분리하는 것이 좋습니다.

또한 Line 23의 selectedPersonaIndex = -1은 리스트 접근 시 인덱스 오류를 일으킬 수 있으니, 사용 시 범위 체크가 필요합니다.

 data class ProfileEditState(
-    val userInfo: UserInfo = UserInfo(
-        userId = 1,
-        nickname = "숭이숭이숭이",
-        selectedTown = SelectedTownInfo(0, "망원동"),
-        persona = Persona.REST,
-        profileImageUrl = ""
-    ),
+    val userInfo: UserInfo? = null,
     val inputNickname: String = "",
     val isNicknameDuplicate: Boolean = false,
     val isEditSuccess: Boolean = false,
     val personaList: List<PersonaEntity> = emptyList(),
     val selectedPersonaIndex: Int = -1,
     val isDropped: Boolean = false,
     val completeButtonEnabled: Boolean = false,
     val dialogState: Boolean = false
 ) : UiState

</review_comment_end -->

feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt (2)

112-119: i18n 및 접근성 개선 제안: 하드코딩 문자열과 contentDescription

  • 문자열: 아래 항목을 stringResource로 교체 권장
    • back 아이콘(라인 114), 기본 프로필 이미지(라인 123), 섹션/설정 항목들(라인 176, 221, 223, 227, 229, 238)
  • 접근성: 장식용 아이콘(contentDescription 불필요)은 null 지정 권장
    • 다음 화살표 아이콘(라인 159)은 현재 빈 문자열("") → null 권장
  • 예시 diff:
- contentDescription = "back"
+ contentDescription = stringResource(R.string.common_back)

- contentDescription = "기본 프로필"
+ contentDescription = stringResource(R.string.profile_img)

- contentDescription = ""
+ contentDescription = null
  • 참고: EmptyPlaceContainer의 "등록한 장소가 없어요."도 stringResource로 전환 고려(별도 파일).

원하시면 리소스 키 제안과 일괄 변경용 패치 드리겠습니다.

Also applies to: 121-129, 151-161, 176-181, 221-231, 238-241


237-241: 회원탈퇴 onClick 미구현

현재 TODO입니다. MypageIntent.WithdrawButtonClick을 화면/Route에 배선해 동작 연결 권장.

이 의도라면, MypageScreen 파라미터에 onWithdrawClick을 추가하고 Route에서 viewModel.sendIntent(...)로 연결할까요?

data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt (1)

3-3: 디버그 로그 제거 권장

불필요한 Log.d가 포함돼 있습니다. 릴리즈 노이즈/성능을 위해 제거하세요.

-import android.util.Log
@@
-        Log.d("persona: ", "repo impl start")

Also applies to: 37-37

feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageContract.kt (1)

12-20: 초기 더미 데이터 노출 최소화

앱 시작 시 실제 데이터 로드 전 더미 값(닉네임/동네)이 잠깐 보일 수 있습니다. 빈 값/플레이스홀더로 초기화 권장.

-    val userInfo: UserInfo = UserInfo(
-        userId = 1,
-        nickname = "숭이숭이숭이",
-        selectedTown = SelectedTownInfo(0, "망원동"),
-        persona = Persona.REST,
-        profileImageUrl = ""
-    ),
+    val userInfo: UserInfo = UserInfo(
+        userId = 0,
+        nickname = "",
+        selectedTown = SelectedTownInfo(0, ""),
+        persona = Persona.REST,
+        profileImageUrl = ""
+    ),
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (2)

198-198: 텍스트 리소스로 전환

하드코딩 "완료" → stringResource로 치환 권장.

-            text = "완료",
+            text = stringResource(R.string.profile_complete),

197-208: 비활성 시 접근성 처리

onClick 가드만으로는 보조기기에서 여전히 버튼으로 인식됩니다. 비활성 시 semantics.disabled 부여 권장.

-        SolplyBasicButton(
+        SolplyBasicButton(
             text = stringResource(R.string.profile_complete),
             selected = isEditSuccess,
-            onClick = {
+            onClick = {
                 if (isEditSuccess) {
                     onCompleteButtonClick()
                 }
             },
             enabledBackgroundColor = SolplyTheme.colors.gray900,
             disabledBackgroundColor = SolplyTheme.colors.gray300,
-            modifier = Modifier.padding(vertical = 24.dp, horizontal = 16.dp)
+            modifier = Modifier
+                .padding(vertical = 24.dp, horizontal = 16.dp)
+                .semantics {
+                    if (!isEditSuccess) disabled()
+                }
         )

추가 필요 import:

import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d25245 and 160b35d.

📒 Files selected for processing (28)
  • core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/dropdown/SolplyBasicDropDown.kt (2 hunks)
  • core/model/src/main/java/com/teamsolply/solply/model/Persona.kt (1 hunks)
  • data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSource.kt (1 hunks)
  • data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetPersonaListResponseDto.kt (1 hunks)
  • data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetUserInfoResponseDto.kt (1 hunks)
  • data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/NicknameDuplicateResponseDto.kt (1 hunks)
  • data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/PlaceListResponseDto.kt (1 hunks)
  • data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt (1 hunks)
  • domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PersonaEntity.kt (1 hunks)
  • domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PlaceInfoEntity.kt (1 hunks)
  • domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/UserInfo.kt (1 hunks)
  • domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt (1 hunks)
  • feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt (0 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageContract.kt (1 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt (7 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt (2 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/EmptyPlaceContainer.kt (0 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/MypageSettingItem.kt (2 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt (1 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SolplyPersonaDropDown.kt (5 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/navigation/MypageNavigation.kt (1 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditContract.kt (1 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (5 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditViewModel.kt (1 hunks)
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/navigation/ProfileNavigation.kt (3 hunks)
  • feature/mypage/src/main/res/values/strings.xml (1 hunks)
  • remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt (1 hunks)
  • remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt (1 hunks)
💤 Files with no reviewable changes (2)
  • feature/main/src/main/java/com/teamsolply/solply/main/MainScreen.kt
  • feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/EmptyPlaceContainer.kt
🧰 Additional context used
🧬 Code graph analysis (6)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SolplyPersonaDropDown.kt (1)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/dropdown/SolplyBasicDropDown.kt (1)
  • SolplyBasicDropDown (31-103)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditViewModel.kt (1)
core/ui/src/main/java/com/teamsolply/solply/ui/base/BaseViewModel.kt (2)
  • reduce (44-46)
  • postSideEffect (48-52)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt (5)
core/ui/src/main/java/com/teamsolply/solply/ui/base/BaseViewModel.kt (2)
  • reduce (44-46)
  • postSideEffect (48-52)
data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt (1)
  • getPlaceList (20-32)
remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt (1)
  • getPlaceList (15-19)
domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt (1)
  • getPlaceList (9-9)
remote/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSourceImpl.kt (1)
  • getPlaceList (19-20)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt (2)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/card/SolplyPlaceCard.kt (1)
  • SolplyPlaceCard (39-137)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/theme/Theme.kt (1)
  • SolplyTheme (46-53)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (3)
core/ui/src/main/java/com/teamsolply/solply/ui/lifecycle/LaunchedEffectWithLifecycle.kt (1)
  • LaunchedEffectWithLifecycle (11-24)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/dialog/SolplyConfirmDialog.kt (1)
  • SolplyConfirmDialog (28-100)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/button/SolplyButton.kt (1)
  • SolplyBasicButton (58-84)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt (5)
core/ui/src/main/java/com/teamsolply/solply/ui/lifecycle/LaunchedEffectWithLifecycle.kt (1)
  • LaunchedEffectWithLifecycle (11-24)
core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/dialog/SolplyConfirmDialog.kt (1)
  • SolplyConfirmDialog (28-100)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/EmptyPlaceContainer.kt (1)
  • EmptyPlaceContainer (13-29)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SavedPlaceListContainer.kt (1)
  • SavedPlaceListContainer (15-36)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/MypageSettingItem.kt (1)
  • MypageSettingItem (17-71)
🔇 Additional comments (20)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/MypageSettingItem.kt (1)

46-49: 구현이 정확하며 클릭 핸들러가 올바르게 연결되었습니다.

customClickable 모디파이어가 올바르게 적용되었고 onClick 핸들러가 제대로 연결되었습니다. 다만 rippleEnabled = false로 설정하여 터치 리플 효과가 비활성화되었는데, 이것이 의도된 디자인인지 확인해 주세요. 리플 효과는 사용자에게 요소가 클릭 가능하다는 시각적 피드백을 제공합니다.

core/designsystem/src/main/java/com/teamsolply/solply/designsystem/component/dropdown/SolplyBasicDropDown.kt (1)

37-38: 형식 정리 OK

트레일링 콤마 제거로 동작 변화 없습니다. 그대로 진행해도 됩니다.

Also applies to: 103-103

feature/mypage/src/main/java/com/teamsolply/solply/mypage/navigation/MypageNavigation.kt (1)

23-23: 형식 정리 OK

트레일링 콤마 제거로 동작 변화 없습니다.

feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/navigation/ProfileNavigation.kt (1)

26-26: ProfileEditViewModel로의 전환 적절

  • @HiltViewModel 어노테이션과 @Inject 생성자를 통해 DI가 올바르게 설정되어 있습니다.
  • 현재 SavedStateHandle을 사용하지 않아 생성자에 포함되어 있지 않으니, 필요 시 파라미터로 추가하세요.
  • hiltViewModel(backStackEntry)를 통해 동일 목적지 재사용 시에도 같은 ViewModel 인스턴스가 재사용되어 스코프가 의도대로 작동합니다.
domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/PersonaEntity.kt (1)

5-8: LGTM!

PersonaEntity가 Persona 열거형과 설명을 깔끔하게 래핑하고 있습니다. 구조가 명확하고 도메인 모델로 적합합니다.

core/model/src/main/java/com/teamsolply/solply/model/Persona.kt (1)

5-12: LGTM!

Persona 열거형이 명확한 한글 설명과 함께 잘 정의되어 있습니다. @serializable 애노테이션도 적절히 적용되었습니다.

domain/mypage/src/main/java/com/teamsolply/solply/mypage/model/UserInfo.kt (1)

5-16: LGTM!

UserInfo와 SelectedTownInfo 데이터 클래스가 잘 구조화되어 있습니다. profileImageUrl의 기본값 설정도 적절합니다.

feature/mypage/src/main/java/com/teamsolply/solply/mypage/component/SolplyPersonaDropDown.kt (1)

23-23: PersonaEntity로의 전환이 적절합니다.

DropDownPersonaItem에서 도메인 모델인 PersonaEntity로 변경한 것은 좋은 선택입니다. description 속성을 통해 UI에 일관되게 표시됩니다.

Also applies to: 32-32, 39-39, 60-60

data/mypage/src/main/java/com/teamsolply/solply/mypage/datasource/MypageRemoteDataSource.kt (1)

8-13: LGTM!

MypageRemoteDataSource 인터페이스가 명확하게 정의되어 있습니다. 각 메서드의 시그니처가 적절하고 일관성이 있습니다.

data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetPersonaListResponseDto.kt (1)

7-19: LGTM!

DTO 구조가 명확하고 직렬화 애노테이션이 적절하게 적용되어 있습니다. Persona 열거형과의 통합도 잘 되어 있습니다.

data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/GetUserInfoResponseDto.kt (1)

31-41: LGTM!

DTO에서 도메인 모델로의 매핑 로직이 명확하며, null 처리도 의도에 맞게 구현되어 있습니다.

data/mypage/src/main/java/com/teamsolply/solply/mypage/dto/response/PlaceListResponseDto.kt (1)

7-30: LGTM!

DTO 구조가 명확하고 직렬화 어노테이션이 올바르게 적용되어 있습니다.

domain/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepository.kt (1)

7-12: LGTM!

Repository 인터페이스가 명확하게 정의되어 있으며, Result 타입을 사용한 에러 핸들링이 적절합니다.

remote/mypage/src/main/java/com/teamsolply/solply/mypage/service/MypageService.kt (1)

15-15: API 엔드포인트 경로 일관성을 확인하세요.

Line 12의 "/api/users"는 선행 슬래시가 있지만, 이 엔드포인트는 "api/places"로 선행 슬래시가 없습니다. Retrofit에서는 일반적으로 일관된 형식을 사용하는 것이 좋습니다. BaseUrl 설정에 따라 다를 수 있으니 의도된 것인지 확인해주세요.

</review_comment_end -->

feature/mypage/src/main/res/values/strings.xml (1)

3-19: LGTM!

문자열 리소스가 명확하게 추가되었습니다.

feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageScreen.kt (2)

52-63: 생명주기 안전한 사이드이펙트 수집, 좋습니다.

repeatOnLifecycle + collectLatest 조합, Init 1회 트리거 모두 적절합니다.


90-98: 로그아웃 확인 다이얼로그 연결 OK

상태 기반 표시와 confirm/dismiss 콜백 배선이 명확합니다.

feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (2)

51-67: 초기화/사이드이펙트 처리 방식 적절

Init 1회 트리거 및 repeatOnLifecycle 내 sideEffect 수집 모두 적합합니다.


162-165: 닉네임 검증 스펙 확인 필요

isLetterOrDigit()만 허용 시, 허용 문자(한글, 영문/숫자 외 특수문자 등) 정책과 상충 가능. 언더스코어/하이픈 허용 여부 등 스펙 확인 부탁드립니다.

선호 시 예시:

checkNicknameValidate = { input ->
    // 한글/문자/숫자 + _ - 만 허용
    input.isNotEmpty() && input.all { it.isLetterOrDigit() || it == '_' || it == '-' }
}
data/mypage/src/main/java/com/teamsolply/solply/mypage/repository/MypageRepositoryImpl.kt (1)

24-31: imageUrl/Urls 필드 타입 일치
DTO의 imageUrl과 도메인의 imageUrls가 모두 non-null String으로 선언돼 있어 타입 불일치나 null 처리 위험이 없습니다. placeList 역시 non-null List로 반환되므로 별도 null 검증 불필요합니다. 네이밍 통일은 선택적 리팩토링으로만 고려하세요.

Likely an incorrect or invalid review comment.

Comment on lines +50 to +61
private fun getInitInfo() {
viewModelScope.launch {
mypageRepository.getUserInfo()
.onSuccess { userInfo ->
reduce { copy(userInfo = userInfo) }

getPlaceList(
townId = userInfo.selectedTown.townId
)
}
}
}
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

getUserInfo 실패 시 에러 처리가 누락되었습니다.

getUserInfo 호출 시 onSuccess만 처리하고 onFailure가 없습니다. 네트워크 오류나 API 실패 시 사용자에게 피드백을 제공해야 합니다.

다음과 같이 에러 처리를 추가하세요:

             mypageRepository.getUserInfo()
                 .onSuccess { userInfo ->
                     reduce { copy(userInfo = userInfo) }

                     getPlaceList(
                         townId = userInfo.selectedTown.townId
                     )
                 }
+                .onFailure { error ->
+                    // 에러 상태를 UI에 전달하거나 로깅
+                }
📝 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
private fun getInitInfo() {
viewModelScope.launch {
mypageRepository.getUserInfo()
.onSuccess { userInfo ->
reduce { copy(userInfo = userInfo) }
getPlaceList(
townId = userInfo.selectedTown.townId
)
}
}
}
private fun getInitInfo() {
viewModelScope.launch {
mypageRepository.getUserInfo()
.onSuccess { userInfo ->
reduce { copy(userInfo = userInfo) }
getPlaceList(
townId = userInfo.selectedTown.townId
)
}
.onFailure { error ->
// 에러 상태를 UI에 전달하거나 로깅
}
}
}
🤖 Prompt for AI Agents
In feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt
around lines 50 to 61, the getUserInfo call only handles onSuccess so failures
are ignored; add an onFailure handler to capture the exception, log it, and
update UI state so the user gets feedback (e.g., reduce { copy(error =
errorMessageOrObject) } or set a toast/snackbar event in state); ensure
getPlaceList is only invoked on success and keep existing success handling
intact.

Comment on lines +63 to 73
private fun getPlaceList(townId: Long) {
viewModelScope.launch {
mypageRepository.getPlaceList(townId).onSuccess {
reduce {
copy(
placeList = it.toPersistentList()
)
}
}
}
}
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

getPlaceList 실패 시 에러 처리가 누락되었습니다.

getPlaceList 호출도 onFailure 처리가 없어 네트워크 오류 시 사용자가 상황을 알 수 없습니다.

에러 처리를 추가하세요:

             mypageRepository.getPlaceList(townId).onSuccess {
                 reduce {
                     copy(
                         placeList = it.toPersistentList()
                     )
                 }
             }
+            .onFailure { error ->
+                // 에러 상태를 UI에 전달하거나 로깅
+            }
📝 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
private fun getPlaceList(townId: Long) {
viewModelScope.launch {
mypageRepository.getPlaceList(townId).onSuccess {
reduce {
copy(
placeList = it.toPersistentList()
)
}
}
}
}
private fun getPlaceList(townId: Long) {
viewModelScope.launch {
mypageRepository.getPlaceList(townId).onSuccess {
reduce {
copy(
placeList = it.toPersistentList()
)
}
}
.onFailure { error ->
// 에러 상태를 UI에 전달하거나 로깅
}
}
}
🤖 Prompt for AI Agents
In feature/mypage/src/main/java/com/teamsolply/solply/mypage/MypageViewModel.kt
around lines 63 to 73, the getPlaceList call lacks onFailure handling so network
errors are ignored; add an onFailure handler to the
mypageRepository.getPlaceList(townId) call that reduces state to reflect the
error (e.g., set an error message/string or error flag and clear any loading
indicator), optionally log the throwable, and ensure the UI-consumable state is
updated so the user can see the failure and retry.

private val mypageRepository: MypageRepository
) :
BaseViewModel<ProfileEditState, ProfileEditIntent, ProfileEditSideEffect>(ProfileEditState()) {
private val nicknameFlow = MutableStateFlow("")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

닉네임 유효성 검사 로직이 작동하지 않습니다.

nicknameFlow가 생성되었지만 ChangeInputNickname 인텐트에서 값이 업데이트되지 않습니다. 현재 상태에서는 닉네임 중복 체크가 전혀 실행되지 않습니다.

다음과 같이 수정하세요:

             is ProfileEditIntent.ChangeInputNickname -> {
                 reduce {
                     copy(inputNickname = intent.nickname)
                 }
+                nicknameFlow.value = intent.nickname
             }

Also applies to: 25-29, 115-126

🤖 Prompt for AI Agents
In
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditViewModel.kt
around lines 19 (and also apply to blocks at 25-29 and 115-126), the
MutableStateFlow nicknameFlow is created but never updated in the
ChangeInputNickname intent handler so nickname change events and duplicate-check
logic never run; modify the intent handling to set the MutableStateFlow's value
(e.g., _nicknameFlow.value = newNickname) whenever ChangeInputNickname is
received, expose only an immutable StateFlow publicly if needed, and ensure any
collectors / debounce logic subscribe to that StateFlow so the nickname
duplicate check is triggered; update all corresponding intent branches and
collectors in the indicated lines to use the MutableStateFlow update pattern.

Copy link
Contributor

@leeseokchan00 leeseokchan00 left a comment

Choose a reason for hiding this comment

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

👍

@ImHyungsuk ImHyungsuk merged commit 37001fc into develop Oct 7, 2025
1 check passed
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.

[Feature] 마이페이지 & 프로필 수정 상태관리

3 participants