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

Reward container error handling #796

Merged
merged 51 commits into from
Aug 20, 2019
Merged

Conversation

Scollaco
Copy link
Contributor

📲 What

  • Adds an Error State view to the Rewards Container, allowing the user to tap a button to retry fetching backing infos.

🤔 Why

This is part of the first release of the Native Checkout project.
Trello card: https://trello.com/c/gmkKdvTG/1492-error-handling-for-loading

🛠 How

  • The fetchProject(projectOrParam:) on ProjectPamphletViewModel now returns SignalProducer<Project, ErrorEnvelope> instead of SignalProducer<Project, Never>. This will tell the callers when an error occurred.
  • On ProjectPamphletViewModel.swift, the configurePledgeCTAView output now emits Signal<(Either<Project, ErrorEnvelope>, Bool), Never>
  • The PledgeCTAContainerViewViewModel receives the (Either<Project, ErrorEnvelope>, Bool) as an input and will take care of the UI logic based on what's sent (.left(project) or .right(error)).

👀 See

Before 🐛 After 🦋
IMG_5393 IMG_5392

♿️ Accessibility

  • Tap targets use minimum of 44x44 pts dimensions
  • Works with VoiceOver
  • Supports Dynamic Type

✅ Acceptance criteria

  • From Discovery, turn on Flight Mode on your device and tap on any project
  • The Activity Indicator should be visible
  • Once the server returns an error, the Activity Indicator should disappear and the Retry button should be visible.
  • Turn off flight mode and tap the retry button. The Back this Project button should be visible and the retry Button should be hidden.

