Skip to content
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

[WEAV-154] 프로필 이미지 Reset 기능 #57

Merged
merged 3 commits into from
Dec 7, 2024
Merged

Conversation

jisu15-kim
Copy link
Member

@jisu15-kim jisu15-kim commented Dec 7, 2024

구현사항

  • 프로필 이미지 초기화 기능 구현
  • delete profile image API 연결

Summary by CodeRabbit

릴리스 노트

  • 신규 기능

    • 프로필 이미지 삭제 기능 추가: 사용자가 특정 프로필 이미지를 삭제할 수 있는 기능이 추가되었습니다.
    • 프로필 정보 새로 고침 기능: 프로필 패널이 표시될 때 사용자 정보를 자동으로 새로 고치는 기능이 추가되었습니다.
    • 프로필 이미지 로딩 개선: 비동기적으로 프로필 이미지를 로드하고 로딩 중에는 진행 표시기를 표시합니다.
  • 버그 수정

    • 프로필 이미지 URL 및 ID의 동적 할당을 통해 사용자 정보 모델의 유연성을 개선했습니다.
  • 문서화

    • API 문서에 프로필 이미지 관련 변경 사항이 반영되었습니다.

@jisu15-kim jisu15-kim added the Feature 기능 label Dec 7, 2024
@jisu15-kim jisu15-kim self-assigned this Dec 7, 2024
Copy link
Contributor

coderabbitai bot commented Dec 7, 2024

Walkthrough

이 변경 사항은 Client 구조체에 deleteProfileImage라는 새로운 공개 메서드를 추가하여 특정 프로필 이미지를 삭제할 수 있도록 합니다. 이 메서드는 HTTP DELETE 요청을 처리하며, 응답 코드에 따라 적절한 결과를 반환합니다. 또한, APIProtocol에 새로운 메서드가 추가되고, GetMyUserInfoResponse 구조체에 프로필 이미지 배열을 포함하는 새로운 속성이 추가됩니다. 이 외에도 여러 서비스 및 뷰 파일에서 프로필 이미지 관리 기능이 개선되었습니다.

Changes

파일 경로 변경 요약
OpenApiGenerator/Sources/OpenapiGenerated/Client.swift Client 구조체에 deleteProfileImage 메서드 추가.
OpenApiGenerator/Sources/OpenapiGenerated/Types.swift APIProtocoldeleteProfileImage 메서드 추가, GetMyUserInfoResponseprofileImages 속성 추가, ProfileImage 구조체 정의.
OpenApiGenerator/Sources/openapi-generator-cli/3days-oas 하위 프로젝트 커밋 참조 업데이트.
Projects/Core/NetworkKit/Sources/ProfileService/ProfileService.swift ProfileServiceProtocolrequestResetProfileImage 메서드 추가 및 requestCompleteCallback 메서드 수정.
Projects/Core/NetworkKit/Sources/ProfileService/ProfileServiceMock.swift ProfileServiceMock 클래스에 requestResetProfileImage 메서드 추가.
Projects/Features/Home/Sources/Profile/ProfileView.swift ProfileView에서 사용자 정보 새로 고침 기능 추가.
Projects/Features/Home/Sources/ProfilePannel/ProfilePanelView.swift ProfilePanelView에서 이미지 로딩 방식 및 refreshHandler 속성 추가.
Projects/Model/Model/Sources/Auth/Domain/UserInfo.swift UserInfoUserInfoProfile 구조체 수정, 프로필 이미지 처리 개선.
Tuist/ProjectDescriptionHelpers/Dependency+extensions.swift ExternalDependency 열거형에서 nuke 값을 NukeUI로 변경.

Possibly related PRs

🐇 변화의 날이 밝았네,
프로필 삭제, 새로움이 가득해.
이미지를 지우고, 새로 고침,
사용자 경험이 더욱 빛나지.
함께 뛰어놀자, 기쁨의 땅으로,
프로필과 함께, 행복을 찾아가자! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
Projects/Core/NetworkKit/Sources/ProfileService/ProfileService.swift (1)

153-153: PNG 확장자 하드코딩 개선 필요

이미지 확장자가 하드코딩되어 있어, 향후 다른 이미지 형식 지원 시 유연성이 떨어질 수 있습니다.

다음과 같은 개선을 제안드립니다:

