Skip to content

Commit

Permalink
[MBL-1606] New Crowdfund Checkout UI (#2127)
Browse files Browse the repository at this point in the history
* remove expandable header

* replace summary section with pledge summary table

* don't show pledge amount, total summary, and bonus amount sections

* only show bonus amount in summary if it's greater than 0

* update pledge summary table cell UI

* create new NoShippingPledgeRewardsSummaryTotalViewController

* makes feature flagging easier

* only add header labels to pledge summary table cells if feature flag is on

* add new CTA container that includes the pledge amount

* add title label

* remove used outputs

* fix tests

* pledge rewards summary table test coverage

* add snapshots
  • Loading branch information
scottkicks authored Aug 22, 2024
1 parent 0ff8bfe commit 0986687
Show file tree
Hide file tree
Showing 33 changed files with 1,197 additions and 544 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,6 @@ final class ConfirmDetailsContinueCTAView: UIView {
}
}

private func attributedCurrency(withProject project: Project, total: Double) -> NSAttributedString? {
let defaultAttributes = checkoutCurrencyDefaultAttributes()
.withAllValuesFrom([.foregroundColor: UIColor.ksr_support_700])
let projectCurrencyCountry = projectCountry(forCurrency: project.stats.currency) ?? project.country

return Format.attributedCurrency(
total,
country: projectCurrencyCountry,
omitCurrencyCode: project.stats.omitUSCurrencyCode,
defaultAttributes: defaultAttributes,
superscriptAttributes: checkoutCurrencySuperscriptAttributes()
)
}

// MARK: - Accessors

@objc func continueButtonTapped() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import Library
import Prelude
import UIKit

final class NoShippingPledgeRewardsSummaryTotalViewController: UIViewController {
// MARK: - Properties

private let dataSource = NoShippingPledgeRewardsSummaryDataSource()

private var tableViewContainerHeightConstraint: NSLayoutConstraint?

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

private lazy var tableViewContainer: UIView = {
UIView(frame: .zero)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
|> \.clipsToBounds .~ true
}()

private lazy var tableView: UITableView = {
ContentSizeTableView(frame: .zero, style: .plain)
|> \.separatorInset .~ .zero
|> \.contentInsetAdjustmentBehavior .~ .never
|> \.isScrollEnabled .~ false
|> \.dataSource .~ self.dataSource
|> \.delegate .~ self
|> \.rowHeight .~ UITableView.automaticDimension
}()

private lazy var separatorView: UIView = { UIView(frame: .zero) }()

private lazy var pledgeTotalViewController = {
PostCampaignPledgeRewardsSummaryTotalViewController.instantiate()
}()

private let viewModel: PostCampaignPledgeRewardsSummaryViewModelType =
PostCampaignPledgeRewardsSummaryViewModel()

// MARK: - Lifecycle

override func viewDidLoad() {
super.viewDidLoad()

self.configureSubviews()
self.setupConstraints()
self.setEntireViewToIsHidden(true)

self.viewModel.inputs.viewDidLoad()
}

private func configureSubviews() {
_ = (self.rootStackView, self.view)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

_ = (
[self.tableViewContainer, self.separatorView, self.pledgeTotalViewController.view],
self.rootStackView
)
|> ksr_addArrangedSubviewsToStackView()

_ = (self.tableView, self.tableViewContainer)
|> ksr_addSubviewToParent()

self.addChild(self.pledgeTotalViewController)
self.pledgeTotalViewController.didMove(toParent: self)

self.tableView.registerCellClass(PostCampaignPledgeRewardsSummaryHeaderCell.self)
self.tableView.registerCellClass(PostCampaignPledgeRewardsSummaryCell.self)
}

override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()

self.tableViewContainerHeightConstraint?.constant = self.tableView.intrinsicContentSize.height
}

private func setupConstraints() {
let tableViewContainerHeightConstraint = self.tableViewContainer.heightAnchor
.constraint(equalToConstant: 0)
self.tableViewContainerHeightConstraint = tableViewContainerHeightConstraint

NSLayoutConstraint.activate([
tableViewContainerHeightConstraint,
self.tableView.leftAnchor.constraint(equalTo: self.tableViewContainer.leftAnchor),
self.tableView.rightAnchor.constraint(equalTo: self.tableViewContainer.rightAnchor),
self.tableView.topAnchor.constraint(equalTo: self.tableViewContainer.topAnchor),
self.tableViewContainer.leftAnchor.constraint(equalTo: self.rootStackView.leftAnchor),
self.tableViewContainer.rightAnchor.constraint(equalTo: self.rootStackView.rightAnchor),
self.tableViewContainer.topAnchor.constraint(equalTo: self.rootStackView.topAnchor),
self.separatorView.leftAnchor
.constraint(equalTo: self.rootStackView.leftAnchor, constant: Styles.grid(4)),
self.separatorView.rightAnchor
.constraint(equalTo: self.rootStackView.rightAnchor, constant: -Styles.grid(4)),
self.separatorView.heightAnchor.constraint(equalToConstant: 1),
self.rootStackView.widthAnchor.constraint(equalTo: self.view.widthAnchor)
])
}

// MARK: - Bind Styles

override func bindStyles() {
super.bindStyles()

_ = self.view
|> \.clipsToBounds .~ true
|> checkoutWhiteBackgroundStyle

_ = self.rootStackView
|> self.rootStackViewStyle

_ = self.tableView
|> checkoutWhiteBackgroundStyle
|> \.translatesAutoresizingMaskIntoConstraints .~ false

_ = self.separatorView
|> self.separatorViewStyle

self.tableViewContainerHeightConstraint?.constant = self.tableView.intrinsicContentSize.height
}

// MARK: - View model

override func bindViewModel() {
super.bindViewModel()

self.viewModel.outputs.loadRewardsIntoDataSource
.observeForUI()
.observeValues { [weak self] data in
guard let self else { return }

self.dataSource.load(data)
self.tableView.reloadData()
self.tableView.setNeedsLayout()

self.setEntireViewToIsHidden(false)
self.tableViewContainerHeightConstraint?.constant = self.tableView.intrinsicContentSize.height
}

self.viewModel.outputs.configurePledgeTotalViewWithData
.observeForUI()
.observeValues { [weak self] data in
guard let self else { return }

self.pledgeTotalViewController.configure(with: data)
}
}

// MARK: - Configuration

func configureWith(
rewardsData: PostCampaignRewardsSummaryViewData,
bonusAmount: Double?,
pledgeData: PledgeSummaryViewData
) {
self.viewModel.inputs
.configureWith(rewardsData: rewardsData, bonusAmount: bonusAmount, pledgeData: pledgeData)

self.view.setNeedsLayout()
}

// MARK: Styles

private let rootStackViewStyle: StackViewStyle = { stackView in
stackView
|> \.axis .~ NSLayoutConstraint.Axis.vertical
|> \.spacing .~ Styles.grid(1)
|> \.isLayoutMarginsRelativeArrangement .~ true
}

private let separatorViewStyle: ViewStyle = { view in
view
|> \.backgroundColor .~ .ksr_support_200
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}

// MARK: - Helpers

private func setEntireViewToIsHidden(_ isHidden: Bool) {
self.view.isHidden = isHidden
self.pledgeTotalViewController.view.isHidden = isHidden
self.separatorView.isHidden = isHidden
}
}

// MARK: - UITableViewDelegate

extension NoShippingPledgeRewardsSummaryTotalViewController: UITableViewDelegate {
func tableView(_: UITableView, willSelectRowAt _: IndexPath) -> IndexPath? {
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private let titleLabelStyle: LabelStyle = { (label: UILabel) -> UILabel in
_ = label
|> checkoutTitleLabelStyle
|> \.font .~ .ksr_subhead().bolded
|> \.text %~ { _ in Strings.Total() }
|> \.text %~ { _ in Strings.Pledge_amount() }
|> \.backgroundColor .~ .ksr_white

return label
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import KsApi
import Library
import Prelude
import UIKit

internal final class NoShippingPledgeRewardsSummaryDataSource: ValueCellDataSource {
internal enum Section: Int {
case header
case rewards
}

internal func load(_ items: [PostCampaignRewardsSummaryItem]) {
self.clearValues()

let headerItemData = items.compactMap { item -> PledgeExpandableHeaderRewardCellData? in
guard case let .header(data) = item else { return nil }
return data
}

let rewardItemData = items.compactMap { item -> PledgeExpandableHeaderRewardCellData? in
guard case let .reward(data) = item else { return nil }
return data
}

self.set(
values: headerItemData,
cellClass: PostCampaignPledgeRewardsSummaryHeaderCell.self,
inSection: Section.header.rawValue
)

self.set(
values: rewardItemData,
cellClass: PostCampaignPledgeRewardsSummaryCell.self,
inSection: Section.rewards.rawValue
)
}

override func configureCell(tableCell cell: UITableViewCell, withValue value: Any) {
switch (cell, value) {
case let (
cell as PostCampaignPledgeRewardsSummaryHeaderCell,
value as PledgeExpandableHeaderRewardCellData
):
cell.configureWith(value: value)
case let (cell as PostCampaignPledgeRewardsSummaryCell, value as PledgeExpandableHeaderRewardCellData):
cell.configureWith(value: value)
default:
assertionFailure("Unrecognized combo: \(cell), \(value)")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ final class PledgeExpandableRewardsHeaderDataSourceTests: XCTestCase {

func testLoadValues() {
let items: [PledgeExpandableRewardsHeaderItem] = [
.header(("Header title", NSAttributedString(string: "$800"))),
.reward(("Reward title", NSAttributedString(string: "$400"))),
.reward(("Reward title", NSAttributedString(string: "$400")))
.header((nil, "Header title", NSAttributedString(string: "$800"))),
.reward((nil, "Reward title", NSAttributedString(string: "$400"))),
.reward((nil, "Reward title", NSAttributedString(string: "$400")))
]

self.dataSource.load(items)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ final class PostCampaignPledgeRewardsSummaryCell: UITableViewCell, ValueCell {

private lazy var amountLabel: UILabel = UILabel(frame: .zero)
private lazy var rootStackView: UIStackView = UIStackView(frame: .zero)
private lazy var labelsStackView: UIStackView = UIStackView(frame: .zero)
private lazy var headerLabel: UILabel = UILabel(frame: .zero)
private lazy var titleLabel: UILabel = UILabel(frame: .zero)

private let viewModel: PledgeExpandableHeaderRewardCellViewModelType
Expand Down Expand Up @@ -39,8 +41,11 @@ final class PostCampaignPledgeRewardsSummaryCell: UITableViewCell, ValueCell {
_ = self.rootStackView
|> rootStackViewStyle(self.traitCollection.preferredContentSizeCategory > .accessibilityLarge)

_ = self.labelsStackView
|> labelStackViewStyle

_ = self.titleLabel
|> titleLabelStyle
|> labelStyle

self.amountLabel.setContentHuggingPriority(.required, for: .horizontal)
self.amountLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
Expand All @@ -53,6 +58,14 @@ final class PostCampaignPledgeRewardsSummaryCell: UITableViewCell, ValueCell {

self.amountLabel.rac.attributedText = self.viewModel.outputs.amountAttributedText

self.viewModel.outputs.headerLabelText
.observeForUI()
.observeValues { [weak self] text in
guard let self, text != nil else { return }
self.headerLabel.attributedText = text
self.headerLabel.setNeedsLayout()
}

self.viewModel.outputs.labelText
.observeForUI()
.observeValues { [weak self] titleText in
Expand All @@ -74,25 +87,33 @@ final class PostCampaignPledgeRewardsSummaryCell: UITableViewCell, ValueCell {
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

_ = ([self.titleLabel, self.amountLabel], self.rootStackView)
_ = ([self.labelsStackView, self.amountLabel], self.rootStackView)
|> ksr_addArrangedSubviewsToStackView()

_ = ([self.headerLabel, self.titleLabel], self.labelsStackView)
|> ksr_addArrangedSubviewsToStackView()
}

override func layoutSubviews() {
super.layoutSubviews()
self.headerLabel.preferredMaxLayoutWidth = self.titleLabel.frame.size.width
self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.size.width
super.layoutSubviews()
}
}

// MARK: - Styles

private let titleLabelStyle: LabelStyle = { label in
private func headerLabelStyle(_ label: UILabel) {
label.font = UIFont.ksr_subhead().bolded
label.textColor = UIColor.ksr_support_400
label.textColor = UIColor.ksr_black
label.numberOfLines = 0
}

return label
private func labelStyle(_ label: UILabel) {
label.font = UIFont.ksr_subhead().bolded
label.textColor = UIColor.ksr_support_400
label.numberOfLines = 0
}

private func rootStackViewStyle(_ isAccessibilityCategory: Bool) -> (StackViewStyle) {
Expand All @@ -116,3 +137,11 @@ private func rootStackViewStyle(_ isAccessibilityCategory: Bool) -> (StackViewSt
return stackView
}
}

private func labelStackViewStyle(_ stackView: UIStackView) {
stackView.axis = NSLayoutConstraint.Axis.vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = Styles.grid(1)
stackView.isLayoutMarginsRelativeArrangement = true
}
Loading

0 comments on commit 0986687

Please sign in to comment.