justinswart and others added 30 commits February 15, 2019 11:17
* New card screen ui (#471)

* add card view controller

* transition to add new card and uibarbuttons

* settings style applied to add new card screen

* changed keyboard style for textfields

* view controller tests

* placeholder for cardholder name textfield

* Record new screenshots

* new strings, added strings as place holder and text label

* pr feedback - inferring type for cancel button tintcolor

* pr feedback - keypath, no more lenses

* swiftlint fix

* new snapshots

* Payment methods (#457)

* Stripe element add new card (#473)

* New card screen ui (#471)

* add card view controller

* transition to add new card and uibarbuttons

* settings style applied to add new card screen

* changed keyboard style for textfields

* view controller tests

* placeholder for cardholder name textfield

* Record new screenshots

* new strings, added strings as place holder and text label

* pr feedback - inferring type for cancel button tintcolor

* pr feedback - keypath, no more lenses

* swiftlint fix

* new snapshots

* new stppaymentcardtextfield showing

* wip - placeholder colors

* snapshot tests

* Email undeliverable/unverified (#478)

* Adding emailIsVerified

* Renaming GraphUserEmail

* ViewModel tests

* ChangeEmailViewController tests

* Strings, swiftlint

* Strings & new screenshots

* Cleanup

* PR comments

* PR updates

* Test naming

* pr feedback

* new snapshots

* new placeholder colors

* new snapshots

* Delete payment methods (#479)

* Add payment method deletion

* Test datasource

* Test view model

* Clean-up and address PR feedback

* Rename credit card -> payment method

* Fix test

* Add credit card implementation (#503)

* Email undeliverable/unverified (#478)

* Adding emailIsVerified

* Renaming GraphUserEmail

* ViewModel tests

* ChangeEmailViewController tests

* Strings, swiftlint

* Strings & new screenshots

* Cleanup

* PR comments

* PR updates

* Test naming

* Use correct function to log events in crash logs (#481)

* Use correct function to log events in crash logs

* Fix indentation

* changed ui colors for textfield text and font size of text label

* vm and work on enabling save

* wip- get stripe token

* wip- payment source mutation

* wip- keyboard response

* wip- getting a stripe error here

* wip- error fix w/ publishable key, error banner showing

* wip- saving with error and keyboard functionality

* wip- updating card immediately

* wip

* wip - ACs met

* wip - refactor in view model, made IDs testable, begane VM tests

* wip -refactor on vm/vmtest

* wip -refactor deleted comments on vmt

* wip -snapshot tests

* swiftlint fixes

* renaming/refactor, corrected paymentmethodstests

* pr feedback

* swiftlint fixes

* changed function name

* indentation

* Payment methods event tracking (#496)

* Reverted code that deleted SettingsNewsletters from Storyboard.swift

* swiftlint

* Reverted code to instantiate settings newsletters vc on tests

* Alphabetized storyboard enum

* Settings payments colors (#530)

* Unsupported Credit Cards (#561)

* Viewmodel logic for unsupported cards

* Tests and accessibility fixes

* Simplify add card button

* Screenshots and string updates

* Cleanup design and remove logs

* Improving comment message

* Removing filter debug builds

* Addressing PR comments

* Updating payment methods screenshots

* Screenshots

* Possible fix for alignment issues

* Moving iOS 10 handling to output signal closure

* Remove line

* Renaming

* Last cleanup

* Strings & Asset update

* Switching from unsupported to supported

* Zipcode field in Add New Card screen (#566)

* Viewmodel logic for unsupported cards

* Tests and accessibility fixes

* Simplify add card button

* Screenshots and string updates

* Cleanup design and remove logs

* Improving comment message

* Removing filter debug builds

* Addressing PR comments

* Updating payment methods screenshots

* Screenshots

* Possible fix for alignment issues

* Moving iOS 10 handling to output signal closure

* Remove line

* Renaming

* Last cleanup

* Shared styled form field

* Adding functionality for zipcode form field

* More functionality

* View model & screenshot tests

* Swiftlint

* Cleanup

* Improving test and cleanup

* PR feedback and autocapitalizing zipcode

* Regenerating ChangePassword screenshots

* [Payment methods] CVC bug fix (#574)

* wip

* wip

* wip

* wip-swiftlint

* moved CreatePaymentSourceEnvelope to its own file, Mockservice

* swiftlint fix

* deleted unnecesary debugging code

* new paymentsource temploate and vm tests

* swiftlint fix

* fixed vm test

* PR feedback

* update schema

* [Payment methods] Minor bug fixes (#579)

* tableViewIsEditing false and show banner after dismiss

* Swiftlint

* Adding StripePublishableKey to example file

* Update publishableKey test

* Spacing

* Point-free helper

* [Payment Methods] Update padding and image view size (#581)

* Update padding and image view size

* Update stack view's spacing

* Update snapshots

* Use layoutMargins to set the padding

* [Payment methods] Disable edit button if no payment methods (#586)

* [Payment methods] Bugs & visual fixes (#578)

* Adding custom UITableViewHeader and always calling reload data on viewDidLoad

* Updating screenshots

* Cleanup autolayout warnings

* Screenshots

* PR comments

* No autolayout for footer view

* Cleanup

* Remove intrinsicContentSize

* Tableview appears without delay fix (#587)

* Fix header/footer auto sizing

* Update header background color

* Update snapshots

* Screenshots again

* Updating constraint priority to resolve ambiguity

* [Payment methods] Refresh payment methods table view properly (#588)

* Refresh payment methods table view properly

* Remove reference to weak self

* Rename delegate methods

* [Payment methods] Refetch payment methods on viewDidLoad or explicitly (by delegate) (#591)

* Do not refresh on viewWillAppear

* Use optional string to avoid unnecessary initializer

* Bring back viewWillAppear signal to better reflect view controller's lifecycle state

* [Payment methods] A11y - Credit card name (#593)

* Add card name to the a11y label

* Add comment

* [Payment Methods] Design Fixes (#592)

* Design fixes

* Fresh screenshots ✨

* Remove recordMode

* Deleting stale screenshots

* Re-add accidentally deleted line

* update strings

* [Payment Methods] Support optional card type and error handling for payment methods (#596)

* Optional card type, payment methods load error

* Optional support for imageName

* Fix accessibility label

* Fix merge conflict spacing

* Swiftlint

* Implementing PR suggestions

* Fixed bug that shows wrong expiration date (#595)

* [Payment Methods] Add New Card Design Fixes (#594)

* error message present and return key to done

* removed error message banner for incomplete payment details

* new snapshots for correct insets

* [Payment methods] Optimistically disable edit button on card deletion (#589)

* Disable edit button optimistically, use stored cards in result from card deletion

* Fix issue where edit button is not re-enabled upon the failed deletion of the last card.

* Remove unnecessary .init

* Add skipRepeats() to editButtonIsEnabled signal.

* Fixing potential reference cycles (#598)
@ksr-ci-bot
Copy link

ksr-ci-bot commented Aug 13, 2019

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

@Scollaco
Copy link
Contributor Author

@Scollaco Scollaco assigned justinswart and unassigned ifbarrera Aug 13, 2019
@@ -71,11 +71,13 @@
"Closes_project" = "Schließt das Projekt.";
"Collapses_subcategories" = "Reduziert die Anzeige der Unterkategorien.";
"Collections" = "Sammlungen";
"Comment_reply_digest" = "Comment reply digest";
"Comment_reply_digest" = "Tägliche Zusammenfassung Kommentare";
Copy link
Contributor Author

@Scollaco Scollaco Aug 13, 2019

Choose a reason for hiding this comment

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

The Comment_reply_digest was added while running the git add --patch command for being part of the same 'hunk' of the string we want. Will try to revert it using the git gui

@Scollaco Scollaco force-pushed the reward-container-error-handling branch from d007873 to ea74d70 Compare August 13, 2019 21:30
@@ -68,6 +79,10 @@ final class PledgeCTAContainerView: UIView {

let isAccessibilityCategory = self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory

_ = self.pledgeRetryButton
|> pledgeRetryButtonStyle
|> \.isHidden .~ true
Copy link
Contributor

Choose a reason for hiding this comment

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

I think let's only manage the hidden state using the view model output.

@@ -96,7 +111,8 @@ final class PledgeCTAContainerView: UIView {

_ = self.activityIndicator
|> \.color .~ UIColor.ksr_dark_grey_500
|> \.hidesWhenStopped .~ true

self.activityIndicator.startAnimating()
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move this to the lazy var initializer.


// NB: vm needs to return button style
Copy link
Contributor

Choose a reason for hiding this comment

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

😄 this hasn't been done yet.

self.viewModel.outputs.activityIndicatorIsHidden
.observeForUI()
.observeValues { [weak self] isHidden in
self?.activityIndicatorContainerView.isHidden = isHidden
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be able to just use the rac binding 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.

Oh yeah, missed that during cleanup.

.skipNil()
.filter(second >>> isFalse)
.map(first)
.map { $0.0 }
Copy link
Contributor

Choose a reason for hiding this comment

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

.map(first) not working 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.

XCode was complaining, but now it seems to be fine 🤦‍♂

.merge(
project.ignoreValues().mapConst(false),
projectError.ignoreValues().mapConst(true),
self.activityIndicatorIsHidden.filter(isFalse).mapConst(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the filter here? or could this be .negate() 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to use the value of activityIndicatorIsHidden only when it's false, to make sure that the buttons are hidden while the indicator is visible. If activityIndicatorIsHidden returns true, either the Back this Project button or Tap to retry should be visible. Does it make sense?

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, how about expressing it something like this 🤔 I find the merging a little hard to read.

let inError = Signal.merge(
  projectOrError.mapConst(false),
  projectError.ignoreValues().mapConst(true),
  project.ignoreValues().mapConst(false)
)

let updateButtonStates = Signal.merge(
  projectOrError.ignoreValues(),
  self.activityIndicatorIsHidden.ignoreValues()
)

self.pledgeRetryButtonIsHidden = inError
  .map(isFalse)
  .takeWhen(updateButtonStates)

self.pledgeCTAButtonIsHidden = inError
  .map(isTrue)
  .takeWhen(updateButtonStates)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice idea!

@@ -53,19 +75,21 @@ public final class PledgeCTAContainerViewViewModel: PledgeCTAContainerViewViewMo
.map(subtitle(project:pledgeState:))
}

fileprivate let configureWithValueProperty = MutableProperty<(project: Project, isLoading: Bool)?>(nil)
public func configureWith(value: (project: Project, isLoading: Bool)) {
fileprivate let configureWithValueProperty =
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd go with projectOrErrorProperty.

@@ -14,6 +14,10 @@ public protocol ProjectPamphletViewModelInputs {
/// Call after the view loads and passes the initial TopConstraint constant.
func initial(topConstraint: CGFloat)

/// Call when "Content isn't loading right now." is tapped.
Copy link
Contributor

Choose a reason for hiding this comment

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

The text on the button could change so let's just say Called when pledgeRetryButtonTapped is tapped.

self.scheduler.advance()

XCTAssertTrue(self.configurePledgeCTAViewProject.lastValue?.left == projectFull2)
self.configurePledgeCTAViewIsLoading.assertValues([true, true, false, true, true, false])
Copy link
Contributor

Choose a reason for hiding this comment

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

What do the multiple trues mean? Should we just add a skipRepeats()?

Copy link
Contributor Author

@Scollaco Scollaco Aug 15, 2019

Choose a reason for hiding this comment

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

This happens because configurePledgeCTAView combines the latest values of freshProjectAndRefTagEvent and isLoading. So whenever one signal updates, configurePledgeCTAView emits again. In this case, the true right before a false is simply the latest value of isLoading when freshProjectAndRefTagEvent emits a fresh project (or error).

.map(Either.right)
.skipNil()

self.activityIndicatorIsHidden = self.projectOrErrorProperty.signal
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's chain this to projectOrError as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

projectOrError only emits when the project is no longer loading. For a better readability, I will create a property isLoading

let isLoading = self.projectOrErrorProperty.signal
      .skipNil()
      .map(second)

@@ -194,3 +229,17 @@ private func pledgeCTAButtonStyle(
|> (UIButton.lens.titleLabel .. UILabel.lens.lineBreakMode) .~ lineBreakMode
}
}

private let pledgeRetryButtonStyle: ButtonStyle = { button in
Copy link
Contributor

Choose a reason for hiding this comment

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

Think we need to take another look at this button style, the highlight state causes the text to disappear completely.

Also, the image doesn't have a down state, we could give it a tint for that.

We should use the mixDarker and mixLighter extensions for the highlight and disabled states - see ButtonStyles.swift.

Copy link
Contributor

Choose a reason for hiding this comment

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

Dynamic type is also a little weird with this button:

image

Copy link
Contributor Author

@Scollaco Scollaco Aug 16, 2019

Choose a reason for hiding this comment

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

For the .highlighted state I created an UIImage extension and now we are able to apply alpha to images, also changed the colour of the text for that state to grey (and used mixedLighter).

For the dynamic type I set the correct insets for the text (instead of image) and it seems to solve this issue.


// NB: vm needs to return button style

Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove the comment above this line, think it was just there as a reminder before.

private let pledgeRetryButtonStyle: ButtonStyle = { button in
button
|> baseButtonStyle
|> UIButton.lens.titleColor(for: .normal) .~ .ksr_soft_black
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make this .ksr_text_black.

|> UIButton.lens.titleLabel.font .~ .ksr_caption1()
|> UIButton.lens.backgroundColor(for: .normal) .~ .clear
|> UIButton.lens.titleEdgeInsets .~ .init(top: 0, left: Styles.grid(3), bottom: 0, right: 0)
|> UIButton.lens.titleColor(for: .highlighted) .~ UIColor.ksr_grey_500.mixLighter(0.36)
Copy link
Contributor

Choose a reason for hiding this comment

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

ksr_text_black here too 👍

|> UIButton.lens.titleColor(for: .highlighted) .~ UIColor.ksr_grey_500.mixLighter(0.36)
|> UIButton.lens.contentEdgeInsets .~ UIEdgeInsets(topBottom: Styles.gridHalf(1))
|> UIButton.lens.image(for: .normal) %~ { _ in image(named: "icon--refresh-small") }
|> UIButton.lens.image(for: .highlighted) %~ { _ in image(named: "icon--refresh-small", alpha: 0.36) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use alpha 0.66 so that's not too washed out.

self.pledgeCTAButton.widthAnchor.constraint(greaterThanOrEqualToConstant: Layout.Button.minWidth)
self.pledgeCTAButton.widthAnchor.constraint(greaterThanOrEqualToConstant: Layout.Button.minWidth),
self.pledgeRetryButton.heightAnchor.constraint(greaterThanOrEqualToConstant: Layout.Button.minHeight),
self.pledgeRetryButton.widthAnchor.constraint(greaterThanOrEqualToConstant: Layout.Button.minWidth)
Copy link
Contributor

Choose a reason for hiding this comment

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

This constraint is creating a warning, do we need it for this type of button?

image

Copy link
Contributor

@justinswart justinswart left a comment

Choose a reason for hiding this comment

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

Cool lgtm!

@Scollaco Scollaco merged commit 56e0138 into master Aug 20, 2019
@Scollaco Scollaco deleted the reward-container-error-handling branch August 20, 2019 19:49
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.

4 participants