Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

사실 버튼 그룹은 1개 2개 조합만 있는 거였는데, 혹시 사용하실 일 있으실까봐 바텀시트 예시에 있는 3개짜리 버튼 그룹도 임시로 만들어둔거였습니다 ! 실제로 디자인에서 차이가 있었는지는 미처 고려하지 못했네요 ㅠㅠ 죄송합니다

Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public class BKButtonGroup: UIView {
switch layout {
case .horizontal:
stackView.axis = .horizontal
stackView.distribution = .fillProportionally
stackView.distribution = .fillEqually
stackView.alignment = .fill
buttons.forEach { $0.isFullWidth = false }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright © 2025 Booket. All rights reserved

import BKDesign
import SnapKit
import UIKit

enum BookRegistrationStatus: String {
case before = "읽기 전"
case inProgress = "읽는 중"
case after = "독서 완료"
}

final class BookRegistrationStatusView: UIView {
private let stackView = UIStackView()
private let statuses: [BookRegistrationStatus] = [.before, .inProgress, .after]
private var buttons: [BKButton] = []

var onSelected: (() -> Void)?

private(set) var selectedStatus: BookRegistrationStatus? {
didSet {
onSelected?()
}
}

override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}

required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
}

private extension BookRegistrationStatusView {
func setupView() {
addSubview(stackView)
stackView.axis = .horizontal
stackView.spacing = BKInset.inset2
stackView.distribution = .fillEqually
stackView.alignment = .fill

stackView.snp.makeConstraints {
$0.top.equalToSuperview()
.inset(BKInset.inset5)
$0.bottom.equalToSuperview()
.inset(BKInset.inset3)
$0.leading.trailing.equalToSuperview()
}

statuses.forEach { status in
let button = BKButton.secondary(title: status.rawValue)
button.addAction(UIAction { [weak self] _ in
self?.select(status: status)
}, for: .touchUpInside)

button.snp.makeConstraints {
$0.height.equalTo(LayoutConstants.buttonHeight)
}
buttons.append(button)
stackView.addArrangedSubview(button)
}
}

func select(status: BookRegistrationStatus) {
selectedStatus = status

for (index, button) in buttons.enumerated() {
if statuses[index] == status {
button.style = .tertiary
} else {
button.style = .secondary
}
}
}

enum LayoutConstants {
static let buttonHeight: CGFloat = 52
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ extension SearchView: UICollectionViewDelegate {
eventPublisher.send(.loadNextPage)
}
}

func collectionView(
_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath
) {
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
if case let .result(book) = item {
eventPublisher.send(.upsertBook(book.isbn))
}
}
}

private extension SearchView {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Copyright © 2025 Booket. All rights reserved

import BKCore
import BKDesign
import Combine
import UIKit

enum SearchViewEvent: Equatable {
case search(String)
case loadNextPage
case deleteRecentQuery(String)
case upsertBook(String)
}

final class SearchViewController: BaseViewController<SearchView> {
Expand Down Expand Up @@ -64,6 +67,17 @@ final class SearchViewController: BaseViewController<SearchView> {
self?.viewModel.send(.deleteRecentQuery(query))
}
.store(in: &cancellable)

contentView.eventPublisher
.compactMap { event -> String? in
if case let .upsertBook(isbn) = event { return isbn }
return nil
}
.removeDuplicates()
.sink { [weak self] query in
self?.presentBookRegistration(with: query)
}
.store(in: &cancellable)
}

override func bindState() {
Expand All @@ -85,3 +99,58 @@ final class SearchViewController: BaseViewController<SearchView> {
.store(in: &cancellable)
}
}

private extension SearchViewController {
func presentBookRegistration(with isbn: String) {
let statusView = BookRegistrationStatusView()
let sheet = BKBottomSheetViewController(
title: "등록 옵션",
style: .leadingCloseButton,
suppliedContentStyle: .lower(statusView),
buttonConfiguration: .singleFullButton(
title: "도서 등록"
) { [weak self] in
guard let selected = statusView.selectedStatus else { return }
self?.dismiss(animated: true)
self?.handleRegistrationSelection(status: selected, isbn: isbn)
}
)
sheet.button?.primaryButton?.isEnabled = false
statusView.onSelected = {
sheet.button?.primaryButton?.isEnabled = true
}
sheet.show(from: self, animated: true)
}

func handleRegistrationSelection(status: BookRegistrationStatus, isbn: String) {
Log.debug("선택된 책: \(isbn), 상태: \(status.rawValue)", logger: AppLogger.ui)
// viewModel.send(.upsertBook(isbn))
// TODO: - upsert 성공 시 따라오는 동작으로 변경해야 함
presentNoteSuggestion(with: isbn)
}

func presentNoteSuggestion(with isbn: String) {
// TODO: - 그래픽 디자인 작업 이후 변경
let graphic = BKImage.Icon.bookmark
let graphicView = UIImageView(image: graphic)
let sheet = BKBottomSheetViewController(
title: "도서가 등록되었어요!",
subtitle: "독서 기록을 시작할까요?",
style: .centered,
suppliedContentStyle: .upper(graphicView),
buttonConfiguration: .twoButtonGroup(
leftTitle: "아니요, 나중에요",
rightTitle: "네, 시작할게요!",
leftAction: { [weak self] in
self?.dismiss(animated: true)
},
rightAction: { [weak self] in
Log.debug("선택된 책 \(isbn), 등록 시작", logger: AppLogger.ui)
self?.dismiss(animated: true)
}
)
)

sheet.show(from: self, animated: true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class SearchViewModel: BaseViewModel {
case search(String)
case loadNextPage
case deleteRecentQuery(String)
// case upsertBook(String)
case fetchRecentQueriesSuccessed([String])
case fetchSearchResultSuccessed((books: [Book], totalResults: Int))
case fetchNextPageSuccessed([Book])
Expand All @@ -38,6 +39,7 @@ final class SearchViewModel: BaseViewModel {
case deleteRecentQuery(String)
case searchResult(String)
case loadNextPage
// case upsert(Book)
}

@Published private var state = State()
Expand Down