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] Login & Sign-up on the pledge screen (Part 1) #727

Merged
merged 16 commits into from
Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from 14 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
32 changes: 32 additions & 0 deletions Kickstarter-iOS/Views/Cells/PledgeContinueCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,41 @@ import Foundation
import Library
import Prelude

protocol PledgeContinueCellDelegate: AnyObject {
func pledgeContinueCellDidTapContinue(_ cell: PledgeContinueCell)
}

final class PledgeContinueCell: UITableViewCell, ValueCell {
// MARK: - Properties

private let continueButton = MultiLineButton(type: .custom)
internal weak var delegate: PledgeContinueCellDelegate?
private let viewModel = PledgeContinueCellViewModel()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)

self.setupSubviews()

self.bindViewModel()
}

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

override func bindViewModel() {
super.bindViewModel()

self.viewModel.outputs.notifyDelegateContinueButtonTapped
.observeForControllerAction()
.observeValues { [weak self] in
guard let self = self else { return }

self.delegate?.pledgeContinueCellDidTapContinue(self)
}
}

override func bindStyles() {
super.bindStyles()

Expand Down Expand Up @@ -42,5 +64,15 @@ final class PledgeContinueCell: UITableViewCell, ValueCell {
|> ksr_constrainViewToMarginsInParent()

self.continueButton.heightAnchor.constraint(greaterThanOrEqualToConstant: Styles.grid(8)).isActive = true

self.continueButton.addTarget(
self,
action: #selector(PledgeContinueCell.continueButtonTapped),
for: .touchUpInside
)
}

@objc private func continueButtonTapped() {
self.viewModel.inputs.continueButtonTapped()
}
}
35 changes: 35 additions & 0 deletions Kickstarter-iOS/Views/Controllers/PledgeTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PledgeTableViewController: UITableViewController {
private let dataSource: PledgeDataSource = PledgeDataSource()
private weak var pledgeSummaryCell: PledgeSummaryCell?
private weak var shippingLocationCell: PledgeShippingLocationCell?
private var sessionStartedObserver: Any?
private let viewModel: PledgeViewModelType = PledgeViewModel()

// MARK: - Lifecycle
Expand Down Expand Up @@ -49,6 +50,10 @@ class PledgeTableViewController: UITableViewController {
self.viewModel.inputs.viewDidLoad()
}

deinit {
self.sessionStartedObserver.doIfSome(NotificationCenter.default.removeObserver)
}

// MARK: - Styles

override func bindStyles() {
Expand All @@ -63,6 +68,12 @@ class PledgeTableViewController: UITableViewController {
override func bindViewModel() {
super.bindViewModel()

self.viewModel.outputs.goToLoginSignup
.observeForControllerAction()
.observeValues { [weak self] intent in
self?.goToLoginSignup(with: intent)
}

self.viewModel.outputs.pledgeViewDataAndReload
.observeForUI()
.observeValues { [weak self] data, reload in
Expand Down Expand Up @@ -94,6 +105,21 @@ class PledgeTableViewController: UITableViewController {
.observeValues { [weak self] project, pledgeTotal in
self?.pledgeSummaryCell?.configureWith(value: (project, pledgeTotal))
}

self.sessionStartedObserver = NotificationCenter.default
.addObserver(forName: .ksr_sessionStarted, object: nil, queue: nil) { [weak self] _ in
self?.viewModel.inputs.userSessionStarted()
}
}

// MARK: - Private Helpers

private func goToLoginSignup(with intent: LoginIntent) {
let loginSignupViewController = LoginToutViewController.configuredWith(loginIntent: intent)
let navigationController = UINavigationController(rootViewController: loginSignupViewController)
let sheetOverlayViewController = SheetOverlayViewController(child: navigationController)

self.present(sheetOverlayViewController, animated: true)
}

// MARK: - UITableViewDelegate
Expand All @@ -119,6 +145,9 @@ class PledgeTableViewController: UITableViewController {
let shippingLocationCell = (cell as? PledgeShippingLocationCell)
shippingLocationCell?.delegate = self
self.shippingLocationCell = shippingLocationCell
case is PledgeContinueCell:
let pledgeContinueCell = cell as? PledgeContinueCell
pledgeContinueCell?.delegate = self
default:
break
}
Expand Down Expand Up @@ -168,6 +197,12 @@ extension PledgeTableViewController: PledgeShippingLocationCellDelegate {
}
}

extension PledgeTableViewController: PledgeContinueCellDelegate {
func pledgeContinueCellDidTapContinue(_: PledgeContinueCell) {
self.viewModel.inputs.pledgeContinueCellContinueButtonTapped()
}
}

extension PledgeTableViewController: PledgeSummaryCellDelegate {
internal func pledgeSummaryCell(_: PledgeSummaryCell, didOpen helpType: HelpType) {
self.presentHelpWebViewController(with: helpType)
Expand Down
8 changes: 8 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@
77C5E21A214182A2002E1670 /* SettingsPrivacySwitchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77C5E219214182A2002E1670 /* SettingsPrivacySwitchCell.xib */; };
77C5E252214182CA002E1670 /* SettingsPrivacySwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C5E251214182CA002E1670 /* SettingsPrivacySwitchCell.swift */; };
77C7B654226E0E54001101AC /* RewardsCollectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C7B653226E0E54001101AC /* RewardsCollectionViewModelTests.swift */; };
77C93C1A22C13DED005D3195 /* PledgeContinueCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C93C1922C13DED005D3195 /* PledgeContinueCellViewModel.swift */; };
77CB227022C3B3CB00AEAAF1 /* PledgeContinueCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CB226F22C3B3CB00AEAAF1 /* PledgeContinueCellViewModelTests.swift */; };
77C93C2522C27443005D3195 /* DebugData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 775D922822BD7CFD00442301 /* DebugData.swift */; };
77CD894921791B05003066DA /* ProjectStatsTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CD894821791B05003066DA /* ProjectStatsTemplate.swift */; };
77CD8981217FA01B003066DA /* ProjectPamphletContentViewControllerConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CD8980217FA01B003066DA /* ProjectPamphletContentViewControllerConversionTests.swift */; };
Expand Down Expand Up @@ -1502,6 +1504,8 @@
77C5E219214182A2002E1670 /* SettingsPrivacySwitchCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsPrivacySwitchCell.xib; sourceTree = "<group>"; };
77C5E251214182CA002E1670 /* SettingsPrivacySwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacySwitchCell.swift; sourceTree = "<group>"; };
77C7B653226E0E54001101AC /* RewardsCollectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardsCollectionViewModelTests.swift; sourceTree = "<group>"; };
77C93C1922C13DED005D3195 /* PledgeContinueCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgeContinueCellViewModel.swift; sourceTree = "<group>"; };
77CB226F22C3B3CB00AEAAF1 /* PledgeContinueCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgeContinueCellViewModelTests.swift; sourceTree = "<group>"; };
77CD894821791B05003066DA /* ProjectStatsTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectStatsTemplate.swift; sourceTree = "<group>"; };
77CD8980217FA01B003066DA /* ProjectPamphletContentViewControllerConversionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectPamphletContentViewControllerConversionTests.swift; sourceTree = "<group>"; };
77D7A738214187A9003F258C /* SettingsSwitchCellType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSwitchCellType.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3338,6 +3342,8 @@
D66FB347218212B700A27BCC /* PaymentMethodsViewModelTests.swift */,
3706408122A8A66E00889CBD /* PledgeAmountCellViewModel.swift */,
3706408322A8A68600889CBD /* PledgeAmountCellViewModelTests.swift */,
77C93C1922C13DED005D3195 /* PledgeContinueCellViewModel.swift */,
77CB226F22C3B3CB00AEAAF1 /* PledgeContinueCellViewModelTests.swift */,
D7E20EA5228B4AC200BA61A0 /* PledgeCTAContainerViewViewModel.swift */,
D78E038F22930DF80043E92F /* PledgeCTAContainerViewViewModelTests.swift */,
D77594DB226F9C9B005EE69B /* PledgeDescriptionCellViewModel.swift */,
Expand Down Expand Up @@ -4493,6 +4499,7 @@
A707BADA1CFFAB9400653B2F /* Notifications.swift in Sources */,
A721DF651C8CF5A3000CB97C /* KoalaTrackingClient.swift in Sources */,
77556F5420A099B3008CEA57 /* Config+Helpers.swift in Sources */,
77C93C1A22C13DED005D3195 /* PledgeContinueCellViewModel.swift in Sources */,
A76126BB1C90C94000EDCCB9 /* UITableView-Extensions.swift in Sources */,
9D9F58191D13243900CE81DE /* ProjectActivitiesViewModel.swift in Sources */,
A72C3A8E1D00F6A80075227E /* SelectableRow.swift in Sources */,
Expand Down Expand Up @@ -4680,6 +4687,7 @@
A7ED20001E831C5C00BFFA01 /* SearchViewModelTests.swift in Sources */,
D08CD201219216BA009F89F0 /* WatchProjectViewModelTests.swift in Sources */,
A7ED1F291E830FDC00BFFA01 /* EnvironmentTests.swift in Sources */,
77CB227022C3B3CB00AEAAF1 /* PledgeContinueCellViewModelTests.swift in Sources */,
A7ED1F2E1E830FDC00BFFA01 /* LaunchedCountriesTests.swift in Sources */,
A7ED1FAD1E831C5C00BFFA01 /* DeprecatedCheckoutViewModelTests.swift in Sources */,
3705CF48222EE77F0025D37E /* EnvironmentVariablesTests.swift in Sources */,
Expand Down
32 changes: 32 additions & 0 deletions Library/ViewModels/PledgeContinueCellViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import ReactiveSwift

