Conversation
* refactor(MyCollectionProfileSettings): 프로필 설정 페이지 분리작업 * feat(ProfileCard): 팬덤, PICKS 비활성화 * feat(ProfileSetting): 프로필 이미지 변경, 삭제, 태그 수정 API 연동 * feat(ProfileSetting): 프로필 설정 페이지 UI/UX 업데이트 * feat(ProfileSetting): UI 디테일 수정 * feat(ProfileSetting): 태그 수정 시 도움말 추가 * refactor(ProfileSetting): 리팩토링 * feat(ProfileSetting): 여백 설정 * feat(1.0.18): 버전 업데이트
There was a problem hiding this comment.
Pull request overview
내 컬렉션(MyCollection) 내 프로필 설정 화면을 리팩토링하고, 프로필 이미지 삭제/수정 및 태그 수정 API를 ProfileSettingViewModel 기반으로 연동한 변경입니다. 팬덤/PICKS 표시는 비활성화되었습니다.
Changes:
- 프로필 설정 UI를 컴포넌트 단위로 분리하고, 태그 편집(검증/상태/키보드 대응) UX 추가
ProfileSettingViewModel+UserService확장으로 프로필 이미지(프리사인드 업로드) / 태그 업데이트 API 연동- MyCollection 화면에서 프로필 설정 전용 VM을 분리해 화면 전환/유저 동기화 처리
Reviewed changes
Copilot reviewed 15 out of 18 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileTagSectionView.swift | 태그 표시/편집 입력 영역 UI 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileTagEditActionRowView.swift | 태그 편집 취소/저장 액션 UI 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileSettingsInfoRowView.swift | (현재 미사용) 프로필 정보 Row UI 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileSettingsInfoCardView.swift | (현재 미사용) 프로필 정보 카드 UI 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileSettingsHeaderView.swift | 프로필 설정 헤더 UI 교체/단순화 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileSettingPageContainerView.swift | 프로필 설정 페이지 컨테이너(배경/패딩/높이) 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionProfileImageColumnView.swift | PhotosPicker 기반 프로필 이미지 선택/리셋 UI 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/components/MyCollectionAccountActionButton.swift | 로그아웃/회원탈퇴 진입 버튼 컴포넌트 추가 |
| KillingPart/Views/Screens/Main/My/MyCollection/ProfileSetting/ProfileSettingSection.swift | 프로필 설정 섹션 본체(키보드 inset, 태그 검증/제출, 이미지 처리) 구현 |
| KillingPart/Views/Screens/Main/My/MyCollection/MyCollectionView.swift | 프로필 설정 전용 VM 주입/유저 동기화/업데이트 반영 연결 |
| KillingPart/Views/Screens/Main/My/MyCollection/Components/ProfileSettingSection/MyCollectionProfileSettingsSection.swift | 기존 단순 프로필 설정 섹션 제거 |
| KillingPart/Views/Screens/Main/My/MyCollection/Components/ProfileSettingSection/MyCollectionProfileSettingsHeaderView.swift | 기존 헤더 제거(새 헤더로 대체) |
| KillingPart/Views/Screens/Main/My/MyCollection/Components/ProfileCard/MyCollectionProfileCard.swift | 팬덤/PICKS UI 비활성화(현재는 주석 처리) |
| KillingPart/ViewModels/My/MyCollection/ProfileSetting/ProfileSettingViewModel.swift | 프로필 설정 전용 VM 신설(태그/이미지 업데이트, 상태/메시지 관리) |
| KillingPart/ViewModels/My/MyCollection/MyCollectionViewModel.swift | 유저 업데이트 반영 헬퍼(applyUpdatedUser) 추가 |
| KillingPart/Services/UserService.swift | 프로필 이미지 삭제/프리사인드 발급/업로드/태그 업데이트 API 추가 + 에러 메시지 정규화 |
| KillingPart/Models/UserModel.swift | Presigned URL/요청 DTO 추가 |
| KillingPart.xcodeproj/project.pbxproj | 앱 버전(빌드/마케팅) 업데이트 (16→18) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return TagHelperMessage( | ||
| text: "30자 이내의 영문과 숫자, 특수문자([.],[_])로 조합해주세요.", | ||
| color: .red.opacity(0.95) | ||
| ) |
There was a problem hiding this comment.
The helper copy here says "영문" but the actual tag validator only allows lowercase a–z (allowedCharacters excludes A–Z). Align the helper text with the real rule (e.g., explicitly say "영문 소문자") or change validation to accept/normalize uppercase to avoid confusing rejections.
| let normalizedMessage = message? | ||
| .trimmingCharacters(in: .whitespacesAndNewlines) | ||
| .lowercased() ?? "" | ||
|
|
||
| if normalizedMessage.contains("이미 존재") | ||
| || normalizedMessage.contains("이미 사용") | ||
| || normalizedMessage.contains("already") | ||
| { | ||
| tagValidationFeedback = .duplicate | ||
| return | ||
| } | ||
|
|
||
| if normalizedMessage.contains("tag는") | ||
| || normalizedMessage.contains("30") | ||
| || normalizedMessage.contains("영문") | ||
| || normalizedMessage.contains("소문자") | ||
| || normalizedMessage.contains("연속") | ||
| || normalizedMessage.contains("형식") | ||
| { | ||
| tagValidationFeedback = .invalidFormat | ||
| return | ||
| } | ||
|
|
||
| tagValidationFeedback = .unavailable |
There was a problem hiding this comment.
updateTagValidationFeedback(from:) classifies failures by substring-matching (localized) error messages. This is brittle (server copy/locale changes can misclassify). Prefer propagating a typed error/code from UserService/ProfileSettingViewModel and switching on that instead of parsing strings.
| let normalizedMessage = message? | |
| .trimmingCharacters(in: .whitespacesAndNewlines) | |
| .lowercased() ?? "" | |
| if normalizedMessage.contains("이미 존재") | |
| || normalizedMessage.contains("이미 사용") | |
| || normalizedMessage.contains("already") | |
| { | |
| tagValidationFeedback = .duplicate | |
| return | |
| } | |
| if normalizedMessage.contains("tag는") | |
| || normalizedMessage.contains("30") | |
| || normalizedMessage.contains("영문") | |
| || normalizedMessage.contains("소문자") | |
| || normalizedMessage.contains("연속") | |
| || normalizedMessage.contains("형식") | |
| { | |
| tagValidationFeedback = .invalidFormat | |
| return | |
| } | |
| tagValidationFeedback = .unavailable | |
| // Treat `message` as a stable, non-localized error code (e.g. "tag_duplicate", | |
| // "tag_invalid_format", "tag_unavailable") instead of parsing localized text. | |
| let normalizedCode = message? | |
| .trimmingCharacters(in: .whitespacesAndNewlines) | |
| .lowercased() ?? "" | |
| switch normalizedCode { | |
| case "tag_duplicate", | |
| "duplicate", | |
| "tag_already_exists": | |
| tagValidationFeedback = .duplicate | |
| case "tag_invalid_format", | |
| "invalid_format", | |
| "bad_request_tag_format": | |
| tagValidationFeedback = .invalidFormat | |
| case "tag_unavailable", | |
| "unavailable", | |
| "": | |
| // Default or explicit "unavailable" error. | |
| fallthrough | |
| default: | |
| tagValidationFeedback = .unavailable | |
| } |
| Button(action: onBackTap) { | ||
| HStack(spacing: 6) { | ||
| Image(systemName: "arrow.left") | ||
| .font(.system(size: 20, weight: .semibold)) | ||
| } |
There was a problem hiding this comment.
The back button is image-only and lacks an accessibility label. Add an accessibilityLabel (e.g., "뒤로가기") so VoiceOver users can navigate reliably.
| // MyCollectionProfileStatItemView(value: fanStatText, title: "팬덤") | ||
| // MyCollectionProfileStatItemView(value: pickStatText, title: "PICKS") |
There was a problem hiding this comment.
Commented-out SwiftUI views tend to linger and make future edits harder (and keep unused props like fanStatText/pickStatText around). If 팬덤/PICKS are intentionally disabled, prefer deleting these lines and cleaning up the unused properties (or gating behind a clear feature flag).
| // MyCollectionProfileStatItemView(value: fanStatText, title: "팬덤") | |
| // MyCollectionProfileStatItemView(value: pickStatText, title: "PICKS") |
|
|
||
| func syncUser(_ user: UserModel?) { | ||
| self.user = user | ||
| tagDraft = user?.tag ?? "" |
There was a problem hiding this comment.
tagDraft is set directly from user.tag, which can already include an "@" (other view models treat user.tag as optionally prefixed). Since the edit UI renders a separate "@" label, this can result in a duplicated "@@" in the text field. Consider storing a normalized (no-leading-@, trimmed) value in tagDraft in syncUser (and in applyUpdatedUser).
| tagDraft = user?.tag ?? "" | |
| let rawTag = user?.tag ?? "" | |
| tagDraft = normalizedTag(from: rawTag) |
| Button(action: onCancel) { | ||
| Image(systemName: "xmark.circle.fill") | ||
| .font(.system(size: 22, weight: .semibold)) | ||
| .foregroundStyle(.white.opacity(0.72)) |
There was a problem hiding this comment.
This cancel button is icon-only, but it doesn't provide an accessibility label/hint. Add accessibilityLabel (e.g., "태그 편집 취소") so VoiceOver users can understand the action.
| Button(action: onSubmit) { | ||
| if isProcessing { | ||
| ProgressView() | ||
| .tint(AppColors.primary600) | ||
| } else { |
There was a problem hiding this comment.
This submit button (including the loading ProgressView state) is effectively icon-only and lacks an accessibility label. Add accessibilityLabel (and optionally a loading hint/value) so the action is understandable with VoiceOver.
작업내용