[FEAT] 프로필 화면 구현 및 프로필 수정/출발지 설정/로그아웃 기능 제공 - #114
Conversation
MemberProfile 엔티티와 프로필 조회/닉네임·이미지 수정/로그아웃 UseCase, Repository·Session 인터페이스 프로토콜을 추가
MemberProfile DTO·Endpoint와 Repository·UseCase 구현체를 추가해 프로필 조회/수정 및 로그아웃 네트워크 흐름을 연결
MemberProfileClient·SessionClient를 정의하고 Factory 조립 후 BangawoApp prepareDependencies에서 live 주입
AuthFlow에 있던 프로필 입력 화면을 ProfileInputFeature 모듈로 분리하고, 온보딩·프로필 수정 양쪽에서 재사용하도록 수정 모드와 원격 이미지 표현을 지원
MyDeparturePlaceEditSheetContent를 store 의존을 제거한 DeparturePlaceEditSheetContent로 바꿔 재사용 가능하게 수정
ProfileFeature/ProfileView로 프로필 조회·수정·출발지 설정· 로그아웃을 제공하고, 홈 마이페이지 진입과 로그아웃 시 루트 인증 화면 전환을 연결
프로필 화면·입력 화면 로직과 홈 네비게이션, 루트 로그아웃 라우팅에 대한 단위 테스트를 추가
| } | ||
|
|
||
| @MainActor | ||
| public var profilePNGData: Data? { |
There was a problem hiding this comment.
🟠 [P2] Major
image.snapshot().pngData()는 뷰 계층을 UIImage로 렌더링한 후 고부하의 CPU 연산이 수반되는 PNG 데이터 인코딩을 동기적으로 수행합니다.
이 연산이 @MainActor 프로퍼티에서 동기 처리되므로, 프로필 이미지 수정 버튼 탭 시 메인 스레드가 일시적으로 블로킹되어 UI 버벅임(Jank)이 발생할 위험이 큽니다. 스냅샷 추출은 메인 스레드에서 하되, PNG 인코딩 변환 작업은 Task.detached 등을 통해 협력 스레드 풀에서 비동기 처리할 것을 강력히 권장합니다.
| public var profilePNGData: Data? { | |
| @MainActor | |
| public var profileSnapshot: UIImage { | |
| image.snapshot() | |
| } |
이후 호출부(ProfileInputFeature.swift 등)에서 아래처럼 비동기 헬퍼를 도입해 메인 스레드를 보호해 주십시오.
func makePNGData(from image: UIImage) async -> Data? {
await Task.detached(priority: .userInitiated) {
image.pngData()
}.value
}| } | ||
|
|
||
| @ViewBuilder | ||
| private var profileMenu: some View { |
There was a problem hiding this comment.
🟡 [P3] Minor
SwiftUI 선언 규칙에 따라 복잡한 분기나 별도 역할을 가진 하위 영역은 부모 뷰 내부의 @ViewBuilder 연산자 프로퍼티 대신 전용 struct 형태로 독립시키는 것이 좋습니다.
뷰 모델 격인 Store 상태 변화에 따른 Invalidation 범위를 줄여 효율적인 부분 렌더링을 유도할 수 있도록 profileMenu를 별도 SubView struct로 전환해 주세요.
| private var profileMenu: some View { | |
| private struct ProfileMenu: View { | |
| @Binding var isShowingMenu: Bool | |
| @Binding var isPhotosPickerPresented: Bool | |
| let store: StoreOf<ProfileInputFeature> | |
| var body: some View { | |
| if isShowingMenu { | |
| GeometryReader { geo in | |
| DesignSystem.Menu(items: [ | |
| .init( | |
| label: "프로필 설정하기", | |
| icon: .Asset.icStar24, | |
| iconColor: Colors.gray600 | |
| ) { | |
| store.send(.avatarMenuProfileSetupTapped) | |
| isShowingMenu = false | |
| }, | |
| .init( | |
| label: "앨범에서 선택하기", | |
| icon: .Asset.icAlbum24, | |
| iconColor: Colors.gray600 | |
| ) { | |
| isPhotosPickerPresented = true | |
| isShowingMenu = false | |
| } | |
| ]) | |
| .frame(width: Metric.menuWidth) | |
| .offset( | |
| x: (geo.size.width + Metric.avatarSize) / 2 - Metric.menuWidth, | |
| y: Spacing.spacing400 + Metric.avatarSize + Spacing.spacing200 | |
| ) | |
| } | |
| } | |
| } | |
| } |
| guard let httpResponse = response as? HTTPURLResponse else { | ||
| throw ProfileImageUploadError.invalidResponse | ||
| } | ||
| guard (200..<300).contains(httpResponse.statusCode) else { |
There was a problem hiding this comment.
🔵 [P4] Readability
Swift 스타일 가이드에 따라 가독성 향상을 위해 guard 조기 리턴(early return) 구문 블록이 끝난 이후에는 개행(blank line)을 두는 것을 규칙으로 삼고 있습니다. 이전 guard 블록의 닫는 괄호 다음에 빈 줄을 삽입해 주세요.
| guard (200..<300).contains(httpResponse.statusCode) else { | |
| } | |
| guard (200..<300).contains(httpResponse.statusCode) else { |
메인 액터에서 pngData 인코딩까지 동기 수행하던 것을 snapshot만 메인에서 얻고 인코딩은 Task.detached로 분리해 프로필 저장 시 메인 스레드 블로킹 방지
🧐 개요
마이페이지 성격의 프로필 화면을 신설하고, 프로필 수정 · 기본 출발지 설정 · 로그아웃을 한 화면에서 처리할 수 있도록 구현했습니다. 온보딩 전용으로 묶여 있던 프로필 입력 화면(닉네임/프로필 이미지)을
ProfileInputFeature모듈로 분리해 온보딩과 프로필 수정 양쪽에서 재사용합니다.Closes #113
✨ 작업 내용
도메인 · 데이터 계층 (Member)
MemberProfileEntity 및FetchMemberProfileUseCase/UpdateMemberNicknameUseCase/UpdateMemberProfileImageUseCase/LogoutUseCase프로토콜 정의MemberProfileClient/SessionClient정의 후MemberProfileFactory/SessionFactory로 조립,BangawoApp에서 주입프로필 입력 모듈 분리
AuthFlowFeature내부의 프로필 입력 화면을ProfileInputFeature공용 모듈로 분리 (닉네임 입력 · 프로필 이미지 피커)프로필 화면
ProfileFeature/ProfileView구현 — 프로필 조회, 로딩/에러 처리약관
TermWebView)로 전환테스트
ProfileFeature/ProfileInputFeature/RootFeature로그아웃 플로우 단위 테스트 작성✅ 체크리스트
MemberProfileEntity 및 Member 도메인 UseCase 프로토콜 정의MemberProfileClient/SessionClient정의 및 Factory 조립 후BangawoApp에서 주입ProfileInputFeature모듈 분리ProfileFeature/ProfileView구현ProfileFeature/RootFeature단위 테스트 작성🙏 To Reviewer
변경 규모가 다소 큽니다. 도메인/데이터 계층 →
ProfileInputFeature모듈 분리 →ProfileFeature화면 순으로 보시면 흐름을 따라가기 수월합니다.기본 출발지 설정 시트의 목록 조회/기본값 변경 연동은 이번 PR 범위에서 화면 골격 위주로 반영했고, 약관 뷰어는 임시로 항목을 고정해 두었습니다.