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

💲[Native Checkout] Rewards Collection View Plumbing #664

Merged
merged 12 commits into from
May 6, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ internal final class ProjectPamphletContentDataSource: ValueCellDataSource {
} else if let backing = project.personalization.backing {
self.set(values: [project], cellClass: PledgeTitleCell.self, inSection: Section.pledgeTitle.rawValue)
self.set(values: [(project, .right(backing))],
cellClass: RewardCell.self,
cellClass: DeprecatedRewardCell.self,
inSection: Section.calloutReward.rawValue)
}
}
Expand All @@ -105,10 +105,10 @@ internal final class ProjectPamphletContentDataSource: ValueCellDataSource {
}

self.set(values: availableRewards(for: project),
cellClass: RewardCell.self,
cellClass: DeprecatedRewardCell.self,
inSection: Section.availableRewards.rawValue)
self.set(values: unavailableRewards(for: project),
cellClass: RewardCell.self,
cellClass: DeprecatedRewardCell.self,
inSection: Section.unavailableRewards.rawValue)
}
}
Expand Down Expand Up @@ -163,7 +163,7 @@ internal final class ProjectPamphletContentDataSource: ValueCellDataSource {
internal override func configureCell(tableCell cell: UITableViewCell, withValue value: Any) {

switch (cell, value) {
case let (cell as RewardCell, value as (Project, Either<Reward, Backing>)):
case let (cell as DeprecatedRewardCell, value as (Project, Either<Reward, Backing>)):
cell.configureWith(value: value)
case let (cell as ProjectPamphletMainCell, value as Project):
cell.configureWith(value: value)
Expand Down
20 changes: 20 additions & 0 deletions Kickstarter-iOS/DataSources/RewardsCollectionViewDataSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import KsApi
import Library

final class RewardsCollectionViewDataSource: ValueCellDataSource {
func load(rewards: [Reward]) {
self.set(values: rewards,
cellClass: RewardCell.self,
inSection: 0)
}

override func configureCell(collectionCell cell: UICollectionViewCell, withValue value: Any) {
switch (cell, value) {
case let (cell as RewardCell, value as Reward):
cell.configureWith(value: value)
default:
assertionFailure("Unrecognized (cell, value) combo.")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import XCTest
@testable import Kickstarter_Framework
@testable import KsApi

final class RewardsCollectionViewDataSourceTests: XCTestCase {
private let dataSource = RewardsCollectionViewDataSource()
private let collectionView = UICollectionView(frame: .zero,
collectionViewLayout: UICollectionViewFlowLayout())

func testLoadRewards() {
let rewards = [Reward.template, Reward.template]

self.dataSource.load(rewards: rewards)

XCTAssertEqual(1, self.dataSource.numberOfSections(in: self.collectionView))
XCTAssertEqual(2, self.dataSource.collectionView(self.collectionView, numberOfItemsInSection: 0))
}
}
2 changes: 1 addition & 1 deletion Kickstarter-iOS/Library/Nib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ public enum Nib: String {
case BackerDashboardEmptyStateCell
case BackerDashboardProjectCell
case CreditCardCell
case DeprecatedRewardCell
case DiscoveryPostcardCell
case DiscoveryProjectCategoryView
case FindFriendsCell
case LiveStreamNavTitleView
case LoadingBarButtonItemView
case MessageBannerViewController
case PaymentMethodsFooterView
case RewardCell
case SettingsAccountWarningCell
case SettingsFormFieldView
case SettingsFooterView
Expand Down
268 changes: 268 additions & 0 deletions Kickstarter-iOS/Views/Cells/DeprecatedRewardCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import KsApi
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing in this file is actually new. This file was renamed from RewardCell.swift -> DeprecatedRewardCell.swift

import Library
import Prelude

internal protocol RewardCellDelegate: class {
/// Called when the reward cell needs to perform an expansion animation.
func rewardCellWantsExpansion(_ cell: DeprecatedRewardCell)
}

internal final class DeprecatedRewardCell: UITableViewCell, ValueCell {
internal var delegate: RewardCellDelegate?
fileprivate let viewModel: RewardCellViewModelType = DeprecatedRewardCellViewModel()

@IBOutlet fileprivate weak var allGoneContainerView: UIView!
@IBOutlet fileprivate weak var allGoneLabel: UILabel!
@IBOutlet fileprivate weak var cardView: UIView!
@IBOutlet fileprivate weak var checkmarkImageView: UIImageView!
@IBOutlet fileprivate weak var conversionLabel: UILabel!
@IBOutlet fileprivate weak var descriptionLabel: UILabel!
@IBOutlet fileprivate weak var estimatedDeliveryDateLabel: UILabel!
@IBOutlet fileprivate weak var estimatedDeliveryLabel: UILabel!
@IBOutlet fileprivate weak var estimatedDeliveryDateStackView: UIStackView!
@IBOutlet fileprivate weak var footerLabel: UILabel!
@IBOutlet fileprivate weak var footerStackView: UIStackView!
@IBOutlet fileprivate weak var includesTitleLabel: UILabel!
@IBOutlet fileprivate weak var itemsContainerStackView: UIStackView!
@IBOutlet fileprivate weak var itemsHeaderStackView: UIStackView!
@IBOutlet fileprivate weak var itemsStackView: UIStackView!
@IBOutlet fileprivate weak var manageRewardButton: UIButton!
@IBOutlet fileprivate weak var minimumLabel: UILabel!
@IBOutlet fileprivate weak var minimumStackView: UIStackView!
@IBOutlet fileprivate weak var rewardTitleLabel: UILabel!
@IBOutlet fileprivate weak var rootStackView: UIStackView!
@IBOutlet fileprivate weak var selectRewardButton: UIButton!
@IBOutlet fileprivate weak var shippingLocationsLabel: UILabel!
@IBOutlet fileprivate weak var shippingLocationsStackView: UIStackView!
@IBOutlet fileprivate weak var shippingLocationsSummaryLabel: UILabel!
@IBOutlet fileprivate var separatorViews: [UIView]!
@IBOutlet fileprivate weak var titleDescriptionStackView: UIStackView!
@IBOutlet fileprivate weak var viewYourPledgeButton: UIButton!
@IBOutlet fileprivate weak var youreABackerCheckmarkImageView: UIImageView!
@IBOutlet fileprivate weak var youreABackerContainerView: UIView!
@IBOutlet fileprivate weak var youreABackerLabel: UILabel!
@IBOutlet fileprivate weak var youreABackerStackView: UIStackView!

internal override func awakeFromNib() {
super.awakeFromNib()

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapRecognizer.cancelsTouchesInView = false
tapRecognizer.delaysTouchesBegan = false
tapRecognizer.delaysTouchesEnded = false
self.addGestureRecognizer(tapRecognizer)
}

@objc fileprivate func tapped() {
self.viewModel.inputs.tapped()
}

internal func configureWith(value: (Project, Either<Reward, Backing>)) {
self.viewModel.inputs.configureWith(project: value.0, rewardOrBacking: value.1)
}

internal override func bindStyles() {
super.bindStyles()

_ = self
|> baseTableViewCellStyle()
|> DeprecatedRewardCell.lens.accessibilityTraits .~ UIAccessibilityTraits.button.rawValue
|> (DeprecatedRewardCell.lens.contentView..UIView.lens.layoutMargins) %~~ { _, cell in
cell.traitCollection.isRegularRegular
? .init(top: Styles.grid(2), left: Styles.grid(16), bottom: Styles.grid(4), right: Styles.grid(16))
: .init(top: Styles.grid(1), left: Styles.grid(2), bottom: Styles.grid(2), right: Styles.grid(2))
}
|> DeprecatedRewardCell.lens.contentView..UIView.lens.backgroundColor .~ projectCellBackgroundColor()
|> UIView.lens.contentMode .~ .top

_ = self.rootStackView
|> UIStackView.lens.spacing .~ Styles.grid(4)
|> UIStackView.lens.layoutMargins
.~ .init(top: Styles.grid(3), left: Styles.grid(2), bottom: Styles.grid(2), right: Styles.grid(2))
|> UIStackView.lens.isLayoutMarginsRelativeArrangement .~ true

_ = self.minimumStackView
|> UIStackView.lens.spacing .~ Styles.grid(1)

_ = self.titleDescriptionStackView
|> UIStackView.lens.spacing .~ Styles.grid(2)

_ = self.footerStackView
|> UIStackView.lens.spacing .~ Styles.grid(2)

_ = [self.estimatedDeliveryDateStackView, self.shippingLocationsStackView]
||> UIStackView.lens.spacing .~ Styles.gridHalf(1)

_ = [self.itemsContainerStackView, self.itemsHeaderStackView, self.itemsStackView]
||> UIStackView.lens.spacing .~ Styles.grid(2)

_ = [self.minimumStackView, self.titleDescriptionStackView,
self.itemsContainerStackView, self.footerStackView]
||> UIStackView.lens.layoutMargins .~ .init(topBottom: 0, leftRight: Styles.grid(2))
||> UIStackView.lens.isLayoutMarginsRelativeArrangement .~ true

_ = self.allGoneContainerView
|> roundedStyle(cornerRadius: 2)
|> UIView.lens.backgroundColor .~ UIColor.ksr_soft_black
|> UIView.lens.layoutMargins .~ .init(topBottom: Styles.gridHalf(1), leftRight: Styles.grid(1))

_ = self.allGoneLabel
|> UILabel.lens.textColor .~ .white
|> UILabel.lens.font .~ .ksr_headline(size: 12)
|> UILabel.lens.text %~ { _ in Strings.All_gone() }

_ = self.cardView
|> darkCardStyle(cornerRadius: 0)
|> UIView.lens.backgroundColor .~ .white

_ = self.minimumLabel
|> UILabel.lens.font .~ .ksr_title2(size: 24)

_ = self.conversionLabel
|> UILabel.lens.font .~ UIFont.ksr_caption1().italicized

_ = self.rewardTitleLabel
|> UILabel.lens.font .~ .ksr_body(size: 18)
|> UILabel.lens.numberOfLines .~ 0

_ = self.descriptionLabel
|> UILabel.lens.font .~ .ksr_body(size: 16)
|> UILabel.lens.textColor .~ .ksr_text_dark_grey_400
|> UILabel.lens.numberOfLines .~ 0

_ = self.estimatedDeliveryLabel
|> UILabel.lens.text %~ { _ in Strings.Estimated_delivery().uppercased() }
|> UILabel.lens.font .~ .ksr_caption1(size: 12)
|> UILabel.lens.textColor .~ .ksr_text_dark_grey_400

_ = self.estimatedDeliveryDateLabel
|> UILabel.lens.font .~ .ksr_caption1(size: 13)
|> UILabel.lens.textColor .~ .ksr_soft_black

_ = self.includesTitleLabel
|> UILabel.lens.font .~ .ksr_headline(size: 13)
|> UILabel.lens.textColor .~ .ksr_text_dark_grey_500
|> UILabel.lens.text %~ { _ in Strings.rewards_info_includes() }

_ = self.shippingLocationsLabel
|> UILabel.lens.text %~ { _ in Strings.Ships_to().uppercased() }
|> UILabel.lens.font .~ .ksr_caption1(size: 12)
|> UILabel.lens.textColor .~ .ksr_text_dark_grey_400

_ = self.shippingLocationsSummaryLabel
|> UILabel.lens.font .~ .ksr_caption1(size: 13)
|> UILabel.lens.textColor .~ .ksr_soft_black

_ = self.youreABackerCheckmarkImageView
|> UIImageView.lens.tintColor .~ .ksr_text_dark_grey_500
|> UIImageView.lens.image %~ { _ in
UIImage(named: "checkmark-icon", in: .framework, compatibleWith: nil)
}

_ = self.youreABackerContainerView
|> roundedStyle(cornerRadius: 2)
|> UIView.lens.backgroundColor .~ UIColor.ksr_green_500
|> UIView.lens.layoutMargins .~ .init(topBottom: Styles.grid(1), leftRight: Styles.gridHalf(3))

_ = self.youreABackerLabel
|> UILabel.lens.font .~ .ksr_headline(size: 12)
|> UILabel.lens.textColor .~ .white

_ = self.youreABackerStackView
|> UIStackView.lens.spacing .~ Styles.gridHalf(1)
|> UIStackView.lens.alignment .~ .center

_ = self.checkmarkImageView
|> UIImageView.lens.tintColor .~ .white

_ = self.footerLabel
|> UILabel.lens.font .~ .ksr_caption1(size: 13)
|> UILabel.lens.textColor .~ .ksr_soft_black

_ = self.separatorViews
||> separatorStyle

_ = self.selectRewardButton
|> greenButtonStyle
|> UIButton.lens.layer.cornerRadius .~ 0
|> UIButton.lens.isUserInteractionEnabled .~ false
|> UIButton.lens.isAccessibilityElement .~ false

_ = self.manageRewardButton
|> greenBorderButtonStyle
|> UIButton.lens.isUserInteractionEnabled .~ false
|> UIButton.lens.title(for: .normal) %~ { _ in Strings.Manage_your_pledge() }
|> UIButton.lens.isAccessibilityElement .~ false

_ = self.viewYourPledgeButton
|> borderButtonStyle
|> UIButton.lens.isUserInteractionEnabled .~ false
|> UIButton.lens.title(for: .normal) %~ { _ in Strings.View_your_pledge() }
|> UIButton.lens.isAccessibilityElement .~ false

self.viewModel.inputs.boundStyles()
}

internal override func bindViewModel() {
super.bindViewModel()

self.allGoneContainerView.rac.hidden = self.viewModel.outputs.allGoneHidden
self.conversionLabel.rac.hidden = self.viewModel.outputs.conversionLabelHidden
self.conversionLabel.rac.text = self.viewModel.outputs.conversionLabelText
self.conversionLabel.rac.textColor = self.viewModel.outputs.minimumAndConversionLabelsColor
self.descriptionLabel.rac.hidden = self.viewModel.outputs.descriptionLabelHidden
self.descriptionLabel.rac.text = self.viewModel.outputs.descriptionLabelText
self.estimatedDeliveryDateLabel.rac.text = self.viewModel.outputs.estimatedDeliveryDateLabelText
self.footerStackView.rac.hidden = self.viewModel.outputs.footerStackViewHidden
self.footerLabel.rac.text = self.viewModel.outputs.footerLabelText
self.itemsContainerStackView.rac.hidden = self.viewModel.outputs.itemsContainerHidden
self.manageRewardButton.rac.hidden = self.viewModel.outputs.manageButtonHidden
self.minimumLabel.rac.text = self.viewModel.outputs.minimumLabelText
self.minimumLabel.rac.textColor = self.viewModel.outputs.minimumAndConversionLabelsColor
self.rewardTitleLabel.rac.hidden = self.viewModel.outputs.titleLabelHidden
self.rewardTitleLabel.rac.text = self.viewModel.outputs.titleLabelText
self.rewardTitleLabel.rac.textColor = self.viewModel.outputs.titleLabelTextColor
self.selectRewardButton.rac.hidden = self.viewModel.outputs.pledgeButtonHidden
self.selectRewardButton.rac.title = self.viewModel.outputs.pledgeButtonTitleText
self.shippingLocationsStackView.rac.hidden = self.viewModel.outputs.shippingLocationsStackViewHidden
self.shippingLocationsSummaryLabel.rac.text = self.viewModel.outputs.shippingLocationsSummaryLabelText
self.viewYourPledgeButton.rac.hidden = self.viewModel.outputs.viewPledgeButtonHidden
self.youreABackerContainerView.rac.hidden = self.viewModel.outputs.youreABackerViewHidden
self.youreABackerLabel.rac.text = self.viewModel.outputs.youreABackerLabelText

self.viewModel.outputs.notifyDelegateRewardCellWantsExpansion
.observeForUI()
.observeValues { [weak self] in
self.doIfSome { $0.delegate?.rewardCellWantsExpansion($0) }
}

self.viewModel.outputs.updateTopMarginsForIsBacking
.observeForUI()
.observeValues { [weak self] isBacking in
self?.contentView.layoutMargins.top = Styles.grid(isBacking ? 3 : 1)
}

self.viewModel.outputs.items
.observeForUI()
.observeValues { [weak self] in self?.load(items: $0) }
}

fileprivate func load(items: [String]) {
self.itemsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }

for item in items {
let label = UILabel()
|> UILabel.lens.font .~ .ksr_body(size: 14)
|> UILabel.lens.textColor .~ .ksr_text_dark_grey_500
|> UILabel.lens.text .~ item
|> UILabel.lens.numberOfLines .~ 0

let separator = UIView()
|> separatorStyle
separator.heightAnchor.constraint(equalToConstant: 1).isActive = true

self.itemsStackView.addArrangedSubview(label)
self.itemsStackView.addArrangedSubview(separator)
}
}
}
Loading