Cache wallet balance and activity so screens show it instantly

This commit is contained in:
2026-09-25 21:58:34 +08:00
parent 2728bf161e
commit b989b0f791
3 changed files with 55 additions and 5 deletions
+26
View File
@@ -41,6 +41,27 @@ final class AppModel: ObservableObject {
var symbol: String { chain.symbol }
// MARK: - Cache
/// Restores the last-known state for the chain on screen instantly; the
/// refresh that follows replaces it with live data.
private func showCached(_ forChain: Chain) {
guard let cached = WalletStore.cachedState(forChain) else { return }
balance = cached.balance
transactions = cached.transactions
if let accounts = cached.accounts { moneroAccounts = accounts }
}
private func persistState(for forChain: Chain) {
guard (forChain == .xrp ? xrpWallet : xmrWallet) != nil else { return }
WalletStore.saveCachedState(
ChainCache(balance: balance,
transactions: transactions,
accounts: forChain == .xmr ? moneroAccounts : nil,
savedAt: Date()),
chain: forChain)
}
// MARK: - Bootstrap
func bootstrap() async {
@@ -57,6 +78,7 @@ final class AppModel: ObservableObject {
if xrpWallet != nil || moneroReady {
chain = xrpWallet != nil ? .xrp : .xmr
}
showCached(chain)
await refresh(force: true)
startAutoRefresh()
}
@@ -101,7 +123,9 @@ final class AppModel: ObservableObject {
statusMessage = nil
balance = Balance(total: "0", unlocked: "", funded: false)
transactions = []
moneroAccounts = []
syncStatus = nil
showCached(newChain)
if newChain == .xmr, let xmrWallet, !moneroOpened {
await openMonero(afterRestore: false, wallet: xmrWallet)
@@ -217,6 +241,7 @@ final class AppModel: ObservableObject {
guard !isStale(token, requestChain) else { return }
balance = fetchedBalance
transactions = fetchedTransactions
persistState(for: requestChain)
} catch {
guard !isStale(token, requestChain) else { return }
errorMessage = error.localizedDescription
@@ -251,6 +276,7 @@ final class AppModel: ObservableObject {
balance = fetchedBalance
transactions = fetchedTransactions
moneroAccounts = fetchedAccounts
persistState(for: requestChain)
}
} catch {
guard !isStale(token, requestChain) else { return }
+14 -5
View File
@@ -18,12 +18,12 @@ struct WalletRecord: Codable, Identifiable, Equatable {
var createdAt: Date
}
enum Direction {
enum Direction: String, Codable {
case incoming
case outgoing
}
struct TransactionRecord: Identifiable {
struct TransactionRecord: Identifiable, Codable {
let id: String
let amount: String
let timestamp: Date?
@@ -44,12 +44,21 @@ struct TransactionRecord: Identifiable {
}
struct Balance {
struct Balance: Codable {
var total: String
var unlocked: String
var funded: Bool
}
/// Persisted per chain so a switch or relaunch shows the last-known state
/// instantly while the network catches up.
struct ChainCache: Codable {
var balance: Balance
var transactions: [TransactionRecord]
var accounts: [MoneroAccount]?
var savedAt: Date
}
struct SyncStatus {
var height: UInt64
var daemonHeight: UInt64
@@ -78,7 +87,7 @@ enum TransactionPriority: Int {
}
}
struct MoneroSubaddress: Identifiable {
struct MoneroSubaddress: Identifiable, Codable {
let accountIndex: Int
let index: Int
let address: String
@@ -90,7 +99,7 @@ struct MoneroSubaddress: Identifiable {
var id: String { "\(accountIndex)/\(index)" }
}
struct MoneroAccount: Identifiable {
struct MoneroAccount: Identifiable, Codable {
let index: Int
let address: String
let balance: String
+15
View File
@@ -63,9 +63,24 @@ enum WalletStore {
static func removeWallet(_ chain: Chain) {
defaults.removeObject(forKey: walletKey(chain))
defaults.removeObject(forKey: cacheKey(chain))
Keychain.remove(secretKey(chain))
}
// MARK: - Cached state (balance, transactions, accounts)
private static func cacheKey(_ chain: Chain) -> String { "koins.cache.\(chain.rawValue)" }
static func cachedState(_ chain: Chain) -> ChainCache? {
guard let data = defaults.data(forKey: cacheKey(chain)) else { return nil }
return try? JSONDecoder().decode(ChainCache.self, from: data)
}
static func saveCachedState(_ state: ChainCache, chain: Chain) {
guard let data = try? JSONEncoder().encode(state) else { return }
defaults.set(data, forKey: cacheKey(chain))
}
static func secret(_ chain: Chain) -> String? {
Keychain.get(secretKey(chain))
}