Skip to content

Conversation

@taebin2
Copy link
Contributor

@taebin2 taebin2 commented Jan 12, 2026

🔗 연결된 이슈

Resolved #42

✨ 주요 작업사항

이번 PR의 핵심 변경사항을 알려주세요!

  • ProfileView 제작
  • OtherProfileView 제작
  • 캘린더를 제작하여 ProfileView에 넣어두었습니다.
  • 프로필 편집 관련 View 및 Viewmodel을 구현
  • 팔로워 팔로잉 관련 View 및 Viewmodel을 구현하였습니다.

📸 스크린샷 / 동영상

구현한 화면의 크기를 img width="250"로 설정해서 첨부해주세요!

image image image image

🔍 리뷰어에게 (선택)

코드 리뷰 시 특별히 확인했으면 하는 부분이나, 의견을 묻고 싶은 내용을 적어주세요!

  • 캘린더 기록 하기 부분 아직 미 구현되었는데 (날짜만 되어 있음) 이 부분 기록하기 눌렀을 떄 기록하기로 가고 하면서 로직이 복잡해보이는데 어떤 식으로 하면 되는 지 알려주세요
  • 최애 코디 부분 옷장에 있는 룩북에 하트가 없는데 이 부분도 도와주세요 ㅜㅜ

@taebin2 taebin2 requested review from Funital and Hrepay January 12, 2026 20:49
@taebin2 taebin2 self-assigned this Jan 12, 2026
@taebin2 taebin2 added the 📃 Feat 사용자를 위한 새로운 기능 추가 또는 개선 label Jan 12, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @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

  • 프로필 뷰 및 뷰모델 구현: 사용자 자신의 프로필을 보여주는 ProfileView와 다른 사용자의 프로필을 보여주는 OtherProfileView가 구현되었습니다. 각 뷰는 프로필 정보, 팔로워/팔로잉 수, 최애 코디 섹션, 그리고 캘린더를 포함합니다. 이와 함께 각 뷰의 로직을 처리하는 ProfileViewModelOtherProfileViewModel도 추가되었습니다.
  • 프로필 설정 기능 추가: 사용자가 자신의 프로필 정보를 편집할 수 있는 ProfileSettingView와 해당 뷰의 상태 및 로직을 관리하는 ProfileSettingViewModel이 추가되었습니다. 닉네임 중복 확인, 한 줄 소개 입력, 계정 공개 여부 설정 등의 기능을 제공합니다.
  • 팔로워/팔로잉 목록 및 최애 코디 목록 뷰: 팔로워 및 팔로잉 목록을 표시하는 FollowListView와 최애 코디 목록을 보여주는 FavoriteCodiListView가 구현되었습니다. 이 뷰들은 각각 FollowListViewModel과 함께 동작하며, 팔로우/언팔로우 기능 및 코디 아이템 표시 로직을 포함합니다.
  • 재사용 가능한 UI 컴포넌트 및 유틸리티: 텍스트 입력 필드 UnderlineField, 월별 캘린더 CalendarMonthView, 차단 메뉴 팝업 BlockMenuPopup 등 여러 재사용 가능한 SwiftUI 컴포넌트가 추가되었습니다. 또한, 16진수 색상 초기화(Color+Hex) 및 카드 그림자 스타일(View+Shadow)을 위한 확장 기능이 디자인 시스템에 포함되었습니다.
  • 내비게이션 라우터 업데이트: 새롭게 추가된 프로필 관련 뷰들(favoriteCodiList, settings, followList)에 대한 내비게이션 경로가 AppDestination에 정의되었으며, 전체 화면 모드 및 내비게이션 바 숨김 처리 로직이 업데이트되었습니다.

🧠 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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a 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))를 호출하는 방식입니다. AppDestinationrecordAdd(date: Date) 케이스 추가가 필요할 수 있습니다.
  • 최애 코디 하트: FavoriteCodiListViewshowHeart 파라미터가 이미 있고, ProfileView에서는 true로, OtherProfileView에서는 false로 잘 넘겨주고 계십니다. 실제 '좋아요' 기능을 구현하려면 FavoriteCodiCardView의 하트 버튼에 action을 추가하고, 이를 뷰모델까지 전달하여 API 호출 및 데이터 상태를 업데이트하는 로직을 구현해야 합니다.

몇 가지 코드 개선 제안을 리뷰 댓글로 남겼으니 확인 부탁드립니다. 수고하셨습니다!

Comment on lines +36 to +48
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)
}
}

Choose a reason for hiding this comment

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

high

낙관적 업데이트(Optimistic Update) 시 API 요청 실패에 대한 롤백 처리가 필요합니다. 현재는 UI가 즉시 변경되지만, 네트워크 오류 등으로 서버 상태와 달라질 경우 데이터 불일치가 발생할 수 있습니다. API 호출 실패 시 UI 상태를 원래대로 되돌리는 로직을 추가하는 것을 권장합니다.

Comment on lines +105 to +127
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
}

Choose a reason for hiding this comment

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

medium

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
    }

Comment on lines +129 to +142
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()
}
}

Choose a reason for hiding this comment

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

medium

API 통신 로직은 뷰모델에서 분리하여 별도의 서비스 레이어로 관리하는 것이 테스트와 유지보수에 유리합니다. 현재 DispatchQueue.main.asyncAfter로 구현된 부분을 나중에 실제 API로 교체할 때, NicknameValidationService와 같은 형태로 분리하는 것을 고려해보세요.

Comment on lines +149 to +160
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()
}

Choose a reason for hiding this comment

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

medium

임시 데이터 ForEach(0..<8, id: \.self) 대신 뷰모델의 @Published 프로퍼티와 UI를 바인딩하여 실제 '최애 코디' 목록 데이터를 표시하도록 수정이 필요합니다.

Comment on lines +20 to +24
.foregroundStyle(Color("main1"))

Text("차단하기")
.font(.codive_body2_regular)
.foregroundStyle(Color("Grayscale1"))

Choose a reason for hiding this comment

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

medium

디자인 시스템에 정의된 Color.Codive 타입을 사용해 색상 관리의 일관성을 유지해주세요. Color("main1") 대신 Color.Codive.main1를 사용하는 것이 좋습니다.

Suggested change
.foregroundStyle(Color("main1"))
Text("차단하기")
.font(.codive_body2_regular)
.foregroundStyle(Color("Grayscale1"))
.foregroundStyle(Color.Codive.main1)
Text("차단하기")
.font(.codive_body2_regular)
.foregroundStyle(Color.Codive.grayscale1)

Comment on lines +179 to +184
ForEach(0..<8, id: \.self) { _ in
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.white)
.frame(width: 155, height: 155)
.codiveCardShadow()
}

Choose a reason for hiding this comment

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

medium

임시 데이터 ForEach(0..<8, id: \.self) 대신 뷰모델의 @Published 프로퍼티와 UI를 바인딩하여 실제 데이터를 표시하도록 수정이 필요합니다.

}
}

struct CalendarDayItem: Hashable {

Choose a reason for hiding this comment

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

medium

CalendarDayItemIdentifiable 프로토콜을 따르도록 만들면 ForEach 구문을 ForEach(days)와 같이 더 간결하게 사용할 수 있습니다. let id = UUID() 프로퍼티를 추가하는 것을 고려해보세요.

Suggested change
struct CalendarDayItem: Hashable {
struct CalendarDayItem: Hashable, Identifiable {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📃 Feat 사용자를 위한 새로운 기능 추가 또는 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 프로필 UI 1차 구현하기

2 participants