diff --git a/Kickstarter-iOS/DataSources/PledgeDataSource.swift b/Kickstarter-iOS/DataSources/PledgeDataSource.swift index db4e7c6bdc..566cce8a06 100644 --- a/Kickstarter-iOS/DataSources/PledgeDataSource.swift +++ b/Kickstarter-iOS/DataSources/PledgeDataSource.swift @@ -9,7 +9,7 @@ final class PledgeDataSource: ValueCellDataSource { case summary } - func load(amount: Double, currency: String, delivery: String) { + func load(amount: Double, currency: String, delivery: String, isLoggedIn: Bool) { self.appendRow( value: delivery, cellClass: PledgeDescriptionCell.self, @@ -28,11 +28,21 @@ final class PledgeDataSource: ValueCellDataSource { toSection: Section.inputs.rawValue ) + self.loadSummarySection(isLoggedIn: isLoggedIn) + } + + private func loadSummarySection(isLoggedIn: Bool) { self.appendRow( value: "Total", cellClass: PledgeRowCell.self, toSection: Section.summary.rawValue ) + + if !isLoggedIn { + self.appendRow(value: (), + cellClass: PledgeContinueCell.self, + toSection: Section.summary.rawValue) + } } override func configureCell(tableCell cell: UITableViewCell, withValue value: Any) { @@ -45,6 +55,8 @@ final class PledgeDataSource: ValueCellDataSource { cell.configureWith(value: value) case let (cell as PledgeShippingLocationCell, value as (String, String, Double)): cell.configureWith(value: value) + case let (cell as PledgeContinueCell, value as ()): + cell.configureWith(value: value) default: assertionFailure("Unrecognized (cell, viewModel) combo.") } diff --git a/Kickstarter-iOS/DataSources/PledgeDataSourceTests.swift b/Kickstarter-iOS/DataSources/PledgeDataSourceTests.swift index ec84a9b6fd..f1eea5ccec 100644 --- a/Kickstarter-iOS/DataSources/PledgeDataSourceTests.swift +++ b/Kickstarter-iOS/DataSources/PledgeDataSourceTests.swift @@ -8,9 +8,8 @@ final class PledgeDataSourceTests: XCTestCase { let tableView = UITableView(frame: .zero, style: .plain) // swiftlint:disable line_length - func testLoad() { - - self.dataSource.load(amount: 100, currency: "USD", delivery: "May 2020") + func testLoad_loggedIn() { + self.dataSource.load(amount: 100, currency: "USD", delivery: "May 2020", isLoggedIn: true) XCTAssertEqual(3, self.dataSource.numberOfSections(in: self.tableView)) XCTAssertEqual(1, self.dataSource.tableView(self.tableView, numberOfRowsInSection: 0)) @@ -21,5 +20,19 @@ final class PledgeDataSourceTests: XCTestCase { XCTAssertEqual(PledgeShippingLocationCell.defaultReusableId, self.dataSource.reusableId(item: 1, section: 1)) XCTAssertEqual(PledgeRowCell.defaultReusableId, self.dataSource.reusableId(item: 0, section: 2)) } + + func testLoad_loggedOut() { + self.dataSource.load(amount: 100, currency: "USD", delivery: "May 2020", isLoggedIn: false) + + XCTAssertEqual(3, self.dataSource.numberOfSections(in: self.tableView)) + XCTAssertEqual(1, self.dataSource.tableView(self.tableView, numberOfRowsInSection: 0)) + XCTAssertEqual(2, self.dataSource.tableView(self.tableView, numberOfRowsInSection: 1)) + XCTAssertEqual(2, self.dataSource.tableView(self.tableView, numberOfRowsInSection: 2)) + XCTAssertEqual(PledgeDescriptionCell.defaultReusableId, self.dataSource.reusableId(item: 0, section: 0)) + XCTAssertEqual(PledgeAmountCell.defaultReusableId, self.dataSource.reusableId(item: 0, section: 1)) + XCTAssertEqual(PledgeShippingLocationCell.defaultReusableId, self.dataSource.reusableId(item: 1, section: 1)) + XCTAssertEqual(PledgeRowCell.defaultReusableId, self.dataSource.reusableId(item: 0, section: 2)) + XCTAssertEqual(PledgeContinueCell.defaultReusableId, self.dataSource.reusableId(item: 1, section: 2)) + } // swiftlint:enable line_length } diff --git a/Kickstarter-iOS/Views/Cells/PledgeContinueCell.swift b/Kickstarter-iOS/Views/Cells/PledgeContinueCell.swift new file mode 100644 index 0000000000..d55d3f1dfc --- /dev/null +++ b/Kickstarter-iOS/Views/Cells/PledgeContinueCell.swift @@ -0,0 +1,46 @@ +import Foundation +import Library +import Prelude + +final class PledgeContinueCell: UITableViewCell, ValueCell { + private let continueButton = MultiLineButton(type: .custom) + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + self.setupSubviews() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func bindStyles() { + super.bindStyles() + + _ = self + |> \.backgroundColor .~ .ksr_grey_300 + + _ = self.contentView + |> \.layoutMargins .~ .init(all: Styles.grid(3)) + + _ = self.continueButton + |> checkoutGreenButtonStyle + |> UIButton.lens.title(for: .normal) %~ { _ in + return Strings.Continue() + } + + _ = self.continueButton.titleLabel + ?|> checkoutGreenButtonTitleLabelStyle + } + + func configureWith(value: ()) {} + + private func setupSubviews() { + _ = (self.continueButton, self.contentView) + |> ksr_addSubviewToParent() + |> ksr_constrainViewToMarginsInParent() + + self.continueButton.heightAnchor.constraint(greaterThanOrEqualToConstant: Styles.grid(8)).isActive = true + } +} diff --git a/Kickstarter-iOS/Views/Controllers/PledgeTableViewController.swift b/Kickstarter-iOS/Views/Controllers/PledgeTableViewController.swift index 359a4eb43d..9070ad8cd3 100644 --- a/Kickstarter-iOS/Views/Controllers/PledgeTableViewController.swift +++ b/Kickstarter-iOS/Views/Controllers/PledgeTableViewController.swift @@ -26,6 +26,7 @@ class PledgeTableViewController: UITableViewController { |> \.dataSource .~ self.dataSource self.tableView.registerCellClass(PledgeAmountCell.self) + self.tableView.registerCellClass(PledgeContinueCell.self) self.tableView.registerCellClass(PledgeDescriptionCell.self) self.tableView.registerCellClass(PledgeRowCell.self) self.tableView.registerCellClass(PledgeShippingLocationCell.self) @@ -48,10 +49,11 @@ class PledgeTableViewController: UITableViewController { override func bindViewModel() { super.bindViewModel() - self.viewModel.outputs.amountCurrencyAndDelivery + self.viewModel.outputs.reloadWithData .observeForUI() - .observeValues { [weak self] (amount, currency, delivery) in - self?.dataSource.load(amount: amount, currency: currency, delivery: delivery) + .observeValues { [weak self] (amount, currency, delivery, isLoggedIn) in + self?.dataSource.load(amount: amount, currency: currency, delivery: delivery, isLoggedIn: isLoggedIn) + self?.tableView.reloadData() } } diff --git a/Kickstarter-iOS/Views/Controllers/ProjectPamphletViewController.swift b/Kickstarter-iOS/Views/Controllers/ProjectPamphletViewController.swift index 41ef6f5bb2..582a72b201 100644 --- a/Kickstarter-iOS/Views/Controllers/ProjectPamphletViewController.swift +++ b/Kickstarter-iOS/Views/Controllers/ProjectPamphletViewController.swift @@ -138,10 +138,13 @@ public final class ProjectPamphletViewController: UIViewController { |> \.layoutMargins .~ .init(all: backThisProjectContainerViewMargins) _ = self.backThisProjectButton - |> backThisProjectButtonStyle + |> checkoutGreenButtonStyle + |> UIButton.lens.title(for: .normal) %~ { _ in + return Strings.project_back_button() + } _ = self.backThisProjectButton.titleLabel - ?|> backThisProjectButtonTitleLabelStyle + ?|> checkoutGreenButtonTitleLabelStyle } public override func bindViewModel() { @@ -273,29 +276,3 @@ extension ProjectPamphletViewController: ProjectNavBarViewControllerDelegate { self.contentController.tableView.scrollToTop() } } - -// MARK: - Styles - -private var backThisProjectButtonStyle = { (button: UIButton) -> UIButton in - button - |> greenButtonStyle - |> roundedStyle(cornerRadius: 12) - |> UIButton.lens.layer.borderWidth .~ 0 - |> UIButton.lens.titleEdgeInsets .~ .init(topBottom: Styles.grid(1), leftRight: Styles.grid(2)) - |> UIButton.lens.title(for: .normal) %~ { _ in - return Strings.project_back_button() - } -} - -private var backThisProjectButtonTitleLabelStyle = { (titleLabel: UILabel?) -> UILabel? in - _ = titleLabel - ?|> \.font .~ UIFont.ksr_headline() - ?|> \.numberOfLines .~ 0 - - // Breaking this up to help the compiler - _ = titleLabel - ?|> \.textAlignment .~ NSTextAlignment.center - ?|> \.lineBreakMode .~ NSLineBreakMode.byWordWrapping - - return titleLabel -} diff --git a/Kickstarter.xcodeproj/project.pbxproj b/Kickstarter.xcodeproj/project.pbxproj index 16bc2b078c..eb79cc08ac 100644 --- a/Kickstarter.xcodeproj/project.pbxproj +++ b/Kickstarter.xcodeproj/project.pbxproj @@ -175,7 +175,6 @@ 774A76EE20D863EF0012A71F /* BetaTools.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 774A76AF20D83F6B0012A71F /* BetaTools.storyboard */; }; 774A76F520D98EEF0012A71F /* BetaToolsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774A76F420D98EEF0012A71F /* BetaToolsViewControllerTests.swift */; }; 774ACC19225F8DC30097FCE6 /* ProjectPamphletViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774ACC18225F8DC30097FCE6 /* ProjectPamphletViewControllerTests.swift */; }; - 77502FC22147D92000620BBC /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775229A72146805400595846 /* Stripe.framework */; }; 77539E6C2147E4EE00A564CD /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775229A72146805400595846 /* Stripe.framework */; }; 77539E6D2147E50E00A564CD /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 775229A72146805400595846 /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7754A0AE215A8361003AA36D /* ChangePasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7754A0AD215A8361003AA36D /* ChangePasswordViewController.swift */; }; @@ -220,6 +219,7 @@ 77E6440120F64F0B005F6B38 /* HelpDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E6440020F64F0B005F6B38 /* HelpDataSource.swift */; }; 77E6440320F65074005F6B38 /* HelpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E6440220F65074005F6B38 /* HelpViewController.swift */; }; 77E84E0C2166A8C600DA8891 /* MessageBannerViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77E84E0B2166A8C600DA8891 /* MessageBannerViewControllerTests.swift */; }; + 77ED5200227A004700DAAD13 /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775229A72146805400595846 /* Stripe.framework */; }; 77EFBAA62268D32000DA5C3C /* RewardsCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77EFBAA52268D32000DA5C3C /* RewardsCollectionViewController.swift */; }; 77EFBAE02268D34A00DA5C3C /* RewardsCollectionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77EFBADE2268D33E00DA5C3C /* RewardsCollectionViewControllerTests.swift */; }; 77EFBAE32268D48A00DA5C3C /* RewardsCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77EFBAE12268D44D00DA5C3C /* RewardsCollectionViewModel.swift */; }; @@ -237,6 +237,7 @@ 77FA6CAB20F3E3C200809E31 /* SettingsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77FA6CAA20F3E3C200809E31 /* SettingsTableViewCell.xib */; }; 77FA6CD220F53E5E00809E31 /* SettingsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FA6CD120F53E5E00809E31 /* SettingsDataSourceTests.swift */; }; 77FB8BF720F6586500F91EEB /* SettingsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77FB8BF620F6586500F91EEB /* SettingsHeaderView.xib */; }; + 77FD60A122735AED0084B84C /* PledgeContinueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FD60A022735AED0084B84C /* PledgeContinueCell.swift */; }; 77FD8B44216D6167000A95AC /* LoadingBarButtonItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77FD8B43216D6167000A95AC /* LoadingBarButtonItemView.xib */; }; 77FD8B46216D6245000A95AC /* LoadingBarButtonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FD8B45216D6245000A95AC /* LoadingBarButtonItemView.swift */; }; 7DD8EE241F02DBED0070B63D /* Bolts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DD8EE231F02DBED0070B63D /* Bolts.framework */; }; @@ -2058,6 +2059,7 @@ 77FA6CAA20F3E3C200809E31 /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = ""; }; 77FA6CD120F53E5E00809E31 /* SettingsDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDataSourceTests.swift; sourceTree = ""; }; 77FB8BF620F6586500F91EEB /* SettingsHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsHeaderView.xib; sourceTree = ""; }; + 77FD60A022735AED0084B84C /* PledgeContinueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgeContinueCell.swift; sourceTree = ""; }; 77FD8B43216D6167000A95AC /* LoadingBarButtonItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LoadingBarButtonItemView.xib; sourceTree = ""; }; 77FD8B45216D6245000A95AC /* LoadingBarButtonItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingBarButtonItemView.swift; sourceTree = ""; }; 7DD8EE231F02DBED0070B63D /* Bolts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Bolts.framework; path = Frameworks/FBSDK/iOS/Bolts.framework; sourceTree = ""; }; @@ -3023,8 +3025,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 77502FC22147D92000620BBC /* Stripe.framework in Frameworks */, A701B5EF1EB384E1001D4E5F /* PassKit.framework in Frameworks */, + 77ED5200227A004700DAAD13 /* Stripe.framework in Frameworks */, D0D2ABA21E964852008D298A /* libsqlite3.tbd in Frameworks */, D0D2AB5B1E964849008D298A /* CoreMedia.framework in Frameworks */, A709698C1D1468A200DB39D3 /* Alamofire.framework in Frameworks */, @@ -3457,6 +3459,8 @@ A773531E1D5E8AEF0017E239 /* MostPopularSearchProjectCell.swift */, A7B1EBB21D90496A00BEE8B3 /* NoRewardCell.swift */, A74382041D3458C900040A95 /* PaddingCell.swift */, + 77FD60A022735AED0084B84C /* PledgeContinueCell.swift */, + 37DEC22D2257CDD50051EF9B /* PledgeRowCell.swift */, 370ACAFF225D337900C8745F /* PledgeAmountCell.swift */, D79A054A225E9B6B004BC6A8 /* PledgeDescriptionCell.swift */, 37DEC22D2257CDD50051EF9B /* PledgeRowCell.swift */, @@ -6386,6 +6390,7 @@ D08A817C1DFAAD08000128DB /* LiveStreamCountdownViewController.swift in Sources */, A773531F1D5E8AEF0017E239 /* MostPopularSearchProjectCell.swift in Sources */, 37E9E2A0225EABB000D29DD7 /* AmountInputView.swift in Sources */, + 77FD60A122735AED0084B84C /* PledgeContinueCell.swift in Sources */, A77352ED1D5E70FC0017E239 /* MostPopularCell.swift in Sources */, D79F0F3721028C2600D3B32C /* SettingsPrivacyRecommendationCell.swift in Sources */, D6B6766920FF8D850082717D /* SettingsNewslettersDataSource.swift in Sources */, diff --git a/Library/Styles/CheckoutStyles.swift b/Library/Styles/CheckoutStyles.swift index 446368196d..6ea0a59080 100644 --- a/Library/Styles/CheckoutStyles.swift +++ b/Library/Styles/CheckoutStyles.swift @@ -16,6 +16,26 @@ public func checkoutAdaptableStackViewStyle(_ isAccessibilityCategory: Bool) -> } } +public let checkoutGreenButtonStyle: ButtonStyle = { button -> UIButton in + button + |> greenButtonStyle + |> roundedStyle(cornerRadius: 12) + |> UIButton.lens.layer.borderWidth .~ 0 + |> UIButton.lens.titleEdgeInsets .~ .init(topBottom: Styles.grid(1), leftRight: Styles.grid(2)) +} + +public let checkoutGreenButtonTitleLabelStyle = { (titleLabel: UILabel?) -> UILabel? in + _ = titleLabel + ?|> \.font .~ UIFont.ksr_headline() + ?|> \.numberOfLines .~ 0 + + _ = titleLabel + ?|> \.textAlignment .~ NSTextAlignment.center + ?|> \.lineBreakMode .~ NSLineBreakMode.byWordWrapping + + return titleLabel +} + public let checkoutBackgroundStyle: ViewStyle = { (view: UIView) in view |> \.backgroundColor .~ UIColor.ksr_grey_300 diff --git a/Library/ViewModels/PledgeViewModel.swift b/Library/ViewModels/PledgeViewModel.swift index 5deeb1beee..10a3f904f9 100644 --- a/Library/ViewModels/PledgeViewModel.swift +++ b/Library/ViewModels/PledgeViewModel.swift @@ -4,13 +4,15 @@ import Prelude import ReactiveSwift import Result +public typealias PledgeTableViewData = (amount: Double, currency: String, delivery: String, isLoggedIn: Bool) + public protocol PledgeViewModelInputs { func configureWith(project: Project, reward: Reward) func viewDidLoad() } public protocol PledgeViewModelOutputs { - var amountCurrencyAndDelivery: Signal<(Double, String, String), NoError> { get } + var reloadWithData: Signal { get } } public protocol PledgeViewModelType { @@ -26,12 +28,23 @@ public class PledgeViewModel: PledgeViewModelType, PledgeViewModelInputs, Pledge .map(first) .skipNil() - self.amountCurrencyAndDelivery = projectAndReward.signal + let isLoggedIn = projectAndReward + .map { _ in AppEnvironment.current.currentUser } + .map(isNotNil) + + let amountCurrencyDelivery = projectAndReward.signal .map { (project, reward) in (reward.minimum, currencySymbol(forCountry: project.country).trimmed(), reward.estimatedDeliveryOn .map { Format.date(secondsInUTC: $0, template: "MMMMyyyy", timeZone: UTCTimeZone) } ?? "") } + + self.reloadWithData = Signal.combineLatest(amountCurrencyDelivery, isLoggedIn) + .map { amountCurrencyDelivery, isLoggedIn in + let (amount, currency, delivery) = amountCurrencyDelivery + + return (amount, currency, delivery, isLoggedIn) + } } private let configureProjectAndRewardProperty = MutableProperty<(Project, Reward)?>(nil) @@ -44,7 +57,7 @@ public class PledgeViewModel: PledgeViewModelType, PledgeViewModelInputs, Pledge self.viewDidLoadProperty.value = () } - public let amountCurrencyAndDelivery: Signal<(Double, String, String), NoError> + public let reloadWithData: Signal public var inputs: PledgeViewModelInputs { return self } public var outputs: PledgeViewModelOutputs { return self } diff --git a/Library/ViewModels/PledgeViewModelTests.swift b/Library/ViewModels/PledgeViewModelTests.swift index 33dca8e30d..34a88455b5 100644 --- a/Library/ViewModels/PledgeViewModelTests.swift +++ b/Library/ViewModels/PledgeViewModelTests.swift @@ -14,30 +14,49 @@ final class PledgeViewModelTests: TestCase { private let amount = TestObserver() private let currency = TestObserver() + private let isLoggedIn = TestObserver() private let estimatedDelivery = TestObserver() override func setUp() { super.setUp() - self.vm.outputs.amountCurrencyAndDelivery.map { $0.0 }.observe(self.amount.observer) - self.vm.outputs.amountCurrencyAndDelivery.map { $0.1 }.observe(self.currency.observer) - self.vm.outputs.amountCurrencyAndDelivery.map { $0.2 }.observe(self.estimatedDelivery.observer) + self.vm.outputs.reloadWithData.map { $0.amount }.observe(self.amount.observer) + self.vm.outputs.reloadWithData.map { $0.currency }.observe(self.currency.observer) + self.vm.outputs.reloadWithData.map { $0.isLoggedIn }.observe(self.isLoggedIn.observer) + self.vm.outputs.reloadWithData.map { $0.delivery }.observe(self.estimatedDelivery.observer) } - func testAmountCurrencyAndEstimatedDeliveryDate() { - let estimatedDelivery = 1468527587.32843 + func testReloadWithData_loggedOut() { + let project = Project.template + let reward = Reward.template + + withEnvironment(currentUser: nil) { + self.vm.inputs.configureWith(project: project, reward: reward) + self.vm.inputs.viewDidLoad() + + self.isLoggedIn.assertValues([false]) + } + } + + func testReloadWithData_loggedIn() { + let estimatedDelivery = 1468527587.32843 let project = Project.template let reward = Reward.template |> Reward.lens.estimatedDeliveryOn .~ estimatedDelivery - self.vm.inputs.configureWith(project: project, reward: reward) - self.vm.inputs.viewDidLoad() + let user = User.template + + withEnvironment(currentUser: user) { + self.vm.inputs.configureWith(project: project, reward: reward) + self.vm.inputs.viewDidLoad() - self.amount.assertValues([10]) - self.currency.assertValues(["$"]) - self.estimatedDelivery.assertValues( - [Format.date(secondsInUTC: estimatedDelivery, template: "MMMMyyyy", timeZone: UTCTimeZone)] - ) + self.amount.assertValues([10]) + self.currency.assertValues(["$"]) + self.isLoggedIn.assertValues([true]) + self.estimatedDelivery.assertValues( + [Format.date(secondsInUTC: estimatedDelivery, template: "MMMMyyyy", timeZone: UTCTimeZone)] + ) + } } }