-        let result = try await client.completeProfileImageUpload(body: .json(.init(imageId: imageId, _extension: .PNG)))
+        let result = try await client.completeProfileImageUpload(body: .json(.init(imageId: imageId, _extension: imageExtension)))

이미지 확장자를 파라미터로 받거나, 이미지 데이터로부터 확장자를 추출하는 방식으로 개선하는 것을 고려해보세요.

Projects/Model/Model/Sources/Auth/Domain/UserInfo.swift (1)

53-56: 옵셔널 체이닝을 사용하여 코드를 개선하세요

현재 구현은 작동하지만, 옵셔널 체이닝을 사용하면 더 간결하고 안전하게 작성할 수 있습니다.

-if let profileImage = dto.profileImages?.first {
-    self.profile.profileImageUrl = URL(string: profileImage.url)
-    self.profile.profileImageId = profileImage.id
-}
+dto.profileImages?.first.map { profileImage in
+    self.profile.profileImageUrl = URL(string: profileImage.url)
+    self.profile.profileImageId = profileImage.id
+}
Projects/Features/Home/Sources/Profile/ProfileView.swift (1)

66-70: 리프레시 핸들러 구현을 단순화하세요

현재 구현도 작동하지만, 더 간단하게 작성할 수 있습니다.

-) {
-    Task {
-        await intent.refreshUserInfo()
-    }
-}
+) { Task { await intent.refreshUserInfo() } }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5933517 and 1c3cbac.

