From 455e20643e75dff8739347f43f9755c5917e0941 Mon Sep 17 00:00:00 2001 From: Ghenadie <118184705+GhenadieVP@users.noreply.github.com> Date: Fri, 22 Mar 2024 08:18:47 +0200 Subject: [PATCH] A bunch of bugs (#1054) --- Gemfile.lock | 109 +++++----- .../AccountPortfoliosClient+Live.swift | 193 ++++++++++-------- .../NPSSurveyClient+Live.swift | 8 +- .../Core/SharedModels/Assets/FiatWorth.swift | 4 - RadixWallet/EngineKit/TXID.swift | 2 +- .../Coordinator/AccountDetails+View.swift | 3 +- .../AssetTransfer+Reducer.swift | 2 +- .../TransferAccountList+View.swift | 4 +- 8 files changed, 176 insertions(+), 149 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 68066e014f..c4838e7fc6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,32 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.826.0) - aws-sdk-core (3.183.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-eventstream (1.3.0) + aws-partitions (1.899.0) + aws-sdk-core (3.191.4) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.71.0) - aws-sdk-core (~> 3, >= 3.177.0) + aws-sdk-kms (1.78.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.135.0) - aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-s3 (1.146.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.0) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) + base64 (0.2.0) claide (1.1.0) colored (1.2) colored2 (3.1.2) @@ -32,11 +35,10 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.103.0) + excon (0.110.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -65,7 +67,7 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.7) + fastimage (2.3.0) fastlane (2.216.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) @@ -109,39 +111,40 @@ GEM fastlane-plugin-json (1.1.0) fastlane-plugin-xcconfig (2.0.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.49.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.1) + google-apis-androidpublisher_v3 (0.58.0) + google-apis-core (>= 0.14.0, < 2.a) + google-apis-core (0.14.1) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) + googleauth (~> 1.9) httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-apis-iamcredentials_v1 (0.20.0) + google-apis-core (>= 0.14.0, < 2.a) + google-apis-playcustomapp_v1 (0.15.0) + google-apis-core (>= 0.14.0, < 2.a) + google-apis-storage_v1 (0.36.0) + google-apis-core (>= 0.14.0, < 2.a) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) - google-cloud-storage (1.44.0) + google-cloud-env (2.1.1) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.4.0) + google-cloud-storage (1.49.0) addressable (~> 2.8) digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) + google-apis-core (~> 0.13) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (~> 0.33) google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) + googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) + googleauth (1.11.0) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) @@ -151,19 +154,21 @@ GEM domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.6.3) - jwt (2.7.1) + json (2.7.1) + jwt (2.8.1) + base64 mini_magick (4.12.0) mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) nanaimo (0.3.0) naturally (2.2.1) + nkf (0.2.0) optparse (0.1.1) os (1.1.4) - plist (3.7.0) - public_suffix (5.0.3) - rake (13.0.6) + plist (3.7.1) + public_suffix (5.0.4) + rake (13.1.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -174,7 +179,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.18.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -187,20 +192,16 @@ GEM unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - webrick (1.8.1) + unicode-display_width (2.5.0) word_wrap (1.0.0) xcode-install (2.8.1) claide (>= 0.9.1) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.22.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift index f35119fa7c..075562cb43 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift @@ -27,10 +27,10 @@ extension AccountPortfoliosClient: DependencyKey { /// Fetches the pool and stake units details for a given account; Will update the portfolio accordingly @Sendable - func fetchPoolAndStakeUnitsDetails(_ account: OnLedgerEntity.Account) async { + func fetchPoolAndStakeUnitsDetails(_ account: OnLedgerEntity.Account, cachingStrategy: OnLedgerEntitiesClient.CachingStrategy) async { async let poolDetailsFetch = Task { do { - let poolUnitDetails = try await onLedgerEntitiesClient.getOwnedPoolUnitsDetails(account) + let poolUnitDetails = try await onLedgerEntitiesClient.getOwnedPoolUnitsDetails(account, cachingStrategy: cachingStrategy) await state.set(poolDetails: .success(poolUnitDetails), forAccount: account.address) } catch { await state.set(poolDetails: .failure(error), forAccount: account.address) @@ -38,7 +38,7 @@ extension AccountPortfoliosClient: DependencyKey { }.result async let stakeUnitDetails = Task { do { - let stakeUnitDetails = try await onLedgerEntitiesClient.getOwnedStakesDetails(account: account) + let stakeUnitDetails = try await onLedgerEntitiesClient.getOwnedStakesDetails(account: account, cachingStrategy: cachingStrategy) await state.set(stakeUnitDetails: .success(stakeUnitDetails.asIdentifiable()), forAccount: account.address) } catch { await state.set(stakeUnitDetails: .failure(error), forAccount: account.address) @@ -48,94 +48,116 @@ extension AccountPortfoliosClient: DependencyKey { _ = await (poolDetailsFetch, stakeUnitDetails) } - return AccountPortfoliosClient( - fetchAccountPortfolios: { accountAddresses, forceRefresh in - let gateway = await gatewaysClient.getCurrentGateway() - await state.setRadixGateway(gateway) - if forceRefresh { - for accountAddress in accountAddresses { - cacheClient.removeFolder(.onLedgerEntity(.account(accountAddress.asGeneral))) - } + @Sendable + func applyTokenPrices(_ resources: [ResourceAddress], forceRefresh: Bool) async { + if !resources.isEmpty { + let prices = try? await tokenPricesClient.getTokenPrices( + .init( + tokens: Array(resources.uniqued()), + currency: state.selectedCurrency + ), + forceRefresh + ) + + if let prices { + await state.setTokenPrices(prices) } + } + } - /// Explicetely load and set the currency target and visibility to make sure - /// it is available for usage before resources are loaded - let preferences = await appPreferencesClient.getPreferences().display - await state.setSelectedCurrency(preferences.fiatCurrencyPriceTarget) - await state.setIsCurrencyAmountVisble(preferences.isCurrencyAmountVisible) - - let accounts = try await onLedgerEntitiesClient.getAccounts(accountAddresses).map(\.nonEmptyVaults) - - let portfolios = accounts.map { AccountPortfolio(account: $0) } - await state.handlePortfoliosUpdate(portfolios) - - /// Put together all resources from already fetched and new accounts - let currentAccounts = state.portfoliosSubject.value.wrappedValue.map { $0.values.map(\.account) } ?? [] - let allResources: [ResourceAddress] = { - if gateway == .mainnet { - /// Only Mainnet resources have prices - return (currentAccounts + accounts) - .flatMap { - $0.allFungibleResourceAddresses + - $0.poolUnitResources.poolUnits.flatMap(\.poolResources) + - [.mainnetXRDAddress] - } - } else { - #if DEBUG - /// Helpful for testing on stokenet - return [ - .mainnetXRDAddress, - try! .init(validatingAddress: - "resource_rdx1t4tjx4g3qzd98nayqxm7qdpj0a0u8ns6a0jrchq49dyfevgh6u0gj3" - ), - try! .init(validatingAddress: - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0" - ), - try! .init(validatingAddress: - "resource_rdx1tk7g72c0uv2g83g3dqtkg6jyjwkre6qnusgjhrtz0cj9u54djgnk3c" - ), - try! .init(validatingAddress: - "resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq" - ), - ] - #else - /// No price for resources on testnets - return [] - #endif - } - }() - - if !allResources.isEmpty { - let prices = try? await tokenPricesClient.getTokenPrices( - .init( - tokens: Array(allResources), - currency: preferences.fiatCurrencyPriceTarget - ), - forceRefresh - ) + @Sendable + func fetchAccountPortfolios(_ accountAddresses: [AccountAddress], _ forceRefresh: Bool) async throws -> [AccountPortfolio] { + let gateway = await gatewaysClient.getCurrentGateway() + await state.setRadixGateway(gateway) + if forceRefresh { + for accountAddress in accountAddresses { + cacheClient.removeFolder(.onLedgerEntity(.account(accountAddress.asGeneral))) + } + } - if let prices { - await state.setTokenPrices(prices) - } + /// Explicetely load and set the currency target and visibility to make sure + /// it is available for usage before resources are loaded + let preferences = await appPreferencesClient.getPreferences().display + await state.setSelectedCurrency(preferences.fiatCurrencyPriceTarget) + await state.setIsCurrencyAmountVisble(preferences.isCurrencyAmountVisible) + + let accounts = try await onLedgerEntitiesClient.getAccounts(accountAddresses).map(\.nonEmptyVaults) + + let portfolios = accounts.map { AccountPortfolio(account: $0) } + await state.handlePortfoliosUpdate(portfolios) + + /// Put together all resources from already fetched and new accounts + let currentAccounts = state.portfoliosSubject.value.wrappedValue.map { $0.values.map(\.account) } ?? [] + let allResources: [ResourceAddress] = { + if gateway == .mainnet { + /// Only Mainnet resources have prices + return (currentAccounts + accounts).flatMap(\.resourcesWithPrices) + [.mainnetXRDAddress] + } else { + #if DEBUG + /// Helpful for testing on stokenet + return [ + .mainnetXRDAddress, + try! .init(validatingAddress: + "resource_rdx1t4tjx4g3qzd98nayqxm7qdpj0a0u8ns6a0jrchq49dyfevgh6u0gj3" + ), + try! .init(validatingAddress: + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0" + ), + try! .init(validatingAddress: + "resource_rdx1tk7g72c0uv2g83g3dqtkg6jyjwkre6qnusgjhrtz0cj9u54djgnk3c" + ), + try! .init(validatingAddress: + "resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq" + ), + ] + #else + /// No price for resources on testnets + return [] + #endif } + }() - // Load additional details - _ = await accounts.parallelMap(fetchPoolAndStakeUnitsDetails) + await applyTokenPrices(Array(allResources), forceRefresh: forceRefresh) - return Array(state.portfoliosSubject.value.wrappedValue!.values) - }, - fetchAccountPortfolio: { accountAddress, forceRefresh in - if forceRefresh { - cacheClient.removeFolder(.onLedgerEntity(.account(accountAddress.asGeneral))) - } + // Load additional details + _ = await accounts.parallelMap { + await fetchPoolAndStakeUnitsDetails($0, cachingStrategy: forceRefresh ? .forceUpdate : .useCache) + } + + return Array(state.portfoliosSubject.value.wrappedValue!.values) + } + + @Sendable + func fetchAccountPortfolio(_ accountAddress: AccountAddress, _ forceRefresh: Bool) async throws -> AccountPortfolio { + if forceRefresh { + cacheClient.removeFolder(.onLedgerEntity(.account(accountAddress.asGeneral))) + } + + let account = try await onLedgerEntitiesClient.getAccount(accountAddress) + let portfolio = AccountPortfolio(account: account) - let account = try await onLedgerEntitiesClient.getAccount(accountAddress) - let portfolio = AccountPortfolio(account: account) + let currentResources = await state.tokenPrices.keys + await applyTokenPrices( + currentResources + account.resourcesWithPrices, + forceRefresh: forceRefresh + ) - await state.handlePortfolioUpdate(portfolio) - await fetchPoolAndStakeUnitsDetails(account) + await state.handlePortfolioUpdate(portfolio) + await fetchPoolAndStakeUnitsDetails(account, cachingStrategy: forceRefresh ? .forceUpdate : .useCache) - return portfolio + return portfolio + } + + return AccountPortfoliosClient( + fetchAccountPortfolios: { accountAddresses, forceRefresh in + try await Task.detached { + try await fetchAccountPortfolios(accountAddresses, forceRefresh) + }.value + }, + fetchAccountPortfolio: { accountAddress, forceRefresh in + try await Task.detached { + try await fetchAccountPortfolio(accountAddress, forceRefresh) + }.value }, portfolioUpdates: { state.portfoliosSubject @@ -149,3 +171,10 @@ extension AccountPortfoliosClient: DependencyKey { ) }() } + +extension OnLedgerEntity.Account { + /// The resources which can have prices + fileprivate var resourcesWithPrices: [ResourceAddress] { + allFungibleResourceAddresses + poolUnitResources.poolUnits.flatMap(\.poolResources) + } +} diff --git a/RadixWallet/Clients/NPSSurveyClient/NPSSurveyClient+Live.swift b/RadixWallet/Clients/NPSSurveyClient/NPSSurveyClient+Live.swift index 9f6c5fe575..cb02bc64bb 100644 --- a/RadixWallet/Clients/NPSSurveyClient/NPSSurveyClient+Live.swift +++ b/RadixWallet/Clients/NPSSurveyClient/NPSSurveyClient+Live.swift @@ -19,9 +19,10 @@ extension NPSSurveyClient: DependencyKey { }, incrementTransactionCompleteCounter: { let currentCounter = userDefaults.getTransactionsCompletedCounter() ?? 0 + let lastDate = userDefaults.getDateOfLastSubmittedNPSSurvey() let updatedCounter = currentCounter + 1 userDefaults.setTransactionsCompletedCounter(updatedCounter) - if Self.shouldAskUserForFeedback(updatedCounter) { + if Self.shouldAskUserForFeedback(updatedCounter, dateOfLastSubmittedNPSSurvey: lastDate) { shouldAskForFeedbackSubject.send(true) } }, @@ -37,14 +38,13 @@ extension NPSSurveyClient { private static let nextFeedbackIntervalThresholdInMonths = 3 @Sendable - static func shouldAskUserForFeedback(_ transactionCounter: Int) -> Bool { - @Dependency(\.userDefaults) var userDefaults + static func shouldAskUserForFeedback(_ transactionCounter: Int, dateOfLastSubmittedNPSSurvey: Date?) -> Bool { @Dependency(\.date) var date if transactionCounter == Self.feedbackTransactionCounterThreshold { return true } else if transactionCounter > Self.feedbackTransactionCounterThreshold { - guard let lastSubmittedDate = userDefaults.getDateOfLastSubmittedNPSSurvey() else { + guard let lastSubmittedDate = dateOfLastSubmittedNPSSurvey else { // No submit date saved? // Can happen if user did close the app while the original NPS survey was shown. // And since the next NPS survey check will happen only after user did perform diff --git a/RadixWallet/Core/SharedModels/Assets/FiatWorth.swift b/RadixWallet/Core/SharedModels/Assets/FiatWorth.swift index 0d5d0119a5..200202e372 100644 --- a/RadixWallet/Core/SharedModels/Assets/FiatWorth.swift +++ b/RadixWallet/Core/SharedModels/Assets/FiatWorth.swift @@ -93,10 +93,6 @@ extension FiatWorth { let value = worth.value ?? .zero // Zero for the unknown case, just to do to the base formatting - if value < 1 { - formatter.maximumFractionDigits = 8 - } - let formattedValue = { guard let double = try? value.asDouble(), let value = formatter.string(for: double) else { // Good enough fallback diff --git a/RadixWallet/EngineKit/TXID.swift b/RadixWallet/EngineKit/TXID.swift index 6bb1447cd7..a8348b9d5a 100644 --- a/RadixWallet/EngineKit/TXID.swift +++ b/RadixWallet/EngineKit/TXID.swift @@ -4,7 +4,7 @@ public typealias TXID = TransactionHash extension TXID { public func formatted(_ format: AddressFormat = .default) -> String { - bytes().hex() + asStr() } public var hex: String { diff --git a/RadixWallet/Features/AccountDetailsFeature/Coordinator/AccountDetails+View.swift b/RadixWallet/Features/AccountDetailsFeature/Coordinator/AccountDetails+View.swift index ca0271a2b6..ae6662ae25 100644 --- a/RadixWallet/Features/AccountDetailsFeature/Coordinator/AccountDetails+View.swift +++ b/RadixWallet/Features/AccountDetailsFeature/Coordinator/AccountDetails+View.swift @@ -53,7 +53,7 @@ extension AccountDetails { viewStore.send(.showFiatWorthToggled) } .foregroundColor(.app.white) - .padding([.bottom, .horizontal], .medium1) + .padding(.horizontal, .medium1) } prompts( @@ -65,6 +65,7 @@ extension AccountDetails { historyButton() transferButton() } + .padding(.top, .small1) AssetsView.View(store: store.scope(state: \.assets, action: \.child.assets)) .roundedCorners(.top, radius: .medium1) diff --git a/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift b/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift index 676e98f6a7..46345a072b 100644 --- a/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift +++ b/RadixWallet/Features/AssetTransferFeature/AssetTransfer+Reducer.swift @@ -77,7 +77,7 @@ public struct AssetTransfer: Sendable, FeatureReducer { let manifest = try await createManifest(accounts) Task { _ = try await dappInteractionClient.addWalletInteraction( - .transaction(.init(send: .init(transactionManifest: manifest))), + .transaction(.init(send: .init(transactionManifest: manifest, message: message))), .accountTransfer ) } diff --git a/RadixWallet/Features/AssetTransferFeature/Components/TransferAccountList/TransferAccountList+View.swift b/RadixWallet/Features/AssetTransferFeature/Components/TransferAccountList/TransferAccountList+View.swift index 8f245c6ac6..a14a5f4143 100644 --- a/RadixWallet/Features/AssetTransferFeature/Components/TransferAccountList/TransferAccountList+View.swift +++ b/RadixWallet/Features/AssetTransferFeature/Components/TransferAccountList/TransferAccountList+View.swift @@ -90,11 +90,11 @@ private extension View { private func addAsset(with destinationStore: PresentationStoreOf) -> some View { sheet(store: destinationStore.scope(state: \.state.addAsset, action: \.addAsset)) { assetsStore in AssetsView.View(store: assetsStore) + .navigationTitle(L10n.AssetTransfer.AddAssets.navigationTitle) + .navigationBarTitleDisplayMode(.inline) .withNavigationBar { assetsStore.send(.view(.closeButtonTapped)) } - .navigationTitle(L10n.AssetTransfer.AddAssets.navigationTitle) - .navigationBarTitleDisplayMode(.inline) } } }