Conversation
- InfoLight (#F0F6FF), InfoDark (#0C4596) colorset 추가 - Color.swift에 Info 색상 extension 추가
- Swift 6 concurrency 지원을 위해 @mainactor 추가 - 프로토콜을 채택하는 모든 ViewModel이 MainActor에서 실행 보장
- ProfileViewModel에 GetUserInfoUseCase 주입 및 NavigationCommand 시스템 추가 - isDormant 상태 추가 및 getUserRole 메서드 구현 - DORMANT 상태일 때 휴면 해제 섹션 UI 표시 - 휴면 해제 성공 시 매칭 메인 탭으로 이동 및 토스트 표시
- WithdrawViewFactory에서 ViewModel을 직접 생성하지 않도록 변경 - View의 init에서 UseCase를 받아 ViewModel 생성 - Profile 모듈과 동일한 패턴으로 통일
Walkthrough프로필에 사용자 정보 조회와 비활성 사용자 활성화 흐름을 추가하고, 디자인 시스템에 infoLight/infoDark 색상 자산과 Color 확장을 도입했습니다. 네비게이션용 Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant ProfileView as ProfileView
participant ProfileVM as ProfileViewModel
participant GetUserInfoUC as GetUserInfoUseCase
participant ActivateUC as ActivateUserUseCase
participant Router as Router
User->>ProfileView: 화면 진입 (onAppear)
ProfileView->>ProfileVM: onAppear 트리거
par parallel
ProfileVM->>GetUserInfoUC: getUserRole()
GetUserInfoUC-->>ProfileVM: 역할 및 dormant 상태 응답
ProfileVM->>ProfileVM: isDormant 업데이트
and
ProfileVM->>ProfileVM: unread notifications 조회
end
alt isDormant == true
ProfileView->>User: 비활성 사용자 섹션 노출
User->>ProfileView: 활성화 버튼 탭
ProfileView->>ProfileVM: activateDormantUser 액션
ProfileVM->>ActivateUC: activate 요청
ActivateUC-->>ProfileVM: 성공 응답
ProfileVM->>Router: navigationCommand 설정/네비게이션 실행
Router-->>User: 완료 토스트/화면 전환
else
ProfileView->>User: 일반 프로필 표시
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroViewModel.swift (1)
50-52:⚠️ Potential issue | 🟡 Minor
print(error)— 디버그 로그 잔존
catch블록에print(error)만 존재해, 휴면 전환 실패 시 사용자에게 아무런 피드백이 없습니다. 릴리스 빌드에서도 콘솔 출력이 남고, 실제 오류 처리 로직이 없어 사일런트 실패로 이어질 수 있습니다.🛡️ 오류 처리 개선 제안
} catch { - print(error) + // TODO: 오류 상태를 뷰에 노출하거나 구조화된 로깅 사용 + setNavigationCommand(.none) // 또는 에러 alert 표시 }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroViewModel.swift` around lines 50 - 52, The catch block in WithdrawIntroViewModel currently just calls print(error); replace this with real error handling: record the error using your app logger/analytics (e.g., Logger or an analytics.trackError call), set a user-facing state on WithdrawIntroViewModel (e.g., an `@Published` errorMessage or showError boolean) so the view can present a localized alert, and remove any raw console prints so no debug output remains in release builds; ensure the error is also gracefully handled (fallback flow or noop with user feedback) in the function where the try/catch occurs.
🧹 Nitpick comments (3)
Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroViewModel.swift (1)
47-49:await MainActor.run { }불필요 — 제거 가능
WithdrawIntroViewModel자체가@MainActor로 선언되어 있으므로,requestDormancy()역시 Main Actor에서 실행됩니다.await setUserDormantUseCase.execute()이후 재개(resume)될 때도 Swift 런타임이 자동으로 Main Actor로 돌아오기 때문에,await MainActor.run { }래핑은 아무 효과가 없습니다.♻️ 불필요한 MainActor.run 제거 제안
_ = try await setUserDormantUseCase.execute() - await MainActor.run { - isShowingDormantCompletionAlert = true - } + isShowingDormantCompletionAlert = true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroViewModel.swift` around lines 47 - 49, The await MainActor.run { isShowingDormantCompletionAlert = true } wrapper is redundant because WithdrawIntroViewModel is `@MainActor` and requestDormancy() resumes on the main actor after await setUserDormantUseCase.execute(); remove the MainActor.run call and assign isShowingDormantCompletionAlert = true directly inside requestDormancy() (or ensure requestDormancy() is annotated `@MainActor`) so the property mutation happens on the main actor without the extra wrapper.Presentation/Feature/Profile/Sources/ProfileView.swift (1)
77-104: 불필요한 외부HStack제거 가능
dormantSection의 바깥쪽HStack(spacing: 0)은 내부HStack만을 감싸고 추가적인 레이아웃 역할을 하지 않습니다..padding(.horizontal, 20)과.frame(maxWidth: .infinity)를 내부HStack에 직접 적용하면 불필요한 중첩을 제거할 수 있습니다.♻️ 중첩 HStack 제거 제안
private var dormantSection: some View { - HStack(spacing: 0) { - HStack(spacing: 0) { - Text("다른 사람들에게\n프로필이 노출되지 않아요.") - .multilineTextAlignment(.leading) - .pretendard(.body_S_M) - .foregroundStyle(Color.infoDark) - - Spacer() - - Button( - action: { viewModel.handleAction(.activateDormantUser) }, - label: { - Text("휴면 해제") - .pretendard(.caption_M_M) - .foregroundStyle(Color.grayscaleWhite) - .padding(.vertical, 8) - .padding(.horizontal, 16) - .background(RoundedRectangle(cornerRadius: 4).fill(.infoDark)) - } - ) - } - .padding(.all, 14) - .background(RoundedRectangle(cornerRadius: 8).fill(.infoLight)) - } - .padding(.horizontal, 20) - .frame(maxWidth: .infinity) + HStack(spacing: 0) { + Text("다른 사람들에게\n프로필이 노출되지 않아요.") + .multilineTextAlignment(.leading) + .pretendard(.body_S_M) + .foregroundStyle(Color.infoDark) + + Spacer() + + Button( + action: { viewModel.handleAction(.activateDormantUser) }, + label: { + Text("휴면 해제") + .pretendard(.caption_M_M) + .foregroundStyle(Color.grayscaleWhite) + .padding(.vertical, 8) + .padding(.horizontal, 16) + .background(RoundedRectangle(cornerRadius: 4).fill(.infoDark)) + } + ) + } + .padding(.all, 14) + .background(RoundedRectangle(cornerRadius: 8).fill(.infoLight)) + .padding(.horizontal, 20) + .frame(maxWidth: .infinity) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Presentation/Feature/Profile/Sources/ProfileView.swift` around lines 77 - 104, The outer HStack in dormantSection is redundant; remove the wrapping HStack(spacing: 0) and apply the .padding(.horizontal, 20) and .frame(maxWidth: .infinity) modifiers directly to the remaining inner HStack so layout and spacing remain unchanged. Locate the dormantSection computed property and delete the outer container, keeping the inner HStack that contains the Text and Button (including the Button action viewModel.handleAction(.activateDormantUser)), plus its .padding(.all, 14) and .background, then add the horizontal padding and maxWidth frame to that inner HStack.Presentation/Feature/Profile/Sources/ProfileViewModel.swift (1)
77-80:getUserRole()와checkUnreadNotifications()를 병렬 실행으로 개선 가능두 호출은 서로 독립적인 네트워크 요청이지만 현재 순차 실행됩니다. 별도
Task로 분리하면 두 요청이 동시에 시작되어 화면 반응성이 개선됩니다.⚡ 병렬 실행 제안
- Task { - await getUserRole() - await checkUnreadNotifications() - } + Task { await getUserRole() } + Task { await checkUnreadNotifications() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Presentation/Feature/Profile/Sources/ProfileViewModel.swift` around lines 77 - 80, Replace the sequential awaits so getUserRole() and checkUnreadNotifications() run concurrently: inside the existing Task in ProfileViewModel (or the surrounding async context) start both operations in parallel (e.g., use `async let userRole = getUserRole()` and `async let unread = checkUnreadNotifications()` or spawn two separate Tasks) then await both results, and ensure any state mutations happen on the main actor; also propagate or handle errors from each call separately so one failure doesn't block the other.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@Presentation/DesignSystem/Resources/Colors.xcassets/InfoDark.colorset/Contents.json`:
- Around line 1-20: InfoDark and InfoLight assets only define a "universal"
appearance, so add explicit dark-mode variants to each Colors.xcassets
Contents.json (e.g., in InfoDark.colorset/Contents.json and
InfoLight.colorset/Contents.json) by adding an appearances array or extra color
entries with "appearance" : [{"appearance": "luminosity","value":"dark"}] (or
the Xcode equivalent) and supply alternate sRGB color components appropriate for
dark mode; keep the existing universal/light color as the default, ensure keys
like "color-space" and "components" are preserved, and validate the chosen
dark/light pairs meet accessibility contrast requirements.
In `@Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroView.swift`:
- Line 15: The `@State` property viewModel is missing the private access modifier;
change the declaration of viewModel in WithdrawIntroView (the `@State` var
viewModel: WithdrawIntroViewModel) to be private (e.g., `@State` private var
viewModel: WithdrawIntroViewModel) and update any internal references within
WithdrawIntroView accordingly so external code cannot directly mutate the state,
satisfying the private_swiftui_state lint rule.
In `@Presentation/Router/Sources/NavigationCommand.swift`:
- Around line 106-109: The protocol-level `@MainActor` on
NavigationCommandConsumable causes all conformers to be main-actor isolated but
AccountSettingsViewModel currently lacks an explicit `@MainActor`; update
AccountSettingsViewModel to add `@MainActor` alongside its existing `@Observable`
(or alternately remove `@MainActor` from NavigationCommandConsumable if you intend
mixed isolation), and ensure the class still declares conformance to
NavigationCommandConsumable so the navigationCommand property remains correct.
---
Outside diff comments:
In
`@Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroViewModel.swift`:
- Around line 50-52: The catch block in WithdrawIntroViewModel currently just
calls print(error); replace this with real error handling: record the error
using your app logger/analytics (e.g., Logger or an analytics.trackError call),
set a user-facing state on WithdrawIntroViewModel (e.g., an `@Published`
errorMessage or showError boolean) so the view can present a localized alert,
and remove any raw console prints so no debug output remains in release builds;
ensure the error is also gracefully handled (fallback flow or noop with user
feedback) in the function where the try/catch occurs.
---
Duplicate comments:
In
`@Presentation/DesignSystem/Resources/Colors.xcassets/InfoLight.colorset/Contents.json`:
- Around line 1-20: InfoLight.colorset의 Contents.json에 다크 모드 변형(color appearance
for dark) 정의가 빠져있습니다; InfoDark.colorset과 동일한 맥락으로 두 색상이 함께 사용되므로
InfoLight.colorset의 "colors" 배열 항목에 appearance:"dark" 또는
"appearances":[{"appearance":"luminosity","value":"dark"}] 같은 다크 변형을 추가해
주시고(InfoDark.colorset에 사용된 명명·값을 참고), alpha/rgba 값과 색 공간("color-space")이 일치하는지
확인하여 필요 시 두 색상 세트를 동기화하세요.
---
Nitpick comments:
In `@Presentation/Feature/Profile/Sources/ProfileView.swift`:
- Around line 77-104: The outer HStack in dormantSection is redundant; remove
the wrapping HStack(spacing: 0) and apply the .padding(.horizontal, 20) and
.frame(maxWidth: .infinity) modifiers directly to the remaining inner HStack so
layout and spacing remain unchanged. Locate the dormantSection computed property
and delete the outer container, keeping the inner HStack that contains the Text
and Button (including the Button action
viewModel.handleAction(.activateDormantUser)), plus its .padding(.all, 14) and
.background, then add the horizontal padding and maxWidth frame to that inner
HStack.
In `@Presentation/Feature/Profile/Sources/ProfileViewModel.swift`:
- Around line 77-80: Replace the sequential awaits so getUserRole() and
checkUnreadNotifications() run concurrently: inside the existing Task in
ProfileViewModel (or the surrounding async context) start both operations in
parallel (e.g., use `async let userRole = getUserRole()` and `async let unread =
checkUnreadNotifications()` or spawn two separate Tasks) then await both
results, and ensure any state mutations happen on the main actor; also propagate
or handle errors from each call separately so one failure doesn't block the
other.
In
`@Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroViewModel.swift`:
- Around line 47-49: The await MainActor.run { isShowingDormantCompletionAlert =
true } wrapper is redundant because WithdrawIntroViewModel is `@MainActor` and
requestDormancy() resumes on the main actor after await
setUserDormantUseCase.execute(); remove the MainActor.run call and assign
isShowingDormantCompletionAlert = true directly inside requestDormancy() (or
ensure requestDormancy() is annotated `@MainActor`) so the property mutation
happens on the main actor without the extra wrapper.
Presentation/DesignSystem/Resources/Colors.xcassets/InfoDark.colorset/Contents.json
Show resolved
Hide resolved
Presentation/Feature/Withdraw/Sources/WithdrawIntro/WithdrawIntroView.swift
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Presentation/Feature/AccountSettings/Sources/AccountSettingsViewModel.swift (1)
155-179:@MainActor클래스 내의async메서드에서await MainActor.run { }호출이 불필요합니다.클래스에
@MainActor가 선언된 이후, 클래스의 모든 메서드(비동기 포함)는 암묵적으로@MainActor로 격리됩니다.@MainActor async함수 내에서는await일시 중단 후 재개될 때도 항상 Main Actor 위에서 실행되므로,await MainActor.run { }래퍼는 중복입니다.대상 위치:
setDormancy: Lines 161-167, 175-178syncAccountSettingsFromUserInfo: Lines 184-186handleConfirmLogoutButton: Lines 241-245, 248-250♻️ 불필요한 `await MainActor.run` 제거 제안
private func setDormancy(enabled isEnabled: Bool) async { do { if isEnabled { _ = try await setUserDormantUseCase.execute() } else { _ = try await activateUserUseCase.execute() - await MainActor.run { - setNavigationCommand( - .setRoute(.home) - .withSwitchTab(.home) - .withToast(.dormancyDeactivated) - ) - } + setNavigationCommand( + .setRoute(.home) + .withSwitchTab(.home) + .withToast(.dormancyDeactivated) + ) } await syncAccountSettingsFromUserInfo() } catch { print(error) await syncAccountSettingsFromUserInfo() } - await MainActor.run { - state.dormancy.finishUpdate() - state.dormancy.hideConfirmAlert() - } + state.dormancy.finishUpdate() + state.dormancy.hideConfirmAlert() } private func syncAccountSettingsFromUserInfo() async { do { let userInfo = try await getUserInfoUseCase.execute() - await MainActor.run { - apply(userInfo: userInfo) - } + apply(userInfo: userInfo) } catch { print(error) } }func handleConfirmLogoutButton() { Task { do { _ = try await patchLogoutUseCase.execute() - await MainActor.run { - showLogoutAlert = false - clearUserDataPreservingFCMToken() - setNavigationCommand(.setRoute(.splash)) - } + showLogoutAlert = false + clearUserDataPreservingFCMToken() + setNavigationCommand(.setRoute(.splash)) } catch { print(error) - await MainActor.run { - showLogoutAlert = false - } + showLogoutAlert = false } } }Also applies to: 181-190, 237-253
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Presentation/Feature/AccountSettings/Sources/AccountSettingsViewModel.swift` around lines 155 - 179, The class is already `@MainActor` so remove the redundant await MainActor.run { ... } wrappers inside the async methods: in setDormancy remove the MainActor.run blocks that wrap setNavigationCommand(...) and the final state.dormancy.finishUpdate()/hideConfirmAlert() calls and call those statements directly; do the same in syncAccountSettingsFromUserInfo and handleConfirmLogoutButton (remove await MainActor.run and execute the UI/state updates and navigation directly). Ensure you keep await on any async useCase.execute()/sync calls but eliminate the unnecessary MainActor.run invocations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Presentation/Feature/AccountSettings/Sources/AccountSettingsViewModel.swift`:
- Around line 155-179: The class is already `@MainActor` so remove the redundant
await MainActor.run { ... } wrappers inside the async methods: in setDormancy
remove the MainActor.run blocks that wrap setNavigationCommand(...) and the
final state.dormancy.finishUpdate()/hideConfirmAlert() calls and call those
statements directly; do the same in syncAccountSettingsFromUserInfo and
handleConfirmLogoutButton (remove await MainActor.run and execute the UI/state
updates and navigation directly). Ensure you keep await on any async
useCase.execute()/sync calls but eliminate the unnecessary MainActor.run
invocations.
🏷️ 티켓 번호
PC-1809
👷🏼♂️ 변경 사항
DesignSystem Info 색상 추가
Profile DORMANT 상태 휴면 해제 섹션 구현
Swift 6 concurrency 대응
Withdraw 모듈 패턴 리팩토링
💬 참고 사항
📸 스크린샷(Optional)
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선