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

[ABW-3743] Show fiat value of Transaction fees #1292

Merged
merged 1 commit into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI

extension TransactionReviewNetworkFee.State {
var displayedTotalFee: String {
"\(reviewedTransaction.transactionFee.totalFee.displayedTotalFee) XRD"
L10n.TransactionReview.xrdAmount(reviewedTransaction.transactionFee.totalFee.displayedTotalFee)
}
}

Expand All @@ -19,8 +19,8 @@ extension TransactionReviewNetworkFee {

public var body: some SwiftUI.View {
WithViewStore(store, observe: { $0 }, send: { .view($0) }) { viewStore in
VStack(alignment: .leading, spacing: .small2) {
HStack {
VStack(alignment: .leading, spacing: .zero) {
HStack(alignment: .top) {
Text(L10n.TransactionReview.NetworkFee.heading)
.sectionHeading
.textCase(.uppercase)
Expand All @@ -32,9 +32,19 @@ extension TransactionReviewNetworkFee {

Spacer(minLength: 0)

Text(viewStore.displayedTotalFee)
.textStyle(.body1HighImportance)
.foregroundColor(.app.gray1)
VStack(alignment: .trailing, spacing: .small3) {
Text(viewStore.displayedTotalFee)
.textStyle(.body1HighImportance)
.foregroundColor(.app.gray1)

loadable(viewStore.fiatValue) {
ProgressView()
} successContent: { value in
Text(value)
.textStyle(.body2HighImportance)
.foregroundColor(.app.gray2)
}
}
}

loadable(viewStore.reviewedTransaction.feePayingValidation) { validation in
Expand All @@ -56,6 +66,9 @@ extension TransactionReviewNetworkFee {
.foregroundColor(.app.blue2)
}
}
.task {
viewStore.send(.task)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SwiftUI
public struct TransactionReviewNetworkFee: Sendable, FeatureReducer {
public struct State: Sendable, Hashable {
public var reviewedTransaction: ReviewedTransaction
public var fiatValue: Loadable<String> = .idle

public init(
reviewedTransaction: ReviewedTransaction
Expand All @@ -14,22 +15,90 @@ public struct TransactionReviewNetworkFee: Sendable, FeatureReducer {
}

public enum ViewAction: Sendable, Equatable {
case task
case infoTapped
case customizeTapped
}

public enum InternalAction: Sendable, Equatable {
case setTokenPrices(TaskResult<PriceResult>)
}

public enum DelegateAction: Sendable, Equatable {
case showCustomizeFees
}

public struct PriceResult: Sendable, Equatable {
let prices: TokenPricesClient.TokenPrices
let currency: FiatCurrency
}

@Dependency(\.appPreferencesClient) var appPreferencesClient
@Dependency(\.tokenPricesClient) var tokenPricesClient
@Dependency(\.errorQueue) var errorQueue

public init() {}

public func reduce(into state: inout State, viewAction: ViewAction) -> Effect<Action> {
switch viewAction {
case .task:
state.fiatValue = .loading
return .run { send in
let currency = await appPreferencesClient.getPreferences().display.fiatCurrencyPriceTarget
let result = await TaskResult {
let prices = try await tokenPricesClient.getTokenPrices(.init(tokens: [.mainnetXRD], currency: currency), false)
return PriceResult(prices: prices, currency: currency)
}
await send(.internal(.setTokenPrices(result)))
}
case .infoTapped:
.none
return .none
case .customizeTapped:
.send(.delegate(.showCustomizeFees))
return .send(.delegate(.showCustomizeFees))
}
}

public func reduce(into state: inout State, internalAction: InternalAction) -> Effect<Action> {
switch internalAction {
case let .setTokenPrices(.failure(error)):
loggerGlobal.error("TransactionReviewNetworkFee failed to fetch XRD price, error: \(error)")
state.fiatValue = .failure(error)
return .none

case let .setTokenPrices(.success(result)):
guard let price = result.prices[.mainnetXRD] else {
loggerGlobal.error("TransactionReviewNetworkFee didn't get XRD price on response")
state.fiatValue = .failure(MissingXrdPriceError())
return .none
}
state.fiatValue = .success(state.reviewedTransaction.transactionFee.totalFee.fiatValue(xrdPrice: price, currency: result.currency))
return .none
}
}

private struct MissingXrdPriceError: Error {}
}

private extension TransactionFee.TotalFee {
func fiatValue(xrdPrice: Decimal192, currency: FiatCurrency) -> String {
let formatter = Self.feePriceFormatter
formatter.currencyCode = currency.currencyCode

let maxPrice = max * xrdPrice
let maxValue = formatter.string(for: maxPrice.asDouble) ?? maxPrice.formatted()
guard max > min else {
return maxValue
}

let minPrice = min * xrdPrice
let minValue = formatter.string(for: minPrice.asDouble) ?? minPrice.formatted()
return "\(minValue) - \(maxValue)"
}

private static let feePriceFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumSignificantDigits = 3
return formatter
}()
}
Loading