Skip to content

Commit

Permalink
IOS-8906 [Balance caching] Repository (#4470)
Browse files Browse the repository at this point in the history
  • Loading branch information
Balashov152 authored Jan 23, 2025
1 parent df9812f commit da69139
Show file tree
Hide file tree
Showing 53 changed files with 477 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import TangemStaking

struct CommonWalletModelsFactory {
private let config: UserWalletConfig
private let userWalletId: UserWalletId

init(config: UserWalletConfig) {
init(config: UserWalletConfig, userWalletId: UserWalletId) {
self.config = config
self.userWalletId = userWalletId
}

private func isDerivationDefault(blockchain: Blockchain, derivationPath: DerivationPath?) -> Bool {
Expand Down Expand Up @@ -92,6 +94,7 @@ extension CommonWalletModelsFactory: WalletModelsFactory {
let isMainCoinCustom = !isDerivationDefault(blockchain: currentBlockchain, derivationPath: currentDerivation)
let blockchainNetwork = BlockchainNetwork(currentBlockchain, derivationPath: currentDerivation)
let sendAvailabilityProvider = TransactionSendAvailabilityProvider(isSendingSupportedByCard: config.hasFeature(.send))
let tokenBalancesRepository = CommonTokenBalancesRepository(userWalletId: userWalletId)

if types.contains(.coin) {
let tokenItem: TokenItem = .blockchain(blockchainNetwork)
Expand All @@ -111,7 +114,8 @@ extension CommonWalletModelsFactory: WalletModelsFactory {
amountType: .coin,
shouldPerformHealthCheck: shouldPerformHealthCheck,
isCustom: isMainCoinCustom,
sendAvailabilityProvider: sendAvailabilityProvider
sendAvailabilityProvider: sendAvailabilityProvider,
tokenBalancesRepository: tokenBalancesRepository
)
models.append(mainCoinModel)
}
Expand All @@ -137,7 +141,8 @@ extension CommonWalletModelsFactory: WalletModelsFactory {
amountType: amountType,
shouldPerformHealthCheck: shouldPerformHealthCheck,
isCustom: isTokenCustom,
sendAvailabilityProvider: sendAvailabilityProvider
sendAvailabilityProvider: sendAvailabilityProvider,
tokenBalancesRepository: tokenBalancesRepository
)
models.append(tokenModel)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import BlockchainSdk
struct DemoWalletModelsFactory {
private let factory: CommonWalletModelsFactory

init(config: UserWalletConfig) {
factory = CommonWalletModelsFactory(config: config)
init(config: UserWalletConfig, userWalletId: UserWalletId) {
factory = CommonWalletModelsFactory(config: config, userWalletId: userWalletId)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ extension GenericConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ extension GenericDemoConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return DemoWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return DemoWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ extension LegacyConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
4 changes: 2 additions & 2 deletions Tangem/App/Models/UserWallet/Implementations/NoteConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ extension NoteConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ extension NoteDemoConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return DemoWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return DemoWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ extension Start2CoinConfig: UserWalletConfig {
)
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
4 changes: 2 additions & 2 deletions Tangem/App/Models/UserWallet/Implementations/TwinConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ extension TwinConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
4 changes: 2 additions & 2 deletions Tangem/App/Models/UserWallet/Implementations/VisaConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ extension VisaConfig: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self)
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,12 @@ extension Wallet2Config: UserWalletConfig {
}
}

func makeWalletModelsFactory() -> WalletModelsFactory {
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory {
if isDemo {
return DemoWalletModelsFactory(config: self)
return DemoWalletModelsFactory(config: self, userWalletId: userWalletId)
}

return CommonWalletModelsFactory(config: self)
return CommonWalletModelsFactory(config: self, userWalletId: userWalletId)
}

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory {
Expand Down
2 changes: 1 addition & 1 deletion Tangem/App/Models/UserWallet/UserWalletConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protocol UserWalletConfig: OnboardingStepsBuilderFactory, BackupServiceFactory,

func getFeatureAvailability(_ feature: UserWalletFeature) -> UserWalletFeature.Availability

func makeWalletModelsFactory() -> WalletModelsFactory
func makeWalletModelsFactory(userWalletId: UserWalletId) -> WalletModelsFactory

func makeAnyWalletManagerFactory() throws -> AnyWalletManagerFactory

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension FormattedTokenBalanceType: CustomStringConvertible {
switch self {
case .loading(let cached): "Loading cached balance: \(String(describing: cached))"
case .failure(let cached): "Failure cached balance: \(String(describing: cached))"
case .loaded(let balance): "Loaded balance: \(balance)"
case .loaded: "Loaded balance"
}
}
}
Expand All @@ -73,8 +73,12 @@ extension FormattedTokenBalanceType {
}
}

struct Cached: Hashable {
struct Cached: Hashable, CustomStringConvertible {
let balance: String
let date: Date

var description: String {
"Cached balance on date: \(date.formatted())"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ extension FiatTokenBalanceProvider: TokenBalanceProvider {

var balanceTypePublisher: AnyPublisher<TokenBalanceType, Never> {
Publishers.CombineLatest(
// Listen if rate was loaded after main balance
walletModel.ratePublisher.removeDuplicates(),
cryptoBalanceProvider.balanceTypePublisher.removeDuplicates()
)
Expand All @@ -52,52 +51,70 @@ extension FiatTokenBalanceProvider: TokenBalanceProvider {
// MARK: - Private

extension FiatTokenBalanceProvider {
func mapToTokenBalance(rate: LoadingResult<WalletModel.Rate?, Never>, balanceType: TokenBalanceType) -> TokenBalanceType {
func mapToTokenBalance(rate: WalletModel.Rate, balanceType: TokenBalanceType) -> TokenBalanceType {
switch (rate, balanceType) {
// There is no rate because it's custom token
case (.success(.none), _) where walletModel.isCustom:
// There is no rate because token is custom
case (.custom, _):
return .empty(.custom)

// There is no crypto value to convert
// There is no crypto
case (_, .empty(let reason)):
return .empty(reason)

// There is no rate but main balance is still loading
// Main balance is still loading
// Then show loading to avoid showing empty fiat when crypto is loading
case (.success(.none), .loading):
// Or rate is loading
case (_, .loading(.none)), (.loading(.none), _):
return .loading(.none)

// There is no rate
case (.success(.none), _):
return .empty(.noData)

// There is no crypto value because there was an error
case (_, .failure(.none)):
// There is no crypto or no rate value without cache
case (.failure(.none), _), (_, .failure(.none)):
return .failure(.none)

// There is rate is loading and we can't convert it
case (.loading, _), (_, .loading(.none)):
return .loading(.none)
// Both is loading and has cached value
case (.loading(.some(let rate)), .loading(.some(let cached))):
let fiat = cached.balance * rate.price
return .loading(.init(balance: fiat, date: cached.date))

// Both is failure and has cached value
case (.failure(.some(let rate)), .failure(.some(let cached))):
let fiat = cached.balance * rate.price
return .failure(.init(balance: fiat, date: cached.date))

// Has some rate and cached value
case (.success(.some(let rate)), .loading(.some(let cached))):
let fiat = cached.balance * rate.value
// Has some rate and loading cached value
case (.loaded(let rate), .loading(.some(let cached))):
let fiat = cached.balance * rate.price
return .loading(.init(balance: fiat, date: cached.date))

// Has some rate and cached value
case (.success(.some(let rate)), .failure(.some(let cached))):
let fiat = cached.balance * rate.value
// Has some rate and failure cached value
case (.loaded(let rate), .failure(.some(let cached))):
let fiat = cached.balance * rate.price
return .failure(.init(balance: fiat, date: cached.date))

// Has cached rate and cached value
case (.success(.some(.cached(let rate))), .loaded(let value)):
let fiat = value * rate.balance
// Has some rate and loaded value
case (.loaded(let rate), .loaded(let value)):
let fiat = value * rate.price
return .loaded(fiat)

// Has some cached rate and loading cached value
case (.failure(.some(let rate)), .loading(.some(let cached))):
let fiat = cached.balance * rate.price
return .loading(.init(balance: fiat, date: rate.date))

// Has some cached rate and loaded crypto value
case (.failure(.some(let rate)), .loaded(let value)):
let fiat = value * rate.price
return .failure(.init(balance: fiat, date: rate.date))

// Has some rate and cached value
case (.success(.some(.actual(let rate))), .loaded(let value)):
let fiat = value * rate
return .loaded(fiat)
// Rate is loading with cached value and crypto cached value
case (.loading(.some(let rate)), .failure(.some(let cached))):
let fiat = cached.balance * rate.price
return .loading(.init(balance: fiat, date: rate.date))

// Rate is loading with cached value and crypto loaded value
case (.loading(.some(let rate)), .loaded(let value)):
let fiat = value * rate.price
return .loading(.init(balance: fiat, date: rate.date))
}
}

Expand Down
12 changes: 8 additions & 4 deletions Tangem/App/Services/BalanceProvider/TokenBalanceType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ extension TokenBalanceType {
case .empty: nil
case .loading(let cached): cached
case .failure(let cached): cached
case .loaded(let value): nil
case .loaded: nil
}
}

Expand All @@ -64,9 +64,9 @@ extension TokenBalanceType: CustomStringConvertible {
var description: String {
switch self {
case .empty(let reason): "Empty \(reason)"
case .loading(let cached): "Loading cached: \(String(describing: cached))"
case .loading(let cached): "Loading with cached: \(String(describing: cached))"
case .failure(let cached): "Failure cached: \(String(describing: cached))"
case .loaded(let balance): "Loaded: \(balance)"
case .loaded: "Loaded"
}
}
}
Expand All @@ -85,8 +85,12 @@ extension TokenBalanceType {
case noAccount(message: String)
}

struct Cached: Hashable {
struct Cached: Hashable, CustomStringConvertible {
let balance: Decimal
let date: Date

var description: String {
"Cached balance on date: \(date.formatted())"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ enum PersistentStorageKey {
case pendingOnrampTransactions
case pendingStakingTransactions
case onrampPreference
case cachedBalances
case cachedQuotes

var path: String {
switch self {
Expand All @@ -36,6 +38,10 @@ enum PersistentStorageKey {
return "staking_pending_transactions"
case .onrampPreference:
return "onramp_preference"
case .cachedBalances:
return "cached_balances"
case .cachedQuotes:
return "cached_quotes"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// CommonTokenBalancesRepository.swift
// TangemApp
//
// Created by Sergey Balashov on 26.12.2024.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import Foundation
import Combine
import TangemFoundation

struct CommonTokenBalancesRepository {
@Injected(\.tokenBalancesStorage)
private var storage: TokenBalancesStorage
private let userWalletId: UserWalletId

init(userWalletId: UserWalletId) {
self.userWalletId = userWalletId
}
}

// MARK: - TokenBalancesRepository

extension CommonTokenBalancesRepository: TokenBalancesRepository {
func balance(walletModel: WalletModel, type: CachedBalanceType) -> CachedBalance? {
storage.balance(for: walletModel.id, userWalletId: userWalletId, type: type)
}

func store(balance: CachedBalance, for walletModel: WalletModel, type: CachedBalanceType) {
storage.store(balance: balance, type: type, id: walletModel.id, userWalletId: userWalletId)
}
}
Loading

0 comments on commit da69139

Please sign in to comment.