📒 Files selected for processing (9)
  • OpenApiGenerator/Sources/OpenapiGenerated/Client.swift (1 hunks)
  • OpenApiGenerator/Sources/OpenapiGenerated/Types.swift (8 hunks)
  • OpenApiGenerator/Sources/openapi-generator-cli/3days-oas (1 hunks)
  • Projects/Core/NetworkKit/Sources/ProfileService/ProfileService.swift (3 hunks)
  • Projects/Core/NetworkKit/Sources/ProfileService/ProfileServiceMock.swift (1 hunks)
  • Projects/Features/Home/Sources/Profile/ProfileView.swift (1 hunks)
  • Projects/Features/Home/Sources/ProfilePannel/ProfilePanelView.swift (2 hunks)
  • Projects/Model/Model/Sources/Auth/Domain/UserInfo.swift (4 hunks)
  • Tuist/ProjectDescriptionHelpers/Dependency+extensions.swift (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • OpenApiGenerator/Sources/openapi-generator-cli/3days-oas
🔇 Additional comments (9)
Tuist/ProjectDescriptionHelpers/Dependency+extensions.swift (1)

11-11: Nuke에서 NukeUI로의 마이그레이션 검증 필요

Nuke에서 NukeUI로의 변경이 감지되었습니다. 이는 SwiftUI 통합을 위한 적절한 변경으로 보이나, 다음 사항들의 검증이 필요합니다:

  • 기존 Nuke 관련 import 문의 업데이트
  • NukeUI 관련 새로운 기능의 올바른 사용
✅ Verification successful

Nuke에서 NukeUI로의 마이그레이션이 올바르게 이루어짐

코드베이스 검토 결과, 다음과 같은 사항들이 확인되었습니다:

  • 기존 Nuke import 문이 없으며, NukeUI만 사용 중
  • ProfilePanelView.swift에서 LazyImage 컴포넌트를 통해 NukeUI의 기능을 적절히 활용
  • Package.swift에서 Nuke 패키지를 정상적으로 참조

마이그레이션이 정상적으로 완료되어 추가 검증이 필요하지 않습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Nuke 관련 import 문 검색
echo "기존 Nuke import 문 검색:"
rg "import Nuke$" 

echo "\nNukeUI import 문 검색:"
rg "import NukeUI"

Length of output: 226


Script:

#!/bin/bash
# Nuke 관련 코드 사용 패턴 검색
echo "Nuke 관련 코드 사용:"
rg "Nuke\." -A 2

echo "\nNukeUI 관련 코드 사용:"
rg "NukeUI\." -A 2

echo "\nProfilePanelView.swift 파일 내용 확인:"
cat "Projects/Features/Home/Sources/ProfilePannel/ProfilePanelView.swift"

Length of output: 13225

Projects/Core/NetworkKit/Sources/ProfileService/ProfileServiceMock.swift (1)

42-45: Mock 구현이 적절히 완료되었습니다

프로토콜 요구사항에 맞게 구현되었으며, 성공 메시지가 한글로 적절히 출력되도록 처리되었습니다.

Projects/Core/NetworkKit/Sources/ProfileService/ProfileService.swift (1)

28-29: 프로토콜 확장이 명확하게 정의되었습니다

비동기 처리와 에러 핸들링이 포함된 프로토콜 정의가 적절합니다.

Projects/Model/Model/Sources/Auth/Domain/UserInfo.swift (2)

94-95: 구현이 적절합니다

프로필 이미지 URL과 ID를 옵셔널로 선언한 것이 적절합니다. 이는 프로필 이미지가 필수가 아닌 상황을 잘 반영합니다.


118-119: 초기화 구현이 적절합니다

새로운 프로필 이미지 관련 매개변수들이 기본값을 nil로 가지도록 구현된 것이 적절합니다.

Also applies to: 129-129

OpenApiGenerator/Sources/OpenapiGenerated/Types.swift (3)

70-76: 프로토콜 메서드가 올바르게 정의되었습니다.

프로필 이미지 삭제를 위한 프로토콜 메서드가 명확한 문서화와 함께 잘 정의되어 있습니다.


1394-1426: 프로필 이미지 모델이 적절하게 정의되었습니다.

ProfileImage 구조체가 필요한 모든 속성과 프로토콜을 포함하여 잘 정의되어 있습니다.


629-632: 응답 모델 업데이트가 완벽하게 구현되었습니다.

GetMyUserInfoResponse에 프로필 이미지 배열이 옵셔널 타입으로 적절하게 추가되었습니다.

OpenApiGenerator/Sources/OpenapiGenerated/Client.swift (1)

1038-1144: 클라이언트 구현이 완벽하게 작성되었습니다.

deleteProfileImage 메서드가 다음 사항들을 포함하여 적절하게 구현되었습니다:

  • 올바른 HTTP DELETE 메서드 사용
  • 모든 상태 코드에 대한 체계적인 에러 처리
  • 적절한 시리얼라이제이션/디시리얼라이제이션

Comment on lines +108 to +112
public func requestResetProfileImage(imageId: String) async throws {
let result = try await client.deleteProfileImage(path: .init(imageId: imageId))
_ = try result.noContent
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리 개선 필요

이미지 삭제 요청 시 발생할 수 있는 구체적인 에러 케이스들에 대한 처리가 필요합니다.

다음과 같은 개선을 제안드립니다:

 public func requestResetProfileImage(imageId: String) async throws {
+    guard !imageId.isEmpty else {
+        throw ProfileServiceError.invalidImageId
+    }
     let result = try await client.deleteProfileImage(path: .init(imageId: imageId))
-    _ = try result.noContent
+    do {
+        _ = try result.noContent
+    } catch {
+        throw ProfileServiceError.deletionFailed(error)
+    }
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +82 to +90
Button("기본 이미지 적용") {
Task {
if let profileImageId = profile.profileImageId {
try await ProfileService.shared.requestResetProfileImage(
imageId: profileImageId
)
_ = try await AppCoordinator.shared.refreshMyUserInfo()
refreshHandler()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로딩 상태와 에러 처리를 추가하세요

프로필 이미지 초기화 기능이 구현되어 있지만, 사용자 피드백이 부족합니다.

 Button("기본 이미지 적용") {
+    @State private var isResetting = false
     Task {
         do {
+            isResetting = true
             if let profileImageId = profile.profileImageId {
                 try await ProfileService.shared.requestResetProfileImage(
                     imageId: profileImageId
                 )
                 _ = try await AppCoordinator.shared.refreshMyUserInfo()
                 refreshHandler()
             }
+        } catch {
+            ToastHelper.showErrorMessage(
+                "프로필 이미지 초기화에 실패했습니다."
+            )
+        } finally {
+            isResetting = false
         }
     }
 }
+.disabled(isResetting)

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +42 to +72
ZStack(alignment: .center) {
if let profileImageUrl = profile.profileImageUrl {
LazyImage(url: profileImageUrl) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 96, height: 96, alignment: .center)
.clipped()
.cornerRadius(20, corners: .allCorners)
.cornerRadius(48, corners: .bottomRight)
} else {
ProgressView()
}
}
} else {
DesignCore.Images.profileDefault.image
.frame(width: 102, height: 102)
.cornerRadius(20, corners: .allCorners)
}
DesignCore.Images.cameraCircleFill.image
DesignCore.Images.profileBorder.image
.resizable()
.frame(width: 36, height: 36)
.offset(x: 6, y: -6)
.onTapGesture {
isShowPhotoSheet = true
}
.confirmationDialog(
"프로필 사진 설정",
isPresented: $isShowPhotoSheet,
actions: {
Button("앨범에서 사진 선택") {
isShowPhotoPicker = true
}
Button("기본 이미지 적용") {
// default image
}
Button("취소", role: .cancel) {}
},
message: {
Text("프로필 사진 설정")
.frame(width: 102, height: 102)

ZStack(alignment: .topTrailing) {
Color.clear
DesignCore.Images.cameraCircleFill.image
.resizable()
.frame(width: 36, height: 36)
.offset(x: 6, y: -6)
.onTapGesture {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 로딩 실패 처리를 추가하세요

LazyImage를 사용한 비동기 이미지 로딩 구현은 좋습니다만, 이미지 로딩 실패 시의 처리가 누락되어 있습니다.

 LazyImage(url: profileImageUrl) { state in
     if let image = state.image {
         image
             .resizable()
             .aspectRatio(contentMode: .fill)
             .frame(width: 96, height: 96, alignment: .center)
             .clipped()
             .cornerRadius(20, corners: .allCorners)
             .cornerRadius(48, corners: .bottomRight)
+    } else if state.error != nil {
+        DesignCore.Images.profileDefault.image
+            .frame(width: 102, height: 102)
+            .cornerRadius(20, corners: .allCorners)
     } else {
         ProgressView()
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ZStack(alignment: .center) {
if let profileImageUrl = profile.profileImageUrl {
LazyImage(url: profileImageUrl) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 96, height: 96, alignment: .center)
.clipped()
.cornerRadius(20, corners: .allCorners)
.cornerRadius(48, corners: .bottomRight)
} else {
ProgressView()
}
}
} else {
DesignCore.Images.profileDefault.image
.frame(width: 102, height: 102)
.cornerRadius(20, corners: .allCorners)
}
DesignCore.Images.cameraCircleFill.image
DesignCore.Images.profileBorder.image
.resizable()
.frame(width: 36, height: 36)
.offset(x: 6, y: -6)
.onTapGesture {
isShowPhotoSheet = true
}
.confirmationDialog(
"프로필 사진 설정",
isPresented: $isShowPhotoSheet,
actions: {
Button("앨범에서 사진 선택") {
isShowPhotoPicker = true
}
Button("기본 이미지 적용") {
// default image
}
Button("취소", role: .cancel) {}
},
message: {
Text("프로필 사진 설정")
.frame(width: 102, height: 102)
ZStack(alignment: .topTrailing) {
Color.clear
DesignCore.Images.cameraCircleFill.image
.resizable()
.frame(width: 36, height: 36)
.offset(x: 6, y: -6)
.onTapGesture {
ZStack(alignment: .center) {
if let profileImageUrl = profile.profileImageUrl {
LazyImage(url: profileImageUrl) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 96, height: 96, alignment: .center)
.clipped()
.cornerRadius(20, corners: .allCorners)
.cornerRadius(48, corners: .bottomRight)
} else if state.error != nil {
DesignCore.Images.profileDefault.image
.frame(width: 102, height: 102)
.cornerRadius(20, corners: .allCorners)
} else {
ProgressView()
}
}
} else {
DesignCore.Images.profileDefault.image
.frame(width: 102, height: 102)
.cornerRadius(20, corners: .allCorners)
}
DesignCore.Images.profileBorder.image
.resizable()
.frame(width: 102, height: 102)
ZStack(alignment: .topTrailing) {
Color.clear
DesignCore.Images.cameraCircleFill.image
.resizable()
.frame(width: 36, height: 36)
.offset(x: 6, y: -6)
.onTapGesture {

@jisu15-kim jisu15-kim merged commit 7b4c4bf into develop Dec 7, 2024
2 checks passed
@jisu15-kim jisu15-kim deleted the feature/WEAV-154 branch December 7, 2024 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature 기능
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant