From bd2e91a80c4f21c190f2c0a5ee3d543a690be17f Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 10:54:17 +0200 Subject: [PATCH 01/10] wip --- .../Core/FeaturePrelude/Loadable.swift | 21 ++++++++++++++----- .../AssetsFeature/AssetsView+Reducer.swift | 1 - .../AccountRow/Home+AccountRow+Reducer.swift | 10 ++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/RadixWallet/Core/FeaturePrelude/Loadable.swift b/RadixWallet/Core/FeaturePrelude/Loadable.swift index 03af3c9665..51beae7301 100644 --- a/RadixWallet/Core/FeaturePrelude/Loadable.swift +++ b/RadixWallet/Core/FeaturePrelude/Loadable.swift @@ -307,18 +307,29 @@ extension Loadable { public mutating func refresh( from other: Loadable, valueChangeMap: (_ old: Value, _ new: Value) -> Value = { _, new in new } - ) { + ) where Value: Equatable { switch (self, other) { - // If `other` is success, update the content regardless of the current state - case let (.success(oldValue), .success(newValue)): - self = .success(valueChangeMap(oldValue, newValue)) - case let (_, .success(otherValue)): + // Update to success if no current value + case let (.idle, .success(otherValue)), + let (.loading, .success(otherValue)), + let (.failure, .success(otherValue)): + print("Refresh from nonSuccessValue") self = .success(otherValue) + + // Update to new value only if it changed + case let (.success(oldValue), .success(newValue)): + print("Refresh from success value with success") + if oldValue != newValue { + self = .success(valueChangeMap(oldValue, newValue)) + } + // If current state is success, don't update if `other` is loading or failed case (.success, _): + print("Refresh from success value with other value") break // If current state is other than .success case let (_, other): + print("Refresh from any value with other value") self = other } } diff --git a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift index 1226e85f42..90a31cbbc7 100644 --- a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift @@ -249,7 +249,6 @@ public struct AssetsView: Sendable, FeatureReducer { extension AccountPortfoliosClient.AccountPortfolio { mutating func refresh(from portfolio: AccountPortfoliosClient.AccountPortfolio) { - self.account = portfolio.account self.stakeUnitDetails.refresh(from: portfolio.stakeUnitDetails) self.poolUnitDetails.refresh(from: portfolio.poolUnitDetails) } diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift index a831d5eab3..a3855a5a4f 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift @@ -78,9 +78,13 @@ extension Home { state.isDappDefinitionAccount = portfolio.account.metadata.accountType == .dappDefinition assert(portfolio.account.address == state.account.address) - - state.portfolio = .success(portfolio) - state.totalFiatWorth.refresh(from: portfolio.totalFiatWorth) + var newValue = state.portfolio + newValue.refresh(from: .success(portfolio)) + if state.portfolio != newValue { + state.portfolio = newValue + } + // state.portfolio.refresh(from: .success(portfolio)) + // state.totalFiatWorth.refresh(from: portfolio.totalFiatWorth) return .send(.internal(.checkAccountAccessToMnemonic)) case .checkAccountAccessToMnemonic: From c07a89e91f80dda34ab9e3deb61f2aed0e943856 Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 13:09:16 +0200 Subject: [PATCH 02/10] wip --- .../AccountPortfoliosClient+State.swift | 2 +- .../Core/FeaturePrelude/Loadable.swift | 10 +++++---- .../AccountRow/Home+AccountRow+Reducer.swift | 22 +++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift index f0d02fedd4..54be6f2171 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+State.swift @@ -45,7 +45,7 @@ extension AccountPortfoliosClient.State { } func portfolioForAccount(_ address: AccountAddress) -> AnyAsyncSequence { - portfoliosSubject.compactMap { $0[address].unwrap()?.wrappedValue }.eraseToAnyAsyncSequence() + portfoliosSubject.compactMap { $0[address].unwrap()?.wrappedValue }.removeDuplicates().eraseToAnyAsyncSequence() } private func setOrUpdateAccountPortfolio(_ portfolio: AccountPortfoliosClient.AccountPortfolio) { diff --git a/RadixWallet/Core/FeaturePrelude/Loadable.swift b/RadixWallet/Core/FeaturePrelude/Loadable.swift index 51beae7301..8d4212f76c 100644 --- a/RadixWallet/Core/FeaturePrelude/Loadable.swift +++ b/RadixWallet/Core/FeaturePrelude/Loadable.swift @@ -313,23 +313,25 @@ extension Loadable { case let (.idle, .success(otherValue)), let (.loading, .success(otherValue)), let (.failure, .success(otherValue)): - print("Refresh from nonSuccessValue") + self = .success(otherValue) // Update to new value only if it changed case let (.success(oldValue), .success(newValue)): - print("Refresh from success value with success") if oldValue != newValue { self = .success(valueChangeMap(oldValue, newValue)) } // If current state is success, don't update if `other` is loading or failed case (.success, _): - print("Refresh from success value with other value") break + + case (.loading, .loading), + (.idle, .idle): + break + // If current state is other than .success case let (_, other): - print("Refresh from any value with other value") self = other } } diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift index a3855a5a4f..5cbdc91825 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift @@ -8,7 +8,12 @@ extension Home { public var id: AccountAddress { account.address } public var accountWithInfo: AccountWithInfo - public var portfolio: Loadable + public var portfolio: Loadable { + didSet { + totalFiatWorth.refresh(from: portfolio.totalFiatWorth.flatten()) + } + } + public var showFiatWorth: Bool = true public var totalFiatWorth: Loadable @@ -75,16 +80,15 @@ extension Home { public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { switch internalAction { case let .accountPortfolioUpdate(portfolio): - state.isDappDefinitionAccount = portfolio.account.metadata.accountType == .dappDefinition - assert(portfolio.account.address == state.account.address) - var newValue = state.portfolio - newValue.refresh(from: .success(portfolio)) - if state.portfolio != newValue { - state.portfolio = newValue + + guard state.portfolio != .success(portfolio) else { + return .none } - // state.portfolio.refresh(from: .success(portfolio)) - // state.totalFiatWorth.refresh(from: portfolio.totalFiatWorth) + + state.isDappDefinitionAccount = portfolio.account.metadata.accountType == .dappDefinition + state.portfolio.refresh(from: .success(portfolio)) + return .send(.internal(.checkAccountAccessToMnemonic)) case .checkAccountAccessToMnemonic: From 75c0ea21556dc581722e9b03d3764783d7bee04d Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 13:13:22 +0200 Subject: [PATCH 03/10] wip --- RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift index 90a31cbbc7..1226e85f42 100644 --- a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift @@ -249,6 +249,7 @@ public struct AssetsView: Sendable, FeatureReducer { extension AccountPortfoliosClient.AccountPortfolio { mutating func refresh(from portfolio: AccountPortfoliosClient.AccountPortfolio) { + self.account = portfolio.account self.stakeUnitDetails.refresh(from: portfolio.stakeUnitDetails) self.poolUnitDetails.refresh(from: portfolio.poolUnitDetails) } From 7a2020109aef98c64441c49d848eba1b19998e90 Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 13:31:40 +0200 Subject: [PATCH 04/10] wip --- RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift | 2 ++ .../Children/AccountRow/Home+AccountRow+Reducer.swift | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift index 1226e85f42..4ffa341223 100644 --- a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift @@ -250,6 +250,8 @@ public struct AssetsView: Sendable, FeatureReducer { extension AccountPortfoliosClient.AccountPortfolio { mutating func refresh(from portfolio: AccountPortfoliosClient.AccountPortfolio) { self.account = portfolio.account + self.isCurrencyAmountVisible = portfolio.isCurrencyAmountVisible + self.fiatCurrency = portfolio.fiatCurrency self.stakeUnitDetails.refresh(from: portfolio.stakeUnitDetails) self.poolUnitDetails.refresh(from: portfolio.poolUnitDetails) } diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift index 5cbdc91825..5970bbfbb4 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift @@ -58,12 +58,14 @@ extension Home { self.checkAccountAccessToMnemonic(state: &state) - return .run { send in + return .run { [portfolio = state.portfolio] send in for try await accountPortfolio in await accountPortfoliosClient.portfolioForAccount(accountAddress) { guard !Task.isCancelled else { return } + // if portfolio != .success(accountPortfolio) { await send(.internal(.accountPortfolioUpdate(accountPortfolio))) + // } } } case .exportMnemonicButtonTapped: From 9fa37f4f12f0c1d084e506a8b875f2861be6ff41 Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 13:49:10 +0200 Subject: [PATCH 05/10] improve --- .../AccountRow/Home+AccountRow+Reducer.swift | 50 ++++++++++--------- .../AccountRow/Home+AccountRow+View.swift | 12 ++--- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift index 5970bbfbb4..004c282c89 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift @@ -8,12 +8,7 @@ extension Home { public var id: AccountAddress { account.address } public var accountWithInfo: AccountWithInfo - public var portfolio: Loadable { - didSet { - totalFiatWorth.refresh(from: portfolio.totalFiatWorth.flatten()) - } - } - + public var accountWithResources: Loadable public var showFiatWorth: Bool = true public var totalFiatWorth: Loadable @@ -21,7 +16,7 @@ extension Home { account: Profile.Network.Account ) { self.accountWithInfo = .init(account: account) - self.portfolio = .loading + self.accountWithResources = .loading self.totalFiatWorth = .loading } } @@ -34,7 +29,8 @@ extension Home { } public enum InternalAction: Sendable, Equatable { - case accountPortfolioUpdate(AccountPortfoliosClient.AccountPortfolio) + case accountUpdated(OnLedgerEntity.Account) + case fiatWorthUpdated(Loadable) case checkAccountAccessToMnemonic } @@ -52,22 +48,28 @@ extension Home { switch viewAction { case .task: let accountAddress = state.account.address - if state.portfolio.wrappedValue == nil { - state.portfolio = .loading - } - self.checkAccountAccessToMnemonic(state: &state) - return .run { [portfolio = state.portfolio] send in - for try await accountPortfolio in await accountPortfoliosClient.portfolioForAccount(accountAddress) { + return .run { send in + for try await accountPortfolio in await accountPortfoliosClient.portfolioForAccount(accountAddress).map(\.account).removeDuplicates() { guard !Task.isCancelled else { return } // if portfolio != .success(accountPortfolio) { - await send(.internal(.accountPortfolioUpdate(accountPortfolio))) + await send(.internal(.accountUpdated(accountPortfolio))) // } } } + .merge(with: .run { send in + for try await fiatWorth in await accountPortfoliosClient.portfolioForAccount(accountAddress).map(\.totalFiatWorth).removeDuplicates() { + guard !Task.isCancelled else { + return + } + // if portfolio != .success(accountPortfolio) { + await send(.internal(.fiatWorthUpdated(fiatWorth))) + // } + } + }) case .exportMnemonicButtonTapped: return .send(.delegate(.exportMnemonic)) @@ -81,26 +83,26 @@ extension Home { public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { switch internalAction { - case let .accountPortfolioUpdate(portfolio): - assert(portfolio.account.address == state.account.address) + case let .accountUpdated(account): + assert(account.address == state.account.address) - guard state.portfolio != .success(portfolio) else { - return .none - } - - state.isDappDefinitionAccount = portfolio.account.metadata.accountType == .dappDefinition - state.portfolio.refresh(from: .success(portfolio)) + state.isDappDefinitionAccount = account.metadata.accountType == .dappDefinition + state.accountWithResources.refresh(from: .success(account)) return .send(.internal(.checkAccountAccessToMnemonic)) case .checkAccountAccessToMnemonic: checkAccountAccessToMnemonic(state: &state) return .none + + case let .fiatWorthUpdated(fiatWorth): + state.totalFiatWorth.refresh(from: fiatWorth) + return .none } } private func checkAccountAccessToMnemonic(state: inout State) { - state.checkAccountAccessToMnemonic(portfolio: state.portfolio.account.wrappedValue) + state.checkAccountAccessToMnemonic(portfolio: state.accountWithResources.wrappedValue) } } } diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+View.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+View.swift index 0cd771d28d..f6dd63e7c2 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+View.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+View.swift @@ -47,7 +47,7 @@ extension Home.AccountRow { self.appearanceID = state.account.appearanceID self.showFiatWorth = state.showFiatWorth self.fiatWorth = state.totalFiatWorth - self.isLoadingResources = state.portfolio.isLoading + self.isLoadingResources = state.accountWithResources.isLoading self.tag = .init(state: state) self.isLedgerAccount = state.isLedgerAccount @@ -55,7 +55,7 @@ extension Home.AccountRow { self.mnemonicHandlingCallToAction = state.mnemonicHandlingCallToAction // Resources - guard let portfolio = state.portfolio.wrappedValue else { + guard let accountWithResources = state.accountWithResources.wrappedValue else { self.fungibleResourceIcons = [] self.nonFungibleResourcesCount = 0 self.stakedValidatorsCount = 0 @@ -64,13 +64,13 @@ extension Home.AccountRow { return } - let fungibleResources = portfolio.account.fungibleResources + let fungibleResources = accountWithResources.fungibleResources let xrdIcon: [Thumbnail.TokenContent] = fungibleResources.xrdResource != nil ? [.xrd] : [] let otherIcons: [Thumbnail.TokenContent] = fungibleResources.nonXrdResources.map { .other($0.metadata.iconURL) } self.fungibleResourceIcons = xrdIcon + otherIcons - self.nonFungibleResourcesCount = portfolio.account.nonFungibleResources.count - self.stakedValidatorsCount = portfolio.account.poolUnitResources.radixNetworkStakes.count - self.poolUnitsCount = portfolio.account.poolUnitResources.poolUnits.count + self.nonFungibleResourcesCount = accountWithResources.nonFungibleResources.count + self.stakedValidatorsCount = accountWithResources.poolUnitResources.radixNetworkStakes.count + self.poolUnitsCount = accountWithResources.poolUnitResources.poolUnits.count } } From c92a72e5d867ca349f09397103f2b25721c439f4 Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 14:19:00 +0200 Subject: [PATCH 06/10] wip --- .../AccountPortfoliosClient+Interface.swift | 4 ++++ .../AccountPortfoliosClient+Live.swift | 5 +++++ .../AccountPortfoliosClient+Mock.swift | 3 ++- .../Core/FeaturePrelude/Loadable.swift | 2 +- .../AccountRow/Home+AccountRow+Reducer.swift | 15 ++------------- .../HomeFeature/Coordinator/Home.swift | 19 +++++++++++++++++++ 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Interface.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Interface.swift index 83ea234b5c..233450cd6c 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Interface.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Interface.swift @@ -10,6 +10,8 @@ public struct AccountPortfoliosClient: Sendable { /// Will return the portfolio after fetch, as well will notify any subscribes through `portfolioForAccount` public var fetchAccountPortfolio: FetchAccountPortfolio + public var portfolioUpdates: PortfolioUpdates + /// Subscribe to portfolio changes for a given account address public var portfolioForAccount: PortfolioForAccount @@ -21,6 +23,8 @@ extension AccountPortfoliosClient { public typealias FetchAccountPortfolio = @Sendable (_ address: AccountAddress, _ forceResfresh: Bool) async throws -> AccountPortfolio public typealias FetchAccountPortfolios = @Sendable (_ addresses: [AccountAddress], _ forceResfresh: Bool) async throws -> [AccountPortfolio] public typealias PortfolioForAccount = @Sendable (_ address: AccountAddress) async -> AnyAsyncSequence + + public typealias PortfolioUpdates = @Sendable () -> AnyAsyncSequence> public typealias Portfolios = @Sendable () -> [AccountPortfolio] } diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift index c58c1331a8..f01e45e864 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift @@ -137,6 +137,11 @@ extension AccountPortfoliosClient: DependencyKey { return portfolio }, + portfolioUpdates: { + state.portfoliosSubject + .map { $0.map { Array($0.values) } } + .eraseToAnyAsyncSequence() + }, portfolioForAccount: { address in await state.portfolioForAccount(address) }, diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift index 61caf26162..c8d775d58d 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift @@ -4,7 +4,7 @@ extension AccountPortfoliosClient: TestDependencyKey { public static let testValue = AccountPortfoliosClient( fetchAccountPortfolios: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolios"), - fetchAccountPortfolio: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), + fetchAccountPortfolio: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), portfolioUpdates: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), portfolioForAccount: unimplemented("\(AccountPortfoliosClient.self).portfolioForAccount"), portfolios: unimplemented("\(AccountPortfoliosClient.self).portfolios") ) @@ -12,6 +12,7 @@ extension AccountPortfoliosClient: TestDependencyKey { public static let noop = AccountPortfoliosClient( fetchAccountPortfolios: { _, _ in throw NoopError() }, fetchAccountPortfolio: { _, _ in throw NoopError() }, + portfolioUpdates: { fatalError() }, portfolioForAccount: { _ in fatalError() }, portfolios: { fatalError() } ) diff --git a/RadixWallet/Core/FeaturePrelude/Loadable.swift b/RadixWallet/Core/FeaturePrelude/Loadable.swift index 8d4212f76c..342d0a021d 100644 --- a/RadixWallet/Core/FeaturePrelude/Loadable.swift +++ b/RadixWallet/Core/FeaturePrelude/Loadable.swift @@ -223,7 +223,7 @@ extension Loadable { } } - public func firstd(where predicate: (Value.Element) -> Bool) -> Loadable where Value: Sequence { + public func first(where predicate: (Value.Element) -> Bool) -> Loadable where Value: Sequence { switch self { case .idle: return .idle diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift index 004c282c89..3f953897d8 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift @@ -51,25 +51,14 @@ extension Home { self.checkAccountAccessToMnemonic(state: &state) return .run { send in - for try await accountPortfolio in await accountPortfoliosClient.portfolioForAccount(accountAddress).map(\.account).removeDuplicates() { - guard !Task.isCancelled else { - return - } - // if portfolio != .success(accountPortfolio) { - await send(.internal(.accountUpdated(accountPortfolio))) - // } - } - } - .merge(with: .run { send in for try await fiatWorth in await accountPortfoliosClient.portfolioForAccount(accountAddress).map(\.totalFiatWorth).removeDuplicates() { guard !Task.isCancelled else { return } - // if portfolio != .success(accountPortfolio) { await send(.internal(.fiatWorthUpdated(fiatWorth))) - // } } - }) + } + case .exportMnemonicButtonTapped: return .send(.delegate(.exportMnemonic)) diff --git a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift index 0976013e17..371c9b7ec6 100644 --- a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift +++ b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift @@ -47,6 +47,7 @@ public struct Home: Sendable, FeatureReducer { case loadedShouldWriteDownPersonasSeedPhrase(Bool) case currentGatewayChanged(to: Radix.Gateway) case shouldShowNPSSurvey(Bool) + case accountsResourcesLoaded(Loadable<[OnLedgerEntity.Account]>) } public enum ChildAction: Sendable, Equatable { @@ -151,6 +152,7 @@ public struct Home: Sendable, FeatureReducer { .merge(with: loadShouldWriteDownPersonasSeedPhrase()) .merge(with: loadGateways()) .merge(with: loadNPSSurveyStatus()) + .merge(with: loadAccountResources()) case .createAccountButtonTapped: state.destination = .createAccount( @@ -206,6 +208,14 @@ public struct Home: Sendable, FeatureReducer { errorQueue.schedule(error) return .none + case let .accountsResourcesLoaded(accountsResources): + state.accountRows.mutateAll { row in + if let accountResources = accountsResources.first(where: { $0.address == row.id }).unwrap() { + row.accountWithResources.refresh(from: accountResources) + } + } + return .none + case let .loadedShouldWriteDownPersonasSeedPhrase(shouldBackup): state.shouldWriteDownPersonasSeedPhrase = shouldBackup return .none @@ -372,6 +382,15 @@ public struct Home: Sendable, FeatureReducer { await npsSurveyClient.uploadUserFeedback(feedback) } } + + private func loadAccountResources() -> Effect { + .run { send in + for try await accountResources in accountPortfoliosClient.portfolioUpdates().map { $0.map { $0.map(\.account) } }.removeDuplicates() { + guard !Task.isCancelled else { return } + await send(.internal(.accountsResourcesLoaded(accountResources))) + } + } + } } extension Home.State { From 89dc8149122204143411ffd4755e2356e8264fb2 Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 15:18:48 +0200 Subject: [PATCH 07/10] wip --- .../Core/FeaturePrelude/Loadable.swift | 4 ++++ .../AccountRow/Home+AccountRow+Reducer.swift | 10 +-------- .../HomeFeature/Coordinator/Home.swift | 22 +++++++++++++++++++ .../Models/AccountWithInfoHolder.swift | 11 +++++----- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/RadixWallet/Core/FeaturePrelude/Loadable.swift b/RadixWallet/Core/FeaturePrelude/Loadable.swift index 342d0a021d..f121fc080b 100644 --- a/RadixWallet/Core/FeaturePrelude/Loadable.swift +++ b/RadixWallet/Core/FeaturePrelude/Loadable.swift @@ -292,6 +292,10 @@ extension Loadable { concat(other).map(join) } + public func reduce(_ join: (Element) -> Void) -> Void where Value == Array { + + } + public mutating func mutateValue(_ mutate: (inout Value) -> Void) { switch self { case .idle, .loading, .failure: diff --git a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift index 3f953897d8..2f8ebf73d9 100644 --- a/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift +++ b/RadixWallet/Features/HomeFeature/Children/AccountRow/Home+AccountRow+Reducer.swift @@ -47,17 +47,9 @@ extension Home { public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { switch viewAction { case .task: - let accountAddress = state.account.address self.checkAccountAccessToMnemonic(state: &state) - return .run { send in - for try await fiatWorth in await accountPortfoliosClient.portfolioForAccount(accountAddress).map(\.totalFiatWorth).removeDuplicates() { - guard !Task.isCancelled else { - return - } - await send(.internal(.fiatWorthUpdated(fiatWorth))) - } - } + return .none case .exportMnemonicButtonTapped: return .send(.delegate(.exportMnemonic)) diff --git a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift index 371c9b7ec6..c3e6d8c8cb 100644 --- a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift +++ b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift @@ -391,6 +391,28 @@ public struct Home: Sendable, FeatureReducer { } } } + + private func loadFiatValues() -> Effect { + .run { _ in + let observable = accountPortfoliosClient.portfolioUpdates() + .compactMap { portfoliosLoadable in + portfoliosLoadable.wrappedValue?.reduce(into: [AccountAddress: Loadable]()) { partialResult, portfolio in + partialResult[portfolio.account.address] = portfolio.totalFiatWorth + } + } + .filter { + Array($0.values).reduce(+) + } + + // for try await accountResources in accountPortfoliosClient.portfolioUpdates() + // .map { + // $0.map { $0.map { ($0.account.address, $0.totalFiatWorth) + // } } }.removeDuplicates() { + // guard !Task.isCancelled else { return } + // await send(.internal(.accountsResourcesLoaded(accountResources))) + // } + } + } } extension Home.State { diff --git a/RadixWallet/Features/HomeFeature/Models/AccountWithInfoHolder.swift b/RadixWallet/Features/HomeFeature/Models/AccountWithInfoHolder.swift index 4535922f80..3453daffb9 100644 --- a/RadixWallet/Features/HomeFeature/Models/AccountWithInfoHolder.swift +++ b/RadixWallet/Features/HomeFeature/Models/AccountWithInfoHolder.swift @@ -47,12 +47,13 @@ extension DeviceFactorSourceControlled { return } - guard let xrdResource else { - mnemonicHandlingCallToAction = nil - return - } +// guard let xrdResource else { +// mnemonicHandlingCallToAction = nil +// return +// } - let hasValue = xrdResource.amount.nominalAmount > 0 + // Disabled for now, to be revised with MFA + let hasValue = true // xrdResource.amount.nominalAmount > 0 let hasAlreadyBackedUpMnemonic = userDefaults.getFactorSourceIDOfBackedUpMnemonics().contains(factorSourceID) let exportMnemonicNeeded = !hasAlreadyBackedUpMnemonic && hasValue From 0c54e47f03eeb5bbf8a492dd23246a85c0871d0e Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 15:36:20 +0200 Subject: [PATCH 08/10] wip --- RadixWallet/Clients/TokenPriceClient/TokenPriceClient+Live.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/RadixWallet/Clients/TokenPriceClient/TokenPriceClient+Live.swift b/RadixWallet/Clients/TokenPriceClient/TokenPriceClient+Live.swift index 716be8681c..36458663b6 100644 --- a/RadixWallet/Clients/TokenPriceClient/TokenPriceClient+Live.swift +++ b/RadixWallet/Clients/TokenPriceClient/TokenPriceClient+Live.swift @@ -48,6 +48,7 @@ extension TokenPricesClient.TokenPrices { formatter.numberStyle = .decimal formatter.maximumFractionDigits = Int(RETDecimal.maxDivisibility) formatter.roundingMode = .down + formatter.decimalSeparator = "." // Enfore dot notation for RETDecimal self = tokenPricesResponse.tokens.reduce(into: [:]) { partialResult, next in let trimmed = formatter.string(for: next.price) ?? "" From 2ee1b1c4afb09301bc34492a771597810fb5901b Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 18:00:48 +0200 Subject: [PATCH 09/10] wip --- .../AccountPortfoliosClient+Live.swift | 6 +-- .../ResourceBalance/ResourceBalanceView.swift | 2 +- .../Components/ValidatorStakeView.swift | 1 + .../HomeFeature/Coordinator/Home+View.swift | 2 +- .../HomeFeature/Coordinator/Home.swift | 39 +++++++++++-------- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift index f01e45e864..f35119fa7c 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Live.swift @@ -66,6 +66,9 @@ extension AccountPortfoliosClient: DependencyKey { 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] = { @@ -116,9 +119,6 @@ extension AccountPortfoliosClient: DependencyKey { } } - let portfolios = accounts.map { AccountPortfolio(account: $0) } - await state.handlePortfoliosUpdate(portfolios) - // Load additional details _ = await accounts.parallelMap(fetchPoolAndStakeUnitsDetails) diff --git a/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceView.swift b/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceView.swift index df00b4430c..878e5ba867 100644 --- a/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceView.swift +++ b/RadixWallet/Features/AssetsFeature/Components/HelperViews/ResourceBalance/ResourceBalanceView.swift @@ -386,7 +386,7 @@ extension ResourceBalanceView { } .disabled(onTap == nil) .buttonStyle(.borderless) - .roundedCorners(strokeColor: .red) // .app.gray3 + .roundedCorners(strokeColor: .app.gray3) } } } diff --git a/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/Components/ValidatorStakeView.swift b/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/Components/ValidatorStakeView.swift index d13b34244b..c4064c606a 100644 --- a/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/Components/ValidatorStakeView.swift +++ b/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/Components/ValidatorStakeView.swift @@ -83,6 +83,7 @@ struct ValidatorStakeView: View { onTap: onTap, onClaimAllTapped: onClaimAllTapped ) + .padding(.small1) } } } diff --git a/RadixWallet/Features/HomeFeature/Coordinator/Home+View.swift b/RadixWallet/Features/HomeFeature/Coordinator/Home+View.swift index 442d57cda3..5eab49d784 100644 --- a/RadixWallet/Features/HomeFeature/Coordinator/Home+View.swift +++ b/RadixWallet/Features/HomeFeature/Coordinator/Home+View.swift @@ -6,7 +6,7 @@ extension Home.State { .init( hasNotification: shouldWriteDownPersonasSeedPhrase, showRadixBanner: showRadixBanner, - totalFiatWorth: totalFiatWorth + totalFiatWorth: showFiatWorth ? totalFiatWorth : nil ) } } diff --git a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift index c3e6d8c8cb..8614bd3e8e 100644 --- a/RadixWallet/Features/HomeFeature/Coordinator/Home.swift +++ b/RadixWallet/Features/HomeFeature/Coordinator/Home.swift @@ -13,13 +13,7 @@ public struct Home: Sendable, FeatureReducer { public var showRadixBanner: Bool = false public var showFiatWorth: Bool = true - public var totalFiatWorth: Loadable? { - guard showFiatWorth else { - return nil - } - - return accountRows.map(\.totalFiatWorth).reduce(+) - } + public var totalFiatWorth: Loadable = .idle // MARK: - Destination @PresentationState @@ -48,6 +42,7 @@ public struct Home: Sendable, FeatureReducer { case currentGatewayChanged(to: Radix.Gateway) case shouldShowNPSSurvey(Bool) case accountsResourcesLoaded(Loadable<[OnLedgerEntity.Account]>) + case accountsFiatWorthLoaded([AccountAddress: Loadable]) } public enum ChildAction: Sendable, Equatable { @@ -153,6 +148,7 @@ public struct Home: Sendable, FeatureReducer { .merge(with: loadGateways()) .merge(with: loadNPSSurveyStatus()) .merge(with: loadAccountResources()) + .merge(with: loadFiatValues()) case .createAccountButtonTapped: state.destination = .createAccount( @@ -241,6 +237,14 @@ public struct Home: Sendable, FeatureReducer { state.destination = .npsSurvey(.init()) } return .none + case let .accountsFiatWorthLoaded(fiatWorths): + state.accountRows.mutateAll { + if let fiatWorth = fiatWorths[$0.id] { + $0.totalFiatWorth.refresh(from: fiatWorth) + } + } + state.totalFiatWorth = state.accountRows.map(\.totalFiatWorth).reduce(+) ?? .loading + return .none } } @@ -393,24 +397,25 @@ public struct Home: Sendable, FeatureReducer { } private func loadFiatValues() -> Effect { - .run { _ in - let observable = accountPortfoliosClient.portfolioUpdates() + .run { send in + let accountsTotalFiatWorth = accountPortfoliosClient.portfolioUpdates() .compactMap { portfoliosLoadable in portfoliosLoadable.wrappedValue?.reduce(into: [AccountAddress: Loadable]()) { partialResult, portfolio in partialResult[portfolio.account.address] = portfolio.totalFiatWorth } } .filter { - Array($0.values).reduce(+) + // All items should load + if let aggregated = Array($0.values).reduce(+), aggregated.didLoad { + return true + } + return false } - // for try await accountResources in accountPortfoliosClient.portfolioUpdates() - // .map { - // $0.map { $0.map { ($0.account.address, $0.totalFiatWorth) - // } } }.removeDuplicates() { - // guard !Task.isCancelled else { return } - // await send(.internal(.accountsResourcesLoaded(accountResources))) - // } + for try await accountsTotalFiatWorth in accountsTotalFiatWorth.removeDuplicates() { + guard !Task.isCancelled else { return } + await send(.internal(.accountsFiatWorthLoaded(accountsTotalFiatWorth))) + } } } } From 60b223143c782ff04079f1dae8469dc1a927672c Mon Sep 17 00:00:00 2001 From: Ghenadie Vasiliev-Pusca Date: Tue, 19 Mar 2024 18:56:27 +0200 Subject: [PATCH 10/10] wip --- .../AccountPortfoliosClient+Mock.swift | 5 +++-- .../Features/AssetsFeature/AssetsView+Reducer.swift | 10 ++++++---- .../FungibleAssetList/FungibleAssetList+Reducer.swift | 8 +------- .../NonFungibleAssetList+Reducer.swift | 4 ---- .../Components/StakeUnitList/StakeUnitList.swift | 4 +++- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift index c8d775d58d..7678c4eae3 100644 --- a/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift +++ b/RadixWallet/Clients/AccountPortfoliosClient/AccountPortfoliosClient+Mock.swift @@ -4,7 +4,8 @@ extension AccountPortfoliosClient: TestDependencyKey { public static let testValue = AccountPortfoliosClient( fetchAccountPortfolios: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolios"), - fetchAccountPortfolio: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), portfolioUpdates: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), + fetchAccountPortfolio: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), + portfolioUpdates: unimplemented("\(AccountPortfoliosClient.self).fetchAccountPortfolio"), portfolioForAccount: unimplemented("\(AccountPortfoliosClient.self).portfolioForAccount"), portfolios: unimplemented("\(AccountPortfoliosClient.self).portfolios") ) @@ -12,7 +13,7 @@ extension AccountPortfoliosClient: TestDependencyKey { public static let noop = AccountPortfoliosClient( fetchAccountPortfolios: { _, _ in throw NoopError() }, fetchAccountPortfolio: { _, _ in throw NoopError() }, - portfolioUpdates: { fatalError() }, + portfolioUpdates: { AsyncLazySequence([]).eraseToAnyAsyncSequence() }, portfolioForAccount: { _ in fatalError() }, portfolios: { fatalError() } ) diff --git a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift index 4ffa341223..0c1b07f576 100644 --- a/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/AssetsView+Reducer.swift @@ -195,7 +195,7 @@ public struct AssetsView: Sendable, FeatureReducer { return nil } - return .init(sections: sections) + return .init(sections: sections, destination: state.resources.fungibleTokenList?.destination) }() state.accountPortfolio.refresh(from: .success(portfolio)) @@ -210,7 +210,8 @@ public struct AssetsView: Sendable, FeatureReducer { }, isSelected: mode.nonXrdRowSelected(poolUnit.resource.resourceAddress) ) - }.asIdentifiable() + }.asIdentifiable(), + destination: state.resources.poolUnitsList?.destination ) let stakes = portfolio.account.poolUnitResources.radixNetworkStakes @@ -234,13 +235,14 @@ public struct AssetsView: Sendable, FeatureReducer { dict[resource] = selectedtokens } } : nil, - stakeUnitDetails: state.accountPortfolio.stakeUnitDetails.flatten() + stakeUnitDetails: state.accountPortfolio.stakeUnitDetails.flatten(), + destination: state.resources.stakeUnitList?.destination ) state.totalFiatWorth.refresh(from: portfolio.totalFiatWorth) state.resources = .init( fungibleTokenList: fungibleTokenList, - nonFungibleTokenList: !nfts.isEmpty ? .init(rows: .init(uniqueElements: nfts)) : nil, + nonFungibleTokenList: !nfts.isEmpty ? .init(rows: nfts.asIdentifiable(), destination: state.resources.nonFungibleTokenList?.destination) : nil, stakeUnitList: stakeUnitList, poolUnitsList: poolUnitList ) diff --git a/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/FungibleAssetList+Reducer.swift b/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/FungibleAssetList+Reducer.swift index da4dce3970..423fe297d4 100644 --- a/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/FungibleAssetList+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/Components/FungibleAssetList/FungibleAssetList+Reducer.swift @@ -4,16 +4,10 @@ import SwiftUI // MARK: - FungibleAssetList public struct FungibleAssetList: Sendable, FeatureReducer { public struct State: Sendable, Hashable { - public var sections: IdentifiedArrayOf + public var sections: IdentifiedArrayOf = [] @PresentationState public var destination: Destination.State? - - public init( - sections: IdentifiedArrayOf = [] - ) { - self.sections = sections - } } @CasePathable diff --git a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/NonFungibleAssetList+Reducer.swift b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/NonFungibleAssetList+Reducer.swift index b89ddaabad..d86a09c78a 100644 --- a/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/NonFungibleAssetList+Reducer.swift +++ b/RadixWallet/Features/AssetsFeature/Components/NonFungibleAssetList/NonFungibleAssetList+Reducer.swift @@ -7,10 +7,6 @@ public struct NonFungibleAssetList: Sendable, FeatureReducer { @PresentationState public var destination: Destination.State? - - public init(rows: IdentifiedArrayOf) { - self.rows = rows - } } public enum ChildAction: Sendable, Equatable { diff --git a/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/StakeUnitList.swift b/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/StakeUnitList.swift index 021d5045fc..286b179706 100644 --- a/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/StakeUnitList.swift +++ b/RadixWallet/Features/AssetsFeature/Components/StakeUnitList/StakeUnitList.swift @@ -23,11 +23,13 @@ public struct StakeUnitList: Sendable, FeatureReducer { account: OnLedgerEntity.Account, selectedLiquidStakeUnits: IdentifiedArrayOf?, selectedStakeClaimTokens: SelectedStakeClaimTokens?, - stakeUnitDetails: Loadable> + stakeUnitDetails: Loadable>, + destination: Destination.State? = nil ) { self.account = account self.selectedLiquidStakeUnits = selectedLiquidStakeUnits self.selectedStakeClaimTokens = selectedStakeClaimTokens + self.destination = destination switch stakeUnitDetails { case .idle, .loading: