Skip to content

Conversation

@baekteun
Copy link
Member

💡 배경 및 개요

책 상세 Quote 액션 추가
아 디자인이 굉장히 쉽지 않아요

simulator_screenshot_B6B32866-BE76-445A-8161-391985019AC3 simulator_screenshot_3E3DA9B9-67DB-47F9-BFB3-8F95DE1FF581

@github-actions
Copy link

✅ PR의 Assign 자동 지정을 성공했어요!

@baekteun

Copy link
Contributor

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

Summary of Changes

Hello @baekteun, 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은 책 상세 화면에 인용구(Quote) 관리 기능을 전면적으로 추가합니다. 사용자는 이제 특정 책에 대한 인용구를 생성, 편집, 삭제하고, 모든 인용구를 한눈에 볼 수 있는 전용 화면을 통해 관리할 수 있습니다. 또한, 책 상세 화면의 내비게이션 바가 개선되어 사용자 경험이 향상되었습니다.

Highlights

  • 인용구 관리 인터랙터 추가: CreateQuoteInteractor, DeleteQuoteInteractor, UpdateQuoteInteractor와 같은 새로운 CoreData 인터랙터가 추가되어 인용구의 생성, 삭제, 업데이트 작업을 효율적으로 처리합니다.
  • 모든 인용구 보기 화면 구현: 책에 연결된 모든 인용구를 목록 형태로 보여주는 전용 AllQuotesViewController와 QuoteTableViewCell이 새로 추가되었습니다. 이 화면에서는 스와이프 액션을 통해 인용구를 편집하거나 삭제할 수 있습니다.
  • 인용구 편집기 도입: 인용구의 내용과 페이지 번호를 입력하고 편집할 수 있는 QuoteEditorViewController와 QuoteEditorViewModel이 구현되어 사용자 친화적인 인용구 관리 환경을 제공합니다.
  • 책 상세 화면 기능 통합: BookDetailViewController가 업데이트되어 인용구 추가, 컨텍스트 메뉴 및 스와이프 액션을 통한 인용구 편집 및 삭제 기능이 통합되었습니다. 또한, 내비게이션 바가 재설계되어 닫기 및 추가 옵션 메뉴가 포함되었습니다.
  • 내비게이션 흐름 개선: HomeViewController에서 BookDetailViewController를 UINavigationController 내에 표시하도록 변경하여, AllQuotesViewController나 QuoteEditorViewController와 같은 후속 화면으로의 내비게이션이 더욱 자연스러워졌습니다.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

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 issue 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
Contributor

@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은 책 상세 화면에 인용구(Quote)를 추가, 수정, 삭제하는 기능을 구현한 것으로 보입니다. 전반적으로 새로운 기능이 잘 구현되었으며, 특히 CoreData와 상호작용하는 Interactor 패턴과 현대적인 UI 구현 방식(Diffable DataSource, Observation)을 사용한 점이 좋습니다. 다만, 몇 가지 잠재적인 런타임 오류를 방지하고 코드의 일관성과 유지보수성을 높이기 위한 개선점을 제안합니다.

quoteEntity.content = request.content
quoteEntity.page = request.page

let book = context.object(with: request.bookObjectID) as! BookEntity
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

강제 타입 캐스팅(as!)은 request.bookObjectID에 해당하는 객체가 BookEntity가 아닐 경우 런타임에 크래시를 유발할 수 있습니다. 보다 안전한 앱을 위해 guard let과 옵셔널 캐스팅(as?)을 사용하여 이 경우를 처리하고, 실패 시 에러를 던지는 것을 권장합니다.

Suggested change
let book = context.object(with: request.bookObjectID) as! BookEntity
guard let book = context.object(with: request.bookObjectID) as? BookEntity else { throw NSError(domain: "CreateQuoteInteractor", code: 1, userInfo: [NSLocalizedDescriptionKey: "Book not found"]) }


public func callAsFunction(request: Request) async throws {
try await contextManager.performAndSave { @Sendable context in
let quote = context.object(with: request.quoteObjectID) as! QuoteEntity
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

강제 타입 캐스팅(as!)은 request.quoteObjectID에 해당하는 객체가 QuoteEntity가 아닐 경우 런타임에 크래시를 유발할 수 있습니다. guard let과 옵셔널 캐스팅(as?)을 사용하여 안전하게 처리하는 것이 좋습니다.

Suggested change
let quote = context.object(with: request.quoteObjectID) as! QuoteEntity
guard let quote = context.object(with: request.quoteObjectID) as? QuoteEntity else { throw NSError(domain: "UpdateQuoteInteractor", code: 1, userInfo: [NSLocalizedDescriptionKey: "Quote not found"]) }

Comment on lines +59 to +61
return !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
page >= 1 &&
page <= totalPages
Copy link
Contributor

Choose a reason for hiding this comment

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

high

isValid 로직에 작은 버그가 있습니다. 책의 전체 페이지 수(totalPages)가 0일 경우, page <= totalPages 조건은 항상 false가 되어 인용구를 저장할 수 없게 됩니다. 전체 페이지 수가 설정되지 않은 책의 경우에도 인용구를 저장할 수 있도록 유효성 검사 로직을 수정해야 합니다.

Suggested change
return !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
page >= 1 &&
page <= totalPages
return !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
page >= 1 &&
(totalPages > 0 ? page <= totalPages : true)

let book = context.object(with: request.bookObjectID) as! BookEntity
quoteEntity.book = book

context.insert(quoteEntity)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

QuoteEntity(context: context)를 통해 NSManagedObject를 생성하면 이미 해당 context에 삽입(insert)됩니다. 따라서 context.insert(quoteEntity) 라인은 불필요하며, 제거해도 기능에 영향이 없습니다.

Comment on lines +55 to +70
@objc private func addQuoteTapped() {
guard let book = getBook() else { return }

let quoteViewModel = QuoteEditorViewModel(book: book, editMode: .create)
let quoteViewController = QuoteEditorViewController(viewModel: quoteViewModel) { [weak self] in
// Refresh will happen automatically via NSFetchedResultsController
}
let navigationController = UINavigationController(rootViewController: quoteViewController)

if let sheet = navigationController.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
}

present(navigationController, animated: true)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

QuoteEditorViewController를 생성하고 표시하는 코드가 addQuoteTapped, tableView(_:didSelectRowAt:), tableView(_:trailingSwipeActionsConfigurationForRowAt:) 등 여러 곳에서 중복되고 있습니다. 코드 중복을 줄이고 유지보수성을 높이기 위해 이 로직을 별도의 private helper 메서드로 추출하는 것을 고려해 보세요. 예를 들어, private func presentQuoteEditor(for editMode: QuoteEditorViewModel.EditMode)와 같은 메서드를 만들 수 있습니다.

Copy link

@leehyeonbin leehyeonbin left a comment

Choose a reason for hiding this comment

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

어잇 디자인이 아쉽긴하네요

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants