Skip to content

Commit

Permalink
[MBL-1208] Part 2: Pledge Total Summary Section (#1970)
Browse files Browse the repository at this point in the history
* Navigate to initial ConfirmDetailsViewController from rewards options

The initial view controller includes:
* Shipping location selector (when available)
* Pledge/Bonus stepper options (when available)

* Add tests

* Create PostCampaignPledgeSummaryViewController

* Displays the total pledge summary with conversion text

* Show pledge summary in the correct states

* There is no reward
* The reward is a digital reward and requires no shipping

* Add tests

* undo test device change

* use existing PledgeSummaryViewController and remove new class
  • Loading branch information
scottkicks authored Mar 14, 2024
1 parent 4566798 commit c7fcfa0
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ final class ConfirmDetailsViewController: UIViewController {
PledgeLocalPickupView(frame: .zero)
}()

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

/// Total Pledge Summary. Shown when there is no reward selected
private lazy var pledgeSummaryViewController: PledgeSummaryViewController = {
PledgeSummaryViewController.instantiate()
}()

private lazy var pledgeAmountSummarySectionViews = {
[
self.pledgeSummarySectionSeparator,
self.pledgeSummaryViewController.view
]
}()

private lazy var inputsSectionViews = {
[
self.shippingLocationViewController.view,
Expand Down Expand Up @@ -112,7 +129,8 @@ final class ConfirmDetailsViewController: UIViewController {

let childViewControllers = [
self.pledgeAmountViewController,
self.shippingLocationViewController
self.shippingLocationViewController,
self.pledgeSummaryViewController
]

let arrangedSubviews = [
Expand All @@ -121,7 +139,8 @@ final class ConfirmDetailsViewController: UIViewController {

let arrangedInsetSubviews = [
[self.titleLabel],
self.inputsSectionViews
self.inputsSectionViews,
self.pledgeAmountSummarySectionViews
]
.flatMap { $0 }
.compact()
Expand All @@ -146,7 +165,8 @@ final class ConfirmDetailsViewController: UIViewController {
self.rootScrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
self.rootScrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
self.rootScrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.rootStackView.widthAnchor.constraint(equalTo: self.view.widthAnchor)
self.rootStackView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
self.pledgeSummarySectionSeparator.heightAnchor.constraint(equalToConstant: 1)
])
}

Expand All @@ -169,6 +189,9 @@ final class ConfirmDetailsViewController: UIViewController {

_ = self.rootInsetStackView
|> rootInsetStackViewStyle

_ = self.pledgeSummarySectionSeparator
|> separatorStyle
}

// MARK: - View model
Expand Down Expand Up @@ -200,6 +223,12 @@ final class ConfirmDetailsViewController: UIViewController {
self?.pledgeAmountViewController.configureWith(value: data)
}

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

Keyboard.change
.observeForUI()
.observeValues { [weak self] change in
Expand All @@ -213,6 +242,9 @@ final class ConfirmDetailsViewController: UIViewController {
self.shippingSummaryView.rac.hidden = self.viewModel.outputs.shippingSummaryViewHidden

self.localPickupLocationView.rac.hidden = self.viewModel.outputs.localPickupViewHidden

self.pledgeSummarySectionSeparator.rac.hidden = self.viewModel.outputs.pledgeSummaryViewHidden
self.pledgeSummaryViewController.view.rac.hidden = self.viewModel.outputs.pledgeSummaryViewHidden
}

// MARK: - Actions
Expand Down
58 changes: 55 additions & 3 deletions Library/ViewModels/ConfirmDetailsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ public protocol ConfirmDetailsViewModelInputs {
public protocol ConfirmDetailsViewModelOutputs {
var configureLocalPickupViewWithData: Signal<PledgeLocalPickupViewData, Never> { get }
var configurePledgeAmountViewWithData: Signal<PledgeAmountViewConfigData, Never> { get }
var configurePledgeSummaryViewControllerWithData: Signal<PledgeSummaryViewData, Never> { get }
var configureShippingLocationViewWithData: Signal<PledgeShippingLocationViewData, Never> { get }
var configureShippingSummaryViewWithData: Signal<PledgeShippingSummaryViewData, Never> { get }
var localPickupViewHidden: Signal<Bool, Never> { get }
var pledgeAmountViewHidden: Signal<Bool, Never> { get }
var pledgeSummaryViewHidden: Signal<Bool, Never> { get }
var shippingLocationViewHidden: Signal<Bool, Never> { get }
var shippingSummaryViewHidden: Signal<Bool, Never> { get }
}
Expand Down Expand Up @@ -70,7 +72,7 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
calculatedShippingTotal
)

// Initial pledge amount is zero if not backed.
/// Initial pledge amount is zero if not backed.
let initialAdditionalPledgeAmount = Signal.merge(
initialData.filter { $0.project.personalization.backing == nil }.mapConst(0.0),
backing.map(\.bonusAmount)
Expand Down Expand Up @@ -142,7 +144,7 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
(project, reward, true, locationId)
}

// Only shown for add-ons based rewards
/// Only shown for add-ons based rewards
self.configureShippingSummaryViewWithData = Signal.combineLatest(
selectedShippingRule.skipNil().map(\.location.localizedName),
project.map(\.stats.omitUSCurrencyCode),
Expand All @@ -166,7 +168,7 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
)
}

// Only shown for if the shipping summary view and shipping location view are hidden
/// Only shown for if the shipping summary view and shipping location view are hidden
self.configureLocalPickupViewWithData = Signal.combineLatest(
projectAndReward,
shippingViewsHidden.filter(isTrue)
Expand All @@ -181,6 +183,54 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
return SignalProducer(value: localPickupLocationData)
}
.skipNil()

// MARK: Total Pledge Summary

/// Hide when there is a reward and shipping is enabled (accounts for digital rewards), and in a pledge context
self.pledgeSummaryViewHidden = Signal.zip(baseReward, context).map { baseReward, context in
(baseReward.isNoReward == false && baseReward.shipping.enabled) && context == .pledge
}

let allRewardsTotal = Signal.combineLatest(
rewards,
selectedQuantities
)
.map(calculateAllRewardsTotal)

let additionalPledgeAmount = Signal.merge(
self.pledgeAmountDataSignal.map { $0.amount },
initialAdditionalPledgeAmount
)

/**
* For a regular reward this includes the bonus support amount,
* the total of all rewards and their respective shipping costs.
* For No Reward this is only the pledge amount.
*/
let calculatedPledgeTotal = Signal.combineLatest(
additionalPledgeAmount,
allRewardsShippingTotal,
allRewardsTotal
)
.map(calculatePledgeTotal)

let pledgeTotal = Signal.merge(
backing.map(\.amount),
calculatedPledgeTotal
)

let projectAndConfirmationLabelHidden = Signal.combineLatest(
project,
context.map { $0.confirmationLabelHidden }
)

self.configurePledgeSummaryViewControllerWithData = Signal.combineLatest(
projectAndConfirmationLabelHidden,
pledgeTotal
)
.map(unpack)
.map { project, confirmationLabelHidden, total in (project, total, confirmationLabelHidden) }
.map(pledgeSummaryViewData)
}

// MARK: - Inputs
Expand Down Expand Up @@ -214,10 +264,12 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail

public let configureLocalPickupViewWithData: Signal<PledgeLocalPickupViewData, Never>
public let configurePledgeAmountViewWithData: Signal<PledgeAmountViewConfigData, Never>
public let configurePledgeSummaryViewControllerWithData: Signal<PledgeSummaryViewData, Never>
public let configureShippingLocationViewWithData: Signal<PledgeShippingLocationViewData, Never>
public let configureShippingSummaryViewWithData: Signal<PledgeShippingSummaryViewData, Never>
public let localPickupViewHidden: Signal<Bool, Never>
public let pledgeAmountViewHidden: Signal<Bool, Never>
public let pledgeSummaryViewHidden: Signal<Bool, Never>
public let shippingLocationViewHidden: Signal<Bool, Never>
public let shippingSummaryViewHidden: Signal<Bool, Never>

Expand Down
83 changes: 81 additions & 2 deletions Library/ViewModels/ConfirmDetailsViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@ import XCTest
final class ConfirmDetailsViewModelTests: TestCase {
private let vm: ConfirmDetailsViewModelType = ConfirmDetailsViewModel()

private let configurePledgeSummaryViewControllerWithDataConfirmationLabelHidden = TestObserver<
Bool,
Never
>()
private let configurePledgeSummaryViewControllerWithDataPledgeTotal = TestObserver<Double, Never>()
private let configurePledgeSummaryViewControllerWithDataProject = TestObserver<Project, Never>()

private let configureLocalPickupViewWithData = TestObserver<PledgeLocalPickupViewData, Never>()
private let configureShippingSummaryViewWithData = TestObserver<PledgeShippingSummaryViewData, Never>()
private let configureShippingLocationViewWithDataProject = TestObserver<Project, Never>()
private let configureShippingLocationViewWithDataReward = TestObserver<Reward, Never>()
private let configureShippingLocationViewWithDataShowAmount = TestObserver<Bool, Never>()

private let localPickupViewHidden = TestObserver<Bool, Never>()
private let pledgeAmountViewHidden = TestObserver<Bool, Never>()
private let shippingLocationViewHidden = TestObserver<Bool, Never>()
Expand All @@ -36,10 +44,15 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.outputs.configureShippingSummaryViewWithData
.observe(self.configureShippingSummaryViewWithData.observer)

self.vm.outputs.localPickupViewHidden.observe(self.localPickupViewHidden.observer)
self.vm.outputs.configurePledgeSummaryViewControllerWithData.map { $0.2 }
.observe(self.configurePledgeSummaryViewControllerWithDataConfirmationLabelHidden.observer)
self.vm.outputs.configurePledgeSummaryViewControllerWithData.map { $0.1 }
.observe(self.configurePledgeSummaryViewControllerWithDataPledgeTotal.observer)
self.vm.outputs.configurePledgeSummaryViewControllerWithData.map { $0.0 }
.observe(self.configurePledgeSummaryViewControllerWithDataProject.observer)

self.vm.outputs.localPickupViewHidden.observe(self.localPickupViewHidden.observer)
self.vm.outputs.pledgeAmountViewHidden.observe(self.pledgeAmountViewHidden.observer)

self.vm.outputs.shippingLocationViewHidden.observe(self.shippingLocationViewHidden.observer)
self.vm.outputs.shippingSummaryViewHidden.observe(self.shippingSummaryViewHidden.observer)
}
Expand All @@ -64,6 +77,10 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataConfirmationLabelHidden.assertValues([false])
self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand Down Expand Up @@ -93,6 +110,10 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataConfirmationLabelHidden.assertValues([false])
self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand All @@ -119,6 +140,9 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertDidNotEmitValue()
self.configureShippingLocationViewWithDataReward.assertDidNotEmitValue()
self.configureShippingLocationViewWithDataShowAmount.assertDidNotEmitValue()
Expand All @@ -145,6 +169,9 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand All @@ -169,6 +196,9 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertDidNotEmitValue()
self.configureShippingLocationViewWithDataReward.assertDidNotEmitValue()
self.configureShippingLocationViewWithDataShowAmount.assertDidNotEmitValue()
Expand All @@ -193,6 +223,9 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand All @@ -217,9 +250,21 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])

let defaultShippingRule = ShippingRule.template
|> ShippingRule.lens.cost .~ 5

self.vm.inputs.shippingRuleSelected(defaultShippingRule)

self.configurePledgeSummaryViewControllerWithDataPledgeTotal
.assertValues([reward.minimum, reward.minimum + defaultShippingRule.cost])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project, project])
}
}

Expand All @@ -241,9 +286,34 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])

let defaultShippingRule = ShippingRule.template
|> ShippingRule.lens.cost .~ 5

self.vm.inputs.shippingRuleSelected(defaultShippingRule)

self.configurePledgeSummaryViewControllerWithDataPledgeTotal
.assertValues([reward.minimum, reward.minimum + defaultShippingRule.cost])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project, project])

let selectedShippingRule = ShippingRule.template
|> ShippingRule.lens.cost .~ 5
|> ShippingRule.lens.location .~ .australia

self.vm.inputs.shippingRuleSelected(selectedShippingRule)

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([
reward.minimum,
reward.minimum + defaultShippingRule.cost,
reward.minimum + selectedShippingRule.cost
])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project, project, project])
}
}

Expand All @@ -265,6 +335,9 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([reward.minimum])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand All @@ -276,6 +349,9 @@ final class ConfirmDetailsViewModelTests: TestCase {

self.vm.inputs.pledgeAmountViewControllerDidUpdate(with: data1)

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([10, 76])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project, project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand All @@ -284,6 +360,9 @@ final class ConfirmDetailsViewModelTests: TestCase {

self.vm.inputs.pledgeAmountViewControllerDidUpdate(with: data2)

self.configurePledgeSummaryViewControllerWithDataPledgeTotal.assertValues([10, 76, 103])
self.configurePledgeSummaryViewControllerWithDataProject.assertValues([project, project, project])

self.configureShippingLocationViewWithDataProject.assertValues([project])
self.configureShippingLocationViewWithDataReward.assertValues([reward])
self.configureShippingLocationViewWithDataShowAmount.assertValues([true])
Expand Down
Loading

0 comments on commit c7fcfa0

Please sign in to comment.