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

[NT-280] Manage pledge screen from CTA container #831

Merged
merged 13 commits into from
Sep 12, 2019

Conversation

Scollaco
Copy link
Contributor

@Scollaco Scollaco commented Sep 9, 2019

πŸ“² What

  • Presents the ManageViewPledge screen (blank for now) from the cta container.

πŸ€” Why

  • This was decided to make the access to the screen easier, compared to what was proposed previously.

πŸ›  How

  • Moved the code from RewardsCollectionViewController to ProjectPamphletViewController
  • Moved tests to the classes to be tested

πŸ‘€ See

Managing πŸ› Viewing Pledge πŸ¦‹
manage_pledge_cta view_pledge_cta

βœ… Acceptance criteria

With featureNativeCheckoutPledgeView enabled.

  • Select a non-backed project and tap the Back this project button. The Rewards Carousel should be presented.
  • Select a backed project and tap the Manage button. The ManageViewPledgeViewController (blank) should be presented.
  • Selected a backed and ended project and tap the View your pledge button. The ManageViewPledgeViewController (blank) should be presented.

Note:

By tapping to view your pledge, note that the title shown is Manage your pledge. The logic to change the title based of the pledge and project states will be implemented in a different PR.

With featureNativeCheckoutPledgeView disabled.

On the Rewards Carousel screen:

  • Select a non-backed project and tap the Back this project button. The Rewards Carousel should be presented.
  • Select a backed project and tap the Manage button. The DeprecatedRewardPledgeViewController should be presented.
  • Selected a backed and ended project and tap the View your pledge button. The BackingViewController should be presented.

@ksr-ci-bot
Copy link

ksr-ci-bot commented Sep 9, 2019

1 Warning
⚠️ Big PR

SwiftFormat found issues:

File Rules
Library/ViewModels/ProjectPamphletViewModel.swift spaceAroundOperators, wrapArguments

Generated by 🚫 Danger

Copy link
Contributor

@dusi dusi left a comment

Choose a reason for hiding this comment

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

Mostly questions

@@ -135,6 +135,26 @@ public final class ProjectPamphletViewController: UIViewController {
self?.goToRewards(project: project, refTag: refTag)
}

self.viewModel.outputs.goToManageViewPledge
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm seeing a lot of inconsistencies between the naming of VMs output signal and the private action on the controller...do you think it would be worth it cleaning this up..

So for example:

self.viewModel.outputs.goToManageViewPledge would trigger self?.goToManageViewPledge, etc...

For the deprecated ones I'd suggest we simply add it as a suffix:

self.viewModel.outputs.goToViewBackingDeprecated links to self?.goToViewBacking...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It makes sense to change the private actions to match the name of the output. Will change that.

Regarding the deprecated naming, I'm following the naming convention that we adopted for deprecated classes (DeprecatedCheckoutViewModel, DeprecatedRewardPledgeViewController, DeprecatedRewardCell etc)

.configuredWith(
project: project, reward: reward
)
pledgeViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason we've decided to set this externally on DeprecatedRewardPledgeViewController?