public protocol PledgeContinueCellViewModelOutputs {
var notifyDelegateContinueButtonTapped: Signal<Void, Never> { get }
}

public protocol PledgeContinueCellViewModelInputs {
func continueButtonTapped()
ifbarrera marked this conversation as resolved.
Show resolved Hide resolved
}

public protocol PledgeContinueCellViewModelType {
var inputs: PledgeContinueCellViewModelInputs { get }
var outputs: PledgeContinueCellViewModelOutputs { get }
}

public final class PledgeContinueCellViewModel: PledgeContinueCellViewModelType,
PledgeContinueCellViewModelInputs, PledgeContinueCellViewModelOutputs {
public init() {
self.notifyDelegateContinueButtonTapped = self.continueButtonTappedProperty.signal
}

private let continueButtonTappedProperty = MutableProperty(())
public func continueButtonTapped() {
self.continueButtonTappedProperty.value = ()
}

public let notifyDelegateContinueButtonTapped: Signal<Void, Never>

public var inputs: PledgeContinueCellViewModelInputs { return self }
public var outputs: PledgeContinueCellViewModelOutputs { return self }
}
29 changes: 29 additions & 0 deletions Library/ViewModels/PledgeContinueCellViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@testable import KsApi
@testable import Library
import Prelude
import ReactiveExtensions_TestHelpers
import XCTest

final class PledgeContinueCellViewModelTests: TestCase {
private let vm = PledgeContinueCellViewModel()

private let goToLoginSignup = TestObserver<Void, Never>()

override func setUp() {
super.setUp()

self.vm.outputs.notifyDelegateContinueButtonTapped.observe(self.goToLoginSignup.observer)
}

func testGoToLoginSignup() {
self.goToLoginSignup.assertDidNotEmitValue()

self.vm.inputs.continueButtonTapped()

self.goToLoginSignup.assertValueCount(1)

self.vm.inputs.continueButtonTapped()

self.goToLoginSignup.assertValueCount(2)
}
}
39 changes: 36 additions & 3 deletions Library/ViewModels/PledgeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ public typealias PledgeViewData = (

public protocol PledgeViewModelInputs {
func configureWith(project: Project, reward: Reward)
func pledgeContinueCellContinueButtonTapped()
func userSessionStarted()
func pledgeAmountDidUpdate(to amount: Double)
func pledgeShippingCellWillPresentShippingRules(with rule: ShippingRule)
func shippingRuleDidUpdate(to rule: ShippingRule)
func viewDidLoad()
}

public protocol PledgeViewModelOutputs {
var goToLoginSignup: Signal<LoginIntent, Never> { get }
var configureShippingLocationCellWithData: Signal<(Bool, Project, ShippingRule?), Never> { get }
var configureSummaryCellWithData: Signal<(Project, Double), Never> { get }

Expand Down Expand Up @@ -106,16 +109,34 @@ public class PledgeViewModel: PledgeViewModelType, PledgeViewModelInputs, Pledge
}

let pledgeTotal = Signal.combineLatest(pledgeAmount, shippingAmount).map(+)

let data = Signal
.combineLatest(project, reward, isLoggedIn, isShippingLoading, defaultShippingRule, pledgeTotal)
.map(pledgeViewData(with:reward:isLoggedIn:isShippingLoading:selectedShippingRule:pledgeTotal:))

let userUpdatedData = data.takeWhen(self.userSessionStartedSignal)
.map { data -> PledgeViewData in
(
project: data.project,
reward: data.reward,
isLoggedIn: AppEnvironment.current.currentUser != nil,
shipping: data.shipping,
pledgeTotal: data.pledgeTotal
)
}

let initialLoad = data.take(first: 1).map { data in (data, true) }
let silentReload = data.skip(first: 1).map { data in (data, false) }
let userUpdatedReload = userUpdatedData.map { data in (data, true) }

self.pledgeViewDataAndReload = Signal.merge(
data.take(first: 1).map { data in (data, true) },
data.skip(first: 1).map { data in (data, false) }
initialLoad,
silentReload,
userUpdatedReload
)

self.goToLoginSignup = self.pledgeContinueCellContinueButtonTappedSignal
.map { _ in LoginIntent.backProject }

let updatedData = self.pledgeViewDataAndReload
.filter(second >>> isFalse)
.map(first)
Expand All @@ -137,11 +158,22 @@ public class PledgeViewModel: PledgeViewModelType, PledgeViewModelInputs, Pledge
}
}

private let (pledgeContinueCellContinueButtonTappedSignal, pledgeContinueCellContinueButtonTappedObserver)
= Signal<Void, Never>.pipe()
public func pledgeContinueCellContinueButtonTapped() {
self.pledgeContinueCellContinueButtonTappedObserver.send(value: ())
}

private let configureProjectAndRewardProperty = MutableProperty<(Project, Reward)?>(nil)
public func configureWith(project: Project, reward: Reward) {
self.configureProjectAndRewardProperty.value = (project, reward)
}

private let (userSessionStartedSignal, userSessionStartedObserver) = Signal<Void, Never>.pipe()
public func userSessionStarted() {
self.userSessionStartedObserver.send(value: ())
}

private let (pledgeAmountSignal, pledgeAmountObserver) = Signal<Double, Never>.pipe()
public func pledgeAmountDidUpdate(to amount: Double) {
self.pledgeAmountObserver.send(value: amount)
Expand All @@ -162,6 +194,7 @@ public class PledgeViewModel: PledgeViewModelType, PledgeViewModelInputs, Pledge
self.viewDidLoadProperty.value = ()
}

public let goToLoginSignup: Signal<LoginIntent, Never>
public let configureShippingLocationCellWithData: Signal<(Bool, Project, ShippingRule?), Never>
public let configureSummaryCellWithData: Signal<(Project, Double), Never>
public let pledgeViewDataAndReload: Signal<(PledgeViewData, Bool), Never>
Expand Down
38 changes: 36 additions & 2 deletions Library/ViewModels/PledgeViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import XCTest

// swiftlint:disable line_length
final class PledgeViewModelTests: TestCase {
private var vm: PledgeViewModelType!
private let vm: PledgeViewModelType = PledgeViewModel()

private let configureShippingLocationCellWithDataIsShippingRulesLoading = TestObserver<Bool, Never>()
private let configureShippingLocationCellWithDataProject = TestObserver<Project, Never>()
Expand All @@ -18,6 +18,8 @@ final class PledgeViewModelTests: TestCase {
private let configureSummaryCellWithDataPledgeTotal = TestObserver<Double, Never>()
private let configureSummaryCellWithDataProject = TestObserver<Project, Never>()

private let goToLoginSignup = TestObserver<LoginIntent, Never>()

/**
Given the noise of `pledgeViewDataAndReload` signal and its frequent emissions and also the fact that
what we really care about is the final emission from this signal, we mostly test its last value.
Expand All @@ -40,7 +42,7 @@ final class PledgeViewModelTests: TestCase {
override func setUp() {
super.setUp()

self.vm = PledgeViewModel()
self.vm.outputs.goToLoginSignup.observe(self.goToLoginSignup.observer)

self.vm.outputs.configureShippingLocationCellWithData.map { $0.0 }.observe(self.configureShippingLocationCellWithDataIsShippingRulesLoading.observer)
self.vm.outputs.configureShippingLocationCellWithData.map { $0.1 }.observe(self.configureShippingLocationCellWithDataProject.observer)
Expand Down Expand Up @@ -115,6 +117,38 @@ final class PledgeViewModelTests: TestCase {
}
}

func testLoginSignup() {
let project = Project.template
let reward = Reward.template
let user = User.template

self.vm.inputs.configureWith(project: project, reward: reward)
self.vm.inputs.viewDidLoad()

self.pledgeViewDataAndReloadProject.assertValues([project])
self.pledgeViewDataAndReloadReward.assertValues([reward])
self.pledgeViewDataAndReloadIsLoggedIn.assertValues([false])
self.pledgeViewDataAndReloadIsShippingEnabled.assertValues([false])
self.pledgeViewDataAndReloadSelectedShippingRule.assertValue(nil)
self.pledgeViewDataAndReloadTotal.assertValues([reward.minimum])
self.pledgeViewDataAndReloadReload.assertValues([true])

self.vm.inputs.pledgeContinueCellContinueButtonTapped()

self.goToLoginSignup.assertValue(LoginIntent.backProject)

withEnvironment(currentUser: user) {
self.vm.inputs.userSessionStarted()

self.pledgeViewDataAndReloadProject.assertValues([project, project])
self.pledgeViewDataAndReloadReward.assertValues([reward, reward])
self.pledgeViewDataAndReloadIsLoggedIn.assertValues([false, true])
self.pledgeViewDataAndReloadIsShippingEnabled.assertValues([false, false])
self.pledgeViewDataAndReloadTotal.assertValues([reward.minimum, reward.minimum])
self.pledgeViewDataAndReloadReload.assertValues([true, true])
}
}

func testSelectedShippingRule() {
let project = Project.template
let reward = Reward.template
Expand Down