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

Generalize currency conversion #231

Merged
merged 11 commits into from Aug 31, 2017
Merged

Generalize currency conversion #231

merged 11 commits into from Aug 31, 2017

Conversation

stephencelis
Copy link
Contributor

Adds support for currency rates from our API.

@stephencelis
Copy link
Contributor Author

(Test fixes comin.)

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.

Nice, I like the "About $40" under the GBP amount.

Two questions!

pledged: Int($0 * Float($1.goal)), staticUsdRate: $1.staticUsdRate, updatesCount: $1.updatesCount) }
set: { .init(backersCount: $1.backersCount, commentsCount: $1.commentsCount,
currentCurrency: $1.currentCurrency, currentCurrencyRate: $1.currentCurrencyRate,
goal: $1.goal, pledged: Int($0 * Float($1.goal)), staticUsdRate: $1.staticUsdRate,
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels a little strange that this bit Int($0 * Float($1.goal)) is in the lens, should we move it to the model? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a lens we already had on fundingProgress, which is a computed property on our model. Gotta have the inverse logic here to update the type accordingly.

}
.map { project, rewardOrBacking in
let (country, rate) = zip(
project.stats.currentCurrency.flatMap(Project.Country.init(currencyCode:)),
Copy link
Contributor

Choose a reason for hiding this comment

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

Compiler clever enough here for .flatMap(.init(currencyCode:))?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. Since function isn't its own type and this is point-free.

Copy link
Contributor Author

@stephencelis stephencelis left a comment

Choose a reason for hiding this comment

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

Summary below! Sorry for the noise, but this PR also:

  • Updates strings (for Japan translation fixes)
  • Updates lint stuff (new version of swiftlint)

@@ -211,7 +211,6 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate {

return self.viewModel.outputs.applicationDidFinishLaunchingReturnValue
}
// swiftlint:enable function_body_length
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While this PR was goin on swiftlint 0.22.0 dropped, which now checks for stale linter rules. It doesn't find these trailing ones, so I did a quick manual check based on our disabled rules.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, thanks!

lhs.isLoggedIn == rhs.isLoggedIn &&
lhs.isMember == rhs.isMember
extension TabBarItemsData: Equatable {
static func == (lhs: TabBarItemsData, rhs: TabBarItemsData) -> Bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Quick lil static promotion. Bunch more of these we could update in the future. Cleans things mostly up when generic types are involved (since you can omit the generic params).

@@ -38,7 +37,7 @@ final class UpdatePreviewViewModelTests: TestCase {
self.vm.inputs.viewDidLoad()

let previewUrl = "https://\(Secrets.Api.Endpoint.production)/projects/2/updates/1/preview"
let query = "client_id=\(self.apiService.serverConfig.apiClientAuth.clientId)"
let query = "client_id=\(self.apiService.serverConfig.apiClientAuth.clientId)&currency=USD"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now our way of sending device currency (from the region) is to include it as a param. This means web views should get currency for free. Alternatively we could have synced currency via config, but this could have slowed things during load.

@@ -54,7 +54,7 @@ internal final class ProjectActivityViewControllerTests: TestCase {
}

func testVoiceOverRunning() {
withEnvironment(isVoiceOverRunning: { true }) {
withEnvironment(isVoiceOverRunning: const(true)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

New swiftlint rule requires that functions with multiple function parameters avoids trailing closure syntax. For things like either(ifLeft: { … }, ifRight: { … }) I like this, so keeping it enabled and switching these to use const.

@@ -213,8 +213,6 @@ public final class ProjectPamphletContentViewController: UITableViewController {
fileprivate func scrollingIsAllowed(_ scrollView: UIScrollView) -> Bool {
return self.presentingViewController?.presentedViewController?.isBeingDismissed != .some(true)
&& (!scrollView.isTracking || scrollView.contentOffset.y >= 0)
// swiftlint:disable:previous force_unwrapping
// NB: this ^ shouldn't be necessary, looks like a bug in swiftlint.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

👋

@@ -50,6 +50,8 @@ public struct Project {
public struct Stats {
public let backersCount: Int
public let commentsCount: Int?
public let currentCurrency: String?
public let currentCurrencyRate: Float?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Our API now returns the "current" currency of the response (which should be pass-through from the request), and the exchange rate against the project currency.

@@ -240,7 +240,8 @@ public struct AppEnvironment {
basicHTTPAuth: service.serverConfig.basicHTTPAuth
),
oauthToken: service.oauthToken,
language: current.language.rawValue
language: current.language.rawValue,
currency: current.locale.currencyCode ?? "USD"
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 could do a more graceful regional fallback here...but not sure how to manage that.

!needsConversion(projectCountry: $0.country, userCountry: AppEnvironment.current.config?.countryCode)
}

self.conversionLabelHidden = project.map(needsConversion(project:) >>> 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.

This is the meat of the new logic! We generalize the old and new paths (with or without the API returning current_currency to a needsConversion(project:).

Cleaned things up by composing this against negate, and composing it in the filter below against first.

.map { project, rewardOrBacking in
let (country, rate) = zip(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fun bit of FP: Optional zip coalescing to a default pair.

Copy link
Contributor

Choose a reason for hiding this comment

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

Pretty cool!

return needsConversion(projectCountry: project.country,
userCountry: AppEnvironment.current.config?.countryCode)
}
return currentCurrency == AppEnvironment.current.apiService.currency
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If our API ever returns a different currency than the one we send along, we fall back to USD here. We may wanna reconsider that logic, though?

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 not keep these defaults on the Config and lens them in the first time?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I mistyped. I mean we don't show any conversion info.

@stephencelis stephencelis merged commit a7636fa into master Aug 31, 2017
@stephencelis stephencelis deleted the currency-conversion branch August 31, 2017 14:09
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