If we do this inside of the controller and set self.navigationItem (like we do everywhere else we will be able to remove this code as well the identical code in ProjectPamphletContentViewController.

But maybe I'm missing the reason here? πŸ€”

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On a rush to unblock the other tickets I had copied the code from somewhere else. Good catch.

if AppEnvironment.current.device.userInterfaceIdiom == .pad {
let nav = UINavigationController(rootViewController: backingViewController)
|> \.modalPresentationStyle .~ .formSheet
self.present(nav, animated: true, completion: nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we omit completion handler?

|> \.modalPresentationStyle .~ .formSheet
self.present(nav, animated: true, completion: nil)
} else {
self.navigationController?.pushViewController(backingViewController, animated: true)
Copy link
Contributor

Choose a reason for hiding this comment

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

So, I know we've previously discussed the fact that we're presenting manage screen over the previous context using .formSheet...but do we really want to push on non-iPad devices? Just trying to understand the decision behind this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The from BackingViewController, we can access Messages (the vc's navigation controller pushes the MessagesViewController). If we present the BackingViewController modally instead, we are no longer able to see the messages, since it requires a navigation controller to work

@@ -4,7 +4,7 @@ import Prelude
import UIKit

protocol PledgeCTAContainerViewDelegate: AnyObject {
func pledgeCTAButtonTapped()
func pledgeCTAButtonTapped(_ state: PledgeStateCTAType)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe with state?

func pledgeCTAButtonTapped(with state: PledgeStateCTAType)

then we call it with more context like so

self?.delegate?.pledgeCTAButtonTapped(with: state)

Dunno which one is better Β―_(ツ)_/Β―

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Me gusta :)

@@ -34,6 +34,12 @@ public protocol ProjectPamphletViewModelOutputs {
/// Emits a (project, isLoading) tuple used to configure the pledge CTA view
var configurePledgeCTAView: Signal<(Either<Project, ErrorEnvelope>, Bool), Never> { get }

var goToDeprecatedManagePledge: Signal<PledgeData, Never> { get }

var goToDeprecatedViewBacking: Signal<(Project, User?), Never> { get }
Copy link
Contributor

Choose a reason for hiding this comment

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

I know we're deprecating stuff but looking at these parameters I wonder if we can improve this by declaring BackingData typealias, etc. for (Project, User?) tuple

Just a suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea

.map(unpack)

let goToManagePledge = ctaButtonTapped
.filter(canShowManageViewPledgeScreen(_:_:state:))
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like canShowManageViewPledgeScreen does not need refTag? Could omit and have more readable call site canShowManageViewPledgeScreen(_:state:)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem with that is ctaButtonTapped emits (project, refTag, state). If we omit the refTag, the compiler will complain because the types emitted don't match the function arguments, thus the point free syntax will no longer be possible.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see!

Thanks for clarifying this...I don't know whether a stylistic thing like being able to call something with point-free expression would overweight the code debt of passing argument that's not necessary.

In this case, I'd consider whether non-point-free expression would be more suitable (less code + less debt).

.filter { (project, _, state) in canShowManageViewPledgeScreen(project, state:state) }

or

.filter { canShowManageViewPledgeScreen($0.0, state:$0.2) }

let goToManagePledge = ctaButtonTapped
.filter(canShowManageViewPledgeScreen(_:_:state:))
.map { (project, refTag, _) -> PledgeData in
let r = reward(from: project.personalization.backing, inProject: project)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we name this reward (it should fit on the screen and be more descriptive)?

Copy link
Contributor Author

@Scollaco Scollaco Sep 11, 2019

Choose a reason for hiding this comment

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

That's what I did first, but kept getting the error Variable used within its own initial value . Any suggestions?

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, good point!

How about something like this:

.map { (project, refTag, _) -> PledgeData in
        PledgeData(
          project: project,
          reward: reward(from: project.personalization.backing, inProject: project),
          refTag: refTag
        )
      }

or

.map {
        PledgeData(
          project: $0.0, reward: reward(from: $0.0.personalization.backing, inProject: $0.0), refTag: $0.1
        )
      }

(but that might be more than confusing due to the repetition of those variables)

Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is a language limitation I'd be OK with leaving it as is .. your decision! πŸ₯‚

.filter { _ in featureNativeCheckoutPledgeViewIsEnabled() }

self.goToDeprecatedManagePledge = ctaButtonTapped
.filter(shouldGoToDeprecatedManagePledge(_:_:state:))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing with refTag...is this because we'll need it in the future but don't need it just now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nah... we will only need the refTag for the manage pledge context. Here it's possibleto change because we don't need the refTag, so we can map the value creating a new tuple without it before filtering:

self.goToDeprecatedViewBacking = ctaButtonTapped
      .map { (project, _, state) in (project, state) }
      .filter(shouldGoToDeprecatedViewBacking(_:state:))
      .map { project, _ in
        return BackingData(project, AppEnvironment.current.currentUser)
      }

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I'd probably try to carry over as little unused things as possible..this makes it easier to reason about code and will be less confusing in the future (read in a week when we forget WTF was this) :D

self.goToDeprecatedManagePledge = ctaButtonTapped
.filter(shouldGoToDeprecatedManagePledge(_:_:state:))
.map { (project, refTag, _) -> PledgeData in
let r = reward(from: project.personalization.backing, inProject: project)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same for the naming

Copy link
Contributor

@dusi dusi left a comment

Choose a reason for hiding this comment

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

Couple additional comments..starting to look good!!!

func pledgeCTAButtonTapped() {
self.viewModel.inputs.backThisProjectTapped()
func pledgeCTAButtonTapped(with state: PledgeStateCTAType) {
self.viewModel.inputs.pledgeCTAButtonTapped(state)
Copy link
Contributor

Choose a reason for hiding this comment

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

We're keeping the input this way? I thought we would also make it more descriptive (i.e. input.pledgeCTAButtonTapped(with: state)?

.map(unpack)

let goToManagePledge = ctaButtonTapped
.filter(canShowManageViewPledgeScreen(_:_:state:))
Copy link
Contributor

Choose a reason for hiding this comment

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

I see!

Thanks for clarifying this...I don't know whether a stylistic thing like being able to call something with point-free expression would overweight the code debt of passing argument that's not necessary.

In this case, I'd consider whether non-point-free expression would be more suitable (less code + less debt).

.filter { (project, _, state) in canShowManageViewPledgeScreen(project, state:state) }

or

.filter { canShowManageViewPledgeScreen($0.0, state:$0.2) }

let goToManagePledge = ctaButtonTapped
.filter(canShowManageViewPledgeScreen(_:_:state:))
.map { (project, refTag, _) -> PledgeData in
let r = reward(from: project.personalization.backing, inProject: project)
Copy link
Contributor

Choose a reason for hiding this comment

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

Right, good point!

How about something like this:

.map { (project, refTag, _) -> PledgeData in
        PledgeData(
          project: project,
          reward: reward(from: project.personalization.backing, inProject: project),
          refTag: refTag
        )
      }

or

.map {
        PledgeData(
          project: $0.0, reward: reward(from: $0.0.personalization.backing, inProject: $0.0), refTag: $0.1
        )
      }

(but that might be more than confusing due to the repetition of those variables)

let goToManagePledge = ctaButtonTapped
.filter(canShowManageViewPledgeScreen(_:_:state:))
.map { (project, refTag, _) -> PledgeData in
let r = reward(from: project.personalization.backing, inProject: project)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is a language limitation I'd be OK with leaving it as is .. your decision! πŸ₯‚

.filter { _ in featureNativeCheckoutPledgeViewIsEnabled() }

self.goToDeprecatedManagePledge = ctaButtonTapped
.filter(shouldGoToDeprecatedManagePledge(_:_:state:))
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I'd probably try to carry over as little unused things as possible..this makes it easier to reason about code and will be less confusing in the future (read in a week when we forget WTF was this) :D

return userCanSeeNativeCheckout() && (state == .pledge || state == .viewRewards)
}

private func canShowManageViewPledgeScreen(
Copy link
Contributor

Choose a reason for hiding this comment

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

After we remove the unused params, these function names should fit on one line making it a bit more readable IMO.

private func canShowRewardsScreen(state: PledgeStateCTAType?) -> Bool { .. }
private func canShowManageViewPledgeScreen(_ project: Project, state: PledgeStateCTAType?) -> Bool { .. }
private func shouldGoToDeprecatedViewBacking(_ project: Project, state: PledgeStateCTAType?) -> Bool { .. }
private func shouldGoToDeprecatedManagePledge(_ project: Project, state: PledgeStateCTAType?) -> Bool { .. }

@@ -686,7 +862,7 @@ final class ProjectPamphletViewModelTests: TestCase {
self.goToRewardsProject.assertDidNotEmitValue()
self.goToRewardsRefTag.assertDidNotEmitValue()

self.vm.inputs.backThisProjectTapped()
self.vm.inputs.pledgeCTAButtonTapped(.pledge)
Copy link
Contributor

Choose a reason for hiding this comment

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

See, I think this is where a better naming convention would help us a little Tapped(with: .pledge)..What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right. I changed in one place but missed that one. Will change it

Copy link
Contributor

@dusi dusi left a comment

Choose a reason for hiding this comment

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

Looks good nino!

I had couple suggestions but they shouldn't hold this review up...they're up to your judgment.

Cheers~


public typealias BackingData = (Project, User?)
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like

// BackingViewModel.swift
fileprivate let projectAndBackerProperty = MutableProperty<(Project, User?)?>(nil)

could use this data type...should we update it?

// BackingViewModel.swift
fileprivate let projectAndBackerProperty = MutableProperty<BackingData?>(nil)

.map(unpack)

let goToManagePledge = ctaButtonTapped
.filter { canShowManageViewPledgeScreen($0.0, state:$0.2) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this in action, it might actually be more readable to name those params. I'll leave this out to you, I'm OK with both - but wanted to raise it in case you were in favour of the other option.

.filter { project, _, state in canShowManageViewPledgeScreen(project, state: state) }

(this of course would apply to other places in this PR.

@Scollaco Scollaco merged commit 627a60b into master Sep 12, 2019
@Scollaco Scollaco deleted the manage-pledge-screen-from-cta-container branch September 12, 2019 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants