-
Notifications
You must be signed in to change notification settings - Fork 0
[#42] Profile View, Viewmodel 구현 #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Summary of ChangesHello @taebin2, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 사용자 프로필 관련 기능을 대폭 확장하여, 사용자 자신의 프로필을 확인하고 편집할 수 있는 기능과 다른 사용자의 프로필을 탐색하고 상호작용할 수 있는 기능을 도입합니다. 또한, 팔로워/팔로잉 목록 및 최애 코디 목록과 같은 관련 정보들을 체계적으로 제공하며, 이를 통해 사용자 간의 소셜 인터랙션을 강화하고 개인화된 경험을 제공하는 데 중점을 둡니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. 코드의 흐름, 새 프로필 창조, 뷰와 모델 춤추듯 연결되네. 팔로우, 설정, 코디의 향연, 사용자 경험, 빛나는 별처럼. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
이번 PR은 프로필 관련 화면들과 뷰모델을 구현하는 큰 작업이네요. 전반적으로 SwiftUI와 MVVM 패턴을 잘 활용하여 구조를 잘 잡으셨습니다. MyProfile, OtherProfile, ProfileSetting 등 기능별로 뷰와 뷰모델이 잘 분리되어 있고, 재사용 가능한 컴포넌트(UnderlineField, CalendarMonthView 등)를 만들어 활용한 점이 좋습니다.
PR 설명에 남겨주신 질문에 대해 몇 가지 제안을 드립니다.
- 캘린더 기록:
CalendarMonthView에서 날짜를 탭하면ProfileViewModel의@Published var selectedDate가 업데이트됩니다. 이 변경을 감지하여navigationRouter를 사용해 기록 화면으로 이동시키는 로직을ProfileViewModel에 추가하면 됩니다. 예를 들어,onDateTapped(date:)와 같은 메소드를 만들고 내부에서navigationRouter.navigate(to: .recordAdd(date: date))를 호출하는 방식입니다.AppDestination에recordAdd(date: Date)케이스 추가가 필요할 수 있습니다. - 최애 코디 하트:
FavoriteCodiListView에showHeart파라미터가 이미 있고,ProfileView에서는true로,OtherProfileView에서는false로 잘 넘겨주고 계십니다. 실제 '좋아요' 기능을 구현하려면FavoriteCodiCardView의 하트 버튼에action을 추가하고, 이를 뷰모델까지 전달하여 API 호출 및 데이터 상태를 업데이트하는 로직을 구현해야 합니다.
몇 가지 코드 개선 제안을 리뷰 댓글로 남겼으니 확인 부탁드립니다. 수고하셨습니다!
| func onTapButton(userId: UserID) { | ||
| guard let idx = items.firstIndex(where: { $0.id == userId }) else { return } | ||
|
|
||
| // 공통: 현재 버튼은 follow/following 토글 | ||
| // 실제 구현: API 성공 후 반영 | ||
| items[idx].isFollowing.toggle() | ||
|
|
||
| // mode가 followings인 경우: | ||
| // "팔로잉" 목록에서 언팔로우하면 리스트에서 제거 | ||
| if mode == .followings, items[idx].isFollowing == false { | ||
| items.remove(at: idx) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private func updateCanComplete() { | ||
| // 닉네임은 필수이므로 비어있으면 비활성화 | ||
| if nickname.isEmpty { | ||
| canComplete = false | ||
| return | ||
| } | ||
| // 닉네임 길이 에러가 있으면 비활성화 (중복 에러는 제외) | ||
| if nickname.count > nicknameMaxCount { | ||
| canComplete = false | ||
| return | ||
| } | ||
| // 닉네임 중복확인이 완료되지 않았으면 비활성화 | ||
| if nicknameCheckStatus != .available { | ||
| canComplete = false | ||
| return | ||
| } | ||
| // 닉네임 중복확인이 완료된 상태에서, 한줄소개가 20자 초과면 비활성화 | ||
| if intro.count > introMaxCount { | ||
| canComplete = false | ||
| return | ||
| } | ||
| canComplete = true | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateCanComplete 함수의 로직이 여러 if 문으로 분기되어 있어 복잡해 보입니다. 각 조건을 boolean 변수로 추출하여 조합하는 방식으로 리팩토링하면 가독성을 높일 수 있습니다.
private func updateCanComplete() {
let isNicknameValid = !nickname.isEmpty && nickname.count <= nicknameMaxCount
let isNicknameChecked = nicknameCheckStatus == .available
let isIntroValid = intro.count <= introMaxCount
canComplete = isNicknameValid && isNicknameChecked && isIntroValid
}| func runNicknameDuplicateCheck() { | ||
| nicknameCheckStatus = .checking | ||
|
|
||
| DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { | ||
| let lowered = self.nickname.lowercased() | ||
| if lowered == "trendbox" || lowered == "ckj11" { | ||
| self.nicknameCheckStatus = .duplicated | ||
| } else { | ||
| self.nicknameCheckStatus = .available | ||
| } | ||
| // 명시적으로 업데이트 호출 (didSet이 호출되지만 확실하게) | ||
| self.updateCanComplete() | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ForEach(0..<8, id: \.self) { _ in | ||
| RoundedRectangle(cornerRadius: 16, style: .continuous) | ||
| .fill(Color.white) | ||
| .frame(width: 160, height: 160) | ||
| .overlay(alignment: .topTrailing) { | ||
| Image("heart_on") | ||
| .frame(width: 15, height: 18) | ||
| .foregroundStyle(Color.Codive.point1) | ||
| .padding(14) | ||
| } | ||
| .codiveCardShadow() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| .foregroundStyle(Color("main1")) | ||
|
|
||
| Text("차단하기") | ||
| .font(.codive_body2_regular) | ||
| .foregroundStyle(Color("Grayscale1")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
디자인 시스템에 정의된 Color.Codive 타입을 사용해 색상 관리의 일관성을 유지해주세요. Color("main1") 대신 Color.Codive.main1를 사용하는 것이 좋습니다.
| .foregroundStyle(Color("main1")) | |
| Text("차단하기") | |
| .font(.codive_body2_regular) | |
| .foregroundStyle(Color("Grayscale1")) | |
| .foregroundStyle(Color.Codive.main1) | |
| Text("차단하기") | |
| .font(.codive_body2_regular) | |
| .foregroundStyle(Color.Codive.grayscale1) |
| ForEach(0..<8, id: \.self) { _ in | ||
| RoundedRectangle(cornerRadius: 16, style: .continuous) | ||
| .fill(Color.white) | ||
| .frame(width: 155, height: 155) | ||
| .codiveCardShadow() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | ||
| } | ||
|
|
||
| struct CalendarDayItem: Hashable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔗 연결된 이슈
Resolved #42
✨ 주요 작업사항
📸 스크린샷 / 동영상
🔍 리뷰어에게 (선택)