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

[NT-1371] Add-on Reward Selection and Continue Button #1252

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import Prelude
import UIKit

final class RewardAddOnSelectionDataSource: ValueCellDataSource {
func load(_ values: [RewardAddOnCellData]) {
func load(_ values: [RewardAddOnCardViewData]) {
self.clearValues()

self.set(
values: values,
cellClass: RewardAddOnCell.self,
Expand All @@ -15,7 +17,7 @@ final class RewardAddOnSelectionDataSource: ValueCellDataSource {

override func configureCell(tableCell cell: UITableViewCell, withValue value: Any) {
switch (cell, value) {
case let (cell as RewardAddOnCell, value as RewardAddOnCellData):
case let (cell as RewardAddOnCell, value as RewardAddOnCardViewData):
cell.configureWith(value: value)
default:
assertionFailure("Unrecognized (cell, value) combo.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ final class RewardsAddOnSelectionDataSourceTests: XCTestCase {

func testLoadRewards() {
let project = Project.cosmicSurgery
let rewardsData = project.rewards.map { reward -> RewardAddOnCellData in
.init(project: project, reward: reward, shippingRule: .template)
let rewardsData = project.rewards.map { reward -> RewardAddOnCardViewData in
.init(project: project, reward: reward, context: .pledge, shippingRule: .template)
}

self.dataSource.load(rewardsData)
Expand Down
7 changes: 3 additions & 4 deletions Kickstarter-iOS/Views/Cells/RewardAddOnCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ final class RewardAddOnCell: UITableViewCell, ValueCell {
// MARK: - Properties

private lazy var containerView: UIView = { UIView(frame: .zero) }()
private lazy var rewardAddOnCardView: RewardAddOnCardView = { RewardAddOnCardView(frame: .zero) }()
internal lazy var rewardAddOnCardView: RewardAddOnCardView = { RewardAddOnCardView(frame: .zero) }()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
Expand Down Expand Up @@ -49,10 +49,9 @@ final class RewardAddOnCell: UITableViewCell, ValueCell {
|> containerViewStyle
}

internal func configureWith(value: RewardAddOnCellData) {
self.rewardAddOnCardView.configure(with: (value.project, value.reward, .pledge, value.shippingRule))
internal func configureWith(value: RewardAddOnCardViewData) {
self.rewardAddOnCardView.configure(with: value)

self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,47 @@ import Library
import Prelude
import UIKit

final class RewardAddOnSelectionViewController: UITableViewController {
final class RewardAddOnSelectionViewController: UIViewController {
// MARK: - Properties

private let dataSource = RewardAddOnSelectionDataSource()

public lazy var headerLabel: UILabel = UILabel(frame: .zero)
public lazy var headerView: UIView = UIView(frame: .zero)
public lazy var headerRootStackView: UIStackView = UIStackView(frame: .zero)
public lazy var pledgeShippingLocationViewController: PledgeShippingLocationViewController = {
private lazy var headerLabel: UILabel = UILabel(frame: .zero)
private lazy var headerView: UIView = UIView(frame: .zero)
private lazy var headerRootStackView: UIStackView = UIStackView(frame: .zero)
private lazy var pledgeShippingLocationViewController: PledgeShippingLocationViewController = {
PledgeShippingLocationViewController.instantiate()
|> \.delegate .~ self
}()

private lazy var continueCTAView: RewardAddOnSelectionContinueCTAView = {
RewardAddOnSelectionContinueCTAView(frame: .zero)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}()

private lazy var tableView: UITableView = {
let tv = UITableView(frame: .zero)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
|> \.alwaysBounceVertical .~ true
|> \.dataSource .~ self.dataSource
return tv
|> \.delegate .~ self
|> \.rowHeight .~ UITableView.automaticDimension
|> \.tableFooterView .~ UIView(frame: .zero)
|> \.tableHeaderView .~ self.headerView
}()

private let viewModel: RewardAddOnSelectionViewModelType = RewardAddOnSelectionViewModel()

// MARK: - Lifecycle

override func viewDidLoad() {
super.viewDidLoad()

self.configureViews()
self.configureHeaderView()
self.setupConstraints()

self.tableView.dataSource = self.dataSource
self.tableView.tableHeaderView = self.headerView
self.tableView.tableFooterView = UIView(frame: .zero)
self.tableView.registerCellClass(RewardAddOnCell.self)

self.viewModel.inputs.viewDidLoad()
Expand All @@ -48,6 +64,14 @@ final class RewardAddOnSelectionViewController: UITableViewController {

// MARK: - Configuration

private func configureViews() {
_ = (self.tableView, self.view)
|> ksr_addSubviewToParent()

_ = (self.continueCTAView, self.view)
|> ksr_addSubviewToParent()
}

private func configureHeaderView() {
_ = (self.headerRootStackView, self.headerView)
|> ksr_addSubviewToParent()
Expand All @@ -60,6 +84,18 @@ final class RewardAddOnSelectionViewController: UITableViewController {
self.pledgeShippingLocationViewController.didMove(toParent: self)
}

private func setupConstraints() {
NSLayoutConstraint.activate([
self.tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
self.tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
self.tableView.bottomAnchor.constraint(equalTo: self.continueCTAView.topAnchor),
self.continueCTAView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
self.continueCTAView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
self.continueCTAView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}

// MARK: - Styles

override func bindStyles() {
Expand All @@ -68,6 +104,9 @@ final class RewardAddOnSelectionViewController: UITableViewController {
_ = self.view
|> checkoutBackgroundStyle

_ = self.tableView
|> checkoutBackgroundStyle

_ = self.headerLabel
|> checkoutBackgroundStyle

Expand All @@ -94,18 +133,30 @@ final class RewardAddOnSelectionViewController: UITableViewController {
self.pledgeShippingLocationViewController.view.rac.hidden
= self.viewModel.outputs.shippingLocationViewIsHidden

self.viewModel.outputs.configureContinueCTAViewWithData
.observeForUI()
.observeValues { [weak self] data in
self?.continueCTAView.configure(with: data)
}

self.viewModel.outputs.configurePledgeShippingLocationViewControllerWithData
.observeForUI()
.observeValues { [weak self] data in
self?.pledgeShippingLocationViewController.configureWith(value: data)
}

self.viewModel.outputs.loadAddOnRewardsIntoDataSource
self.viewModel.outputs.loadAddOnRewardsIntoDataSourceAndReloadTableView
.observeForUI()
.observeValues { [weak self] rewards in
self?.dataSource.load(rewards)
self?.tableView.reloadData()
}

self.viewModel.outputs.loadAddOnRewardsIntoDataSource
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Split these out into a signal that updates the data source and reloads the table view, and another that only updates the data source. This is so that the table view is not reloaded each time we change a selection on one of the cells but also to maintain data integrity for when we recycle cells as we scroll the view.

.observeForUI()
.observeValues { [weak self] rewards in
self?.dataSource.load(rewards)
}
}
}

Expand All @@ -123,3 +174,29 @@ extension RewardAddOnSelectionViewController: PledgeShippingLocationViewControll
self.tableView.ksr_sizeHeaderFooterViewsToFit()
}
}

// MARK: - RewardAddOnCardViewDelegate

extension RewardAddOnSelectionViewController: RewardAddOnCardViewDelegate {
func rewardAddOnCardView(
_: RewardAddOnCardView,
didSelectQuantity quantity: Int,
rewardId: Int
) {
self.viewModel.inputs.rewardAddOnCardViewDidSelectQuantity(quantity: quantity, rewardId: rewardId)
}
}

// MARK: - UITableViewDelegate

extension RewardAddOnSelectionViewController: UITableViewDelegate {
func tableView(
_: UITableView,
willDisplay cell: UITableViewCell,
forRowAt _: IndexPath
) {
guard let cell = cell as? RewardAddOnCell else { return }

cell.rewardAddOnCardView.delegate = self
}
}
13 changes: 8 additions & 5 deletions Kickstarter-iOS/Views/RewardAddOnCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import Prelude
import ReactiveSwift

protocol RewardAddOnCardViewDelegate: AnyObject {
func rewardAddOnCardView(_ rewardAddOnCardView: RewardAddOnCardView, didTapWithRewardId rewardId: Int)
func rewardAddOnCardView(
_ cardView: RewardAddOnCardView,
didSelectQuantity quantity: Int,
rewardId: Int
)
}

public final class RewardAddOnCardView: UIView {
Expand Down Expand Up @@ -160,12 +164,11 @@ public final class RewardAddOnCardView: UIView {
self.stepper.rac.maximumValue = self.viewModel.outputs.stepperMaxValue
self.stepper.rac.value = self.viewModel.outputs.stepperValue

self.viewModel.outputs.rewardSelected
self.viewModel.outputs.notifiyDelegateDidSelectQuantity
.observeForUI()
.observeValues { [weak self] rewardId in
.observeValues { [weak self] quantity, rewardId in
guard let self = self else { return }

self.delegate?.rewardAddOnCardView(self, didTapWithRewardId: rewardId)
self.delegate?.rewardAddOnCardView(self, didSelectQuantity: quantity, rewardId: rewardId)
}

self.viewModel.outputs.reloadPills
Expand Down
103 changes: 103 additions & 0 deletions Kickstarter-iOS/Views/RewardAddOnSelectionContinueCTAView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import KsApi
import Library
import Prelude
import UIKit

protocol RewardAddOnSelectionContinueCTAViewDelegate: AnyObject {
func continueButtonTapped()
}

private enum Layout {
enum Button {
static let minHeight: CGFloat = 48.0
}
}

final class RewardAddOnSelectionContinueCTAView: UIView {
// MARK: - Properties

private(set) lazy var continueButton: UIButton = {
UIButton(type: .custom)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}()

weak var delegate: RewardAddOnSelectionContinueCTAViewDelegate?

private let viewModel: RewardAddOnSelectionContinueCTAViewModelType
= RewardAddOnSelectionContinueCTAViewModel()

// MARK: - Lifecycle

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

self.configureSubviews()
self.setupConstraints()
self.bindViewModel()
}

required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Styles

override func bindStyles() {
super.bindStyles()

_ = self
|> \.layoutMargins .~ .init(all: Styles.grid(3))

_ = self.layer
|> checkoutLayerCardRoundedStyle
|> \.backgroundColor .~ UIColor.white.cgColor
|> \.shadowColor .~ UIColor.black.cgColor
|> \.shadowOpacity .~ 0.12
|> \.shadowOffset .~ CGSize(width: 0, height: -1.0)
|> \.shadowRadius .~ CGFloat(1.0)
|> \.maskedCorners .~ [
CACornerMask.layerMaxXMinYCorner,
CACornerMask.layerMinXMinYCorner
]
}

// MARK: - View Model

override func bindViewModel() {
super.bindViewModel()

self.continueButton.rac.title = self.viewModel.outputs.buttonTitle

self.viewModel.outputs.buttonStyle
.observeForUI()
.observeValues { [weak self] buttonStyleType in
_ = self?.continueButton
?|> buttonStyleType.style

guard let buttonFont = self?.continueButton.titleLabel?.font else { return }

_ = self?.continueButton
?|> UIButton.lens.titleLabel.font .~ buttonFont.monospaced
}
}

// MARK: - Configuration

func configure(with data: RewardAddOnSelectionContinueCTAViewData) {
self.viewModel.inputs.configure(with: data)
}

// MARK: Functions

private func configureSubviews() {
_ = (self.continueButton, self)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToMarginsInParent()
}

private func setupConstraints() {
NSLayoutConstraint.activate([
self.continueButton.heightAnchor.constraint(greaterThanOrEqualToConstant: Layout.Button.minHeight)
])
}
}
Loading