diff --git a/package.json b/package.json index 33068f13..9acbe3c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minibits_wallet", - "version": "0.1.9-beta.29", + "version": "0.1.9-beta.30", "private": true, "scripts": { "android:clean": "cd android && ./gradlew clean", diff --git a/src/models/RootStore.ts b/src/models/RootStore.ts index d80f52fa..1e321cca 100644 --- a/src/models/RootStore.ts +++ b/src/models/RootStore.ts @@ -11,7 +11,7 @@ import {WalletStoreModel} from './WalletStore' import {NwcStoreModel} from './NwcStore' import { log } from '../services' -export const rootStoreModelVersion = 28 // Update this if model changes require migrations defined in setupRootStore.ts +export const rootStoreModelVersion = 29 // Update this if model changes require migrations defined in setupRootStore.ts /** * A RootStore model. diff --git a/src/models/Transaction.ts b/src/models/Transaction.ts index 5916df97..256c1309 100644 --- a/src/models/Transaction.ts +++ b/src/models/Transaction.ts @@ -54,8 +54,8 @@ export enum TransactionStatus { } export const TransactionModel = types - .model('Transaction', { - id: types.number, + .model('Transaction', { + id: types.identifierNumber, type: types.frozen(), amount: types.integer, fee: types.optional(types.integer, 0), diff --git a/src/models/TransactionsStore.ts b/src/models/TransactionsStore.ts index 3ac11bce..0b8f4aa2 100644 --- a/src/models/TransactionsStore.ts +++ b/src/models/TransactionsStore.ts @@ -19,9 +19,7 @@ import { formatDistance } from 'date-fns' import { MintUnit } from '../services/wallet/currency' import { Mint } from './Mint' -export const maxTransactionsInModel = 10 -export const maxTransactionsByMint = 10 -export const maxTransactionsByHostname = 3 +export const maxTransactionsInHistory = 10 export const maxTransactionsByUnit = 3 export type GroupedByTimeAgo = { @@ -30,191 +28,222 @@ export type GroupedByTimeAgo = { export const TransactionsStoreModel = types .model('TransactionsStore', { - transactions: types.array(TransactionModel), + transactionsMap: types.map(TransactionModel), + history: types.array(types.safeReference(TransactionModel, { acceptsUndefined: false })), + recentByUnit: types.array(types.safeReference(TransactionModel, { acceptsUndefined: false })), }) .actions(withSetPropAction) .views(self => ({ - get all() { - return self.transactions + get pendingHistory() { + return self.history + .slice() + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + .filter(t => t.status === TransactionStatus.PENDING) + }, + get historyByTimeAgo() { + return self.history + .slice() + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + .reduce((groups: GroupedByTimeAgo, transaction: Transaction) => { + const timeAgo = formatDistance(transaction.createdAt as Date, new Date(), {addSuffix: true}) + if (!groups[timeAgo]) { + groups[timeAgo] = [] + } + groups[timeAgo].push(transaction) + return groups + }, {}) + }, + get historyPendingByTimeAgo() { + return this.pendingHistory.reduce((groups: GroupedByTimeAgo, transaction: Transaction) => { + const timeAgo = formatDistance(transaction.createdAt as Date, new Date(), {addSuffix: true}) + if (!groups[timeAgo]) { + groups[timeAgo] = [] + } + groups[timeAgo].push(transaction) + return groups + }, {}) + }, + get historyCount() { + return self.history.length + }, + get pendingHistoryCount() { + return this.pendingHistory.length + }, + getRecentByUnit(unit: MintUnit) { + return self.recentByUnit .slice() - .sort((a, b) => { - // Sort by createdAt timestamp - if (a.createdAt && b.createdAt) { - return b.createdAt.getTime() - a.createdAt.getTime() - } - }) as Transaction[] + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + .filter(t => t.unit === unit) }, - get count() { - return self.transactions.length - }, - get recent() { - return this.all.slice(0, 3) // Return the first 3 transactions - }, - get pending() { - return this.all.filter(t => t.status === TransactionStatus.PENDING) - }, - get groupedByTimeAgo() { - return this.all.reduce((groups: GroupedByTimeAgo, transaction: Transaction) => { - const timeAgo = formatDistance(transaction.createdAt as Date, new Date(), {addSuffix: true}) - if (!groups[timeAgo]) { - groups[timeAgo] = [] - } - groups[timeAgo].push(transaction) - return groups - }, {}) - }, - get groupedPendingByTimeAgo() { - return this.pending.reduce((groups: GroupedByTimeAgo, transaction: Transaction) => { - const timeAgo = formatDistance(transaction.createdAt as Date, new Date(), {addSuffix: true}) - if (!groups[timeAgo]) { - groups[timeAgo] = [] - } - groups[timeAgo].push(transaction) - return groups - }, {}) - }, - - recentByHostname(mintHostname: string) { - return this.all.filter(t => getHostname(t.mint as string) === mintHostname).slice(0, maxTransactionsByHostname) - }, - recentByUnit(unit: MintUnit, count?: number) { - if (!count || count > maxTransactionsByUnit) { - count = maxTransactionsByUnit - } - - return this.all.filter(t => t.unit === unit).slice(0, count) - }, - recentByHostnameGroupedByTimeAgo(mintHostname: string) { - const recentByHostname = this.recentByHostname(mintHostname) - - return recentByHostname.reduce((groups: GroupedByTimeAgo, transaction: Transaction) => { - const timeAgo = formatDistance(transaction.createdAt as Date, new Date(), {addSuffix: true}) - if (!groups[timeAgo]) { - groups[timeAgo] = [] - } - groups[timeAgo].push(transaction) - return groups - }, {}) - }, - getByMint(mintUrl: string) { - return this.all.filter(t => t.mint === mintUrl) - }, - countByMint(mintUrl: string) { - return this.getByMint(mintUrl).length - } - })) + countRecentByUnit(unit: MintUnit) { + return this.getRecentByUnit(unit).length + } + })) .actions(self => ({ - findById(id: number) { - - let tx = self.transactions.find(tx => tx.id === id) + findById(id: number) { + let transaction = self.transactionsMap.get(id) // Search the db and add if tx is not in the state - if(!tx) { + if(!transaction) { const dbTransaction = Database.getTransactionById(id) if(dbTransaction) { - const createdAt = new Date(dbTransaction.createdAt) + const createdAt = new Date(dbTransaction.createdAt) const inStoreTransaction = {...dbTransaction, createdAt} - - tx = TransactionModel.create(inStoreTransaction) - self.transactions.push(tx) + const {id} = dbTransaction + + self.transactionsMap.set(id, inStoreTransaction) + transaction = self.transactionsMap.get(id) } } - return tx + return transaction }, - removeOldTransactions: () => { // not used - const numTransactions = self.count + /*pruneTransactionsMap() { + // Clean up transactionMap: remove transactions not in history or recentByUnit + self.transactionsMap.forEach((_, transactionId) => { + const isInHistory = self.history.some(t => t.id === transactionId) + const isInRecentByUnit = self.recentByUnit.some(t => t.id === transactionId) - // If there are more than 10 transactions, delete the older ones - if (numTransactions > maxTransactionsInModel) { - self.transactions - .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) - .splice(maxTransactionsInModel) // Remove transactions beyond the desired number to keep - - log.debug('[removeOldTransactions]', `${ - numTransactions - maxTransactionsInModel - } transaction(s) removed from TransactionsStore`, - ) - } - }, - removeOldByMint: (mintUrl: string) => { - const numByMint = self.countByMint(mintUrl) + // If the transaction is not in history or recentByUnit, remove it from transactionMap + if (!isInHistory && !isInRecentByUnit) { + self.transactionsMap.delete(transactionId as string) + log.trace(`[pruneTransactionsMap] Transaction ${transactionId} pruned from the map`) + } else { + log.trace(`[pruneTransactionsMap] Transaction ${transactionId} kept in the map`) + } + }) + },*/ + pruneRecentByUnit(unit: MintUnit) { + const unitCount = self.countRecentByUnit(unit) - if (numByMint > maxTransactionsByMint) { - const transactionsToRemove = self.getByMint(mintUrl) - .slice() - .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) - .slice(maxTransactionsByMint) - - transactionsToRemove.map((t) => { - detach(t) - }) + log.trace('[pruneRecentByUnit]', {unit, unitCount}) + + if (unitCount > maxTransactionsByUnit) { + const transactionsToRemove = self.getRecentByUnit(unit) + .slice() + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + .splice(maxTransactionsByUnit) - self.transactions.replace(self.transactions.filter(t => !transactionsToRemove.some(removed => removed.id === t.id))) + self.recentByUnit.replace(self.recentByUnit.filter(t => !transactionsToRemove.some(removed => removed.id === t.id))) - const txByMintAfterDelete = self.countByMint(mintUrl) - const txTotalAfterDelete = self.count - - log.trace('[removeOldByMint]', {txByMintAfterDelete, txTotalAfterDelete}) + log.trace('[pruneRecentByUnit]', `${transactionsToRemove.length} pruned from recentByUnit`) } }, - removeAllWithoutCurrentMint: () => { + pruneRecentWithoutCurrentMint() { const rootStore = getRootStore(self) const {mintsStore} = rootStore - const transactionsToRemove = self.transactions.filter(transaction => { + const transactionsToRemove = self.recentByUnit.filter(transaction => { // Check if the mint property of the transaction does not exist in the mints array return !mintsStore.allMints.some((mint: Mint) => mint.mintUrl === transaction.mint); - }); + }) - self.transactions.replace(self.transactions.filter(t => !transactionsToRemove.some(removed => removed.id === t.id))) + self.recentByUnit.replace(self.recentByUnit.filter(t => !transactionsToRemove.some(removed => removed.id === t.id))) - const txTotalAfterDelete = self.count + log.trace('[pruneRecentWithoutCurrentMint]', `${transactionsToRemove.length} pruned from recentByUnit`) + }, + pruneHistory() { + // Step 1: Trim history to keep only the MAX_HISTORY_TRANSACTIONS most recent + if (self.history.length > maxTransactionsInHistory) { + const transactionsToRemove = self.history + .slice() + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + .splice(maxTransactionsInHistory) - log.trace('[removeAllWithoutCurrentMint]', {deleted: transactionsToRemove.length, txTotalAfterDelete}) - - } + self.history.replace(self.history.filter(t => !transactionsToRemove.some(removed => removed.id === t.id))) + + log.trace('[pruneHistory]', `${transactionsToRemove.length} pruned from history`) + } + }, + removeAllHistory() { + self.history.clear() + log.debug('[removeAllHistory]', 'Removed all transactions from history') + }, + removeAllRecentByUnit() { + self.recentByUnit.clear() + log.debug('[removeAllRecentByUnit]', 'Removed all transactions from recentByUnit') + }, + removeAllTransactions() { + self.recentByUnit.clear() + self.history.clear() + self.transactionsMap.clear() + log.debug('[removeAllTransactions]', 'Removed all transactions from TransactionsStore') + }, })) .actions(self => ({ addTransaction: flow(function* addTransaction(newTransaction){ // First let's store the transaction into the database - const dbTransaction: TransactionRecord = yield Database.addTransactionAsync(newTransaction) + const dbTransaction: TransactionRecord = yield Database.addTransactionAsync(newTransaction) // Add the new transaction to the transactions store - const createdAt = new Date(dbTransaction.createdAt) + const createdAt = new Date(dbTransaction.createdAt) const inStoreTransaction = {...dbTransaction, createdAt} + const {id} = dbTransaction - const transactionInstance = TransactionModel.create(inStoreTransaction) - self.transactions.push(transactionInstance) + if (!self.transactionsMap.has(id)) { + self.transactionsMap.set(id, inStoreTransaction) + } + + const reference = self.transactionsMap.get(id) + self.history.unshift(reference!) + self.recentByUnit.unshift(reference!) log.debug('[addTransaction]', 'New transaction added to the TransactionsStore') - // Purge the oldest transaction from cache, but keep some for each mint - self.removeOldByMint(newTransaction.mint) + // Purge the oldest references from cache, but keep some for each mint + self.pruneRecentByUnit(newTransaction.unit) + self.pruneHistory() - return transactionInstance as Transaction + return reference as Transaction }), - addTransactionsToModel: (dbTransactions: TransactionRecord[]) => { - // This adds to model only. Used to have observable UI in tx history loaded from database. - const inStoreTransactions: Transaction[] = [] + addToHistory(limit: number, offset: number, onlyPending: boolean){ + // Appends transaction to the map and adds reference to history from database. + const result = Database.getTransactions(limit, offset, onlyPending) + log.trace('[addToHistory] dbResult ids', {ids: result?._array.map(t => t.id)}) - for (const dbTransaction of dbTransactions) { - const createdAt = new Date(dbTransaction.createdAt) - const inStoreTransaction = {...dbTransaction, createdAt} + if (result && result.length > 0) { + for (const dbTransaction of result._array) { + const createdAt = new Date(dbTransaction.createdAt) + const inStoreTransaction = {...dbTransaction, createdAt} + const {id} = dbTransaction - if(self.findById(inStoreTransaction.id as number)) { - log.trace('[addTransactionsToModel] Transaction already exists in the model, skipping...') - continue - } - - const transactionInstance = TransactionModel.create(inStoreTransaction) - inStoreTransactions.push(transactionInstance as Transaction) + if (!self.transactionsMap.has(id)) { + self.transactionsMap.set(id, inStoreTransaction) + log.trace('[addToHistory]', `${id} added to transactionsMap`) + } + + const reference = self.transactionsMap.get(id) + + if (!self.history.find(t => t.id === id)) { + self.history.push(reference!) + log.trace('[addToHistory]', `${onlyPending ? 'Pending reference' : 'Reference'} ${id} added to history`) + } + } } + }, + addRecentByUnit() { + // Rehydrates recent from database. + const dbTransactions = Database.getRecentTransactionsByUnit(maxTransactionsByUnit) - self.transactions.push(...inStoreTransactions) - - log.debug('[addTransactionsToModel]', `${inStoreTransactions.length} new transactions added to TransactionsStore`) + if (dbTransactions && dbTransactions.length > 0) { + for (const dbTransaction of dbTransactions) { + const createdAt = new Date(dbTransaction.createdAt) + const inStoreTransaction = {...dbTransaction, createdAt} as Transaction + const {id} = dbTransaction + + if (!self.transactionsMap.has(id)) { + self.transactionsMap.set(id, inStoreTransaction) + } + + const reference = self.transactionsMap.get(id) + + if (!self.recentByUnit.find(t => t.id === id)) { + self.recentByUnit.push(reference!) + log.trace('[addRecentByUnit]', `Transaction ${inStoreTransaction.id} added to recentByUnit`) + } + } + } }, updateStatuses: flow(function* updateStatuses( ids: number[], @@ -226,7 +255,7 @@ export const TransactionsStoreModel = types // Update the model status and amend related tx data for (const id of ids) { - const transactionInstance = self.findById(id) + const transactionInstance = self.transactionsMap.get(id) if (transactionInstance) { transactionInstance.status = status @@ -235,36 +264,50 @@ export const TransactionsStoreModel = types const updatedData = JSON.parse(transactionInstance.data) updatedData.push(JSON.parse(data)) transactionInstance.data = JSON.stringify(updatedData) + } - log.debug('[updateStatuses]', 'Transaction statuses and data updated in TransactionsStore', {ids, status}) - } + log.trace('[updateStatuses]', 'Transaction statuses and data updated in TransactionsStore', {ids, status}) } - }), - expireAllAfterRecovery: flow(function* expireAllAfterRecovery() { - // Update status in database - yield Database.expireAllAfterRecovery() + }), + deleteByStatus(status: TransactionStatus){ - // Update the model statuses - for (const t of self.all) { - t.setIsExpired() - } - }), - deleteByStatus: (status: TransactionStatus) => { - for (const transaction of self.transactions) { - if(transaction.status === status) { - detach(transaction) + self.transactionsMap.forEach((transaction, transactionId) => { + if (transaction.status === status) { + self.transactionsMap.delete(transactionId as string) } - } - - self.transactions.replace(self.transactions.filter(t => t.status !== status)) + }) return Database.deleteTransactionsByStatus(status) - }, - removeAllTransactions() { - self.transactions.clear() - log.debug('[removeAllTransactions]', 'Removed all transactions from TransactionsStore') - }, - })) + } + })).postProcessSnapshot((snapshot) => { + // Trim history if it exceeds the limit + let prunedHistory = snapshot.history + + if (snapshot.history.length > maxTransactionsInHistory) { + // Keep only the most recent transactions within the limit + const orderedHistory = [...snapshot.history].sort((a, b) => (b as number) - (a as number)) + prunedHistory = orderedHistory.slice(0, maxTransactionsInHistory) + } + + // Clean up transactionMap: remove transactions not in history or recentByUnit + const prunedTransactionsMap = Object.fromEntries( + Object.entries(snapshot.transactionsMap).filter(([transactionId]) => + prunedHistory.includes(parseInt(transactionId)) || + snapshot.recentByUnit.includes(parseInt(transactionId)) + ) + ) + + // Return the new snapshot with the trimmed history and filtered transactionMap + const prunedSnapshot = { + ...snapshot, + history: prunedHistory, + transactionsMap: prunedTransactionsMap + } + + console.log('[postProcessSnapshot]', {prunedSnapshot}) + + return prunedSnapshot + }) const getHostname = function (mintUrl: string) { try { diff --git a/src/models/helpers/setupRootStore.ts b/src/models/helpers/setupRootStore.ts index 26e22b43..fef2c5fa 100644 --- a/src/models/helpers/setupRootStore.ts +++ b/src/models/helpers/setupRootStore.ts @@ -55,14 +55,13 @@ export async function setupRootStore(rootStore: RootStore) { // load the last known state from storage const start = performance.now() - restoredState = MMKVStorage.load(ROOT_STORAGE_KEY) || {} - const mmkvLoaded = performance.now() + restoredState = MMKVStorage.load(ROOT_STORAGE_KEY) || {} + const mmkvLoaded = performance.now() const dataSize = Buffer.byteLength(JSON.stringify(restoredState), 'utf8') - log.trace('[setupRootStore]', `Loading ${dataSize.toLocaleString()} bytes of state from MMKV took ${(mmkvLoaded - start).toLocaleString()} ms.`) + log.trace({restoredState}) + log.trace('[setupRootStore]', `Loading ${dataSize.toLocaleString()} bytes of state from MMKV took ${(mmkvLoaded - start).toLocaleString()} ms.`) - // log.trace({restoredState}) - applySnapshot(rootStore, restoredState) const stateHydrated = performance.now() @@ -129,123 +128,7 @@ async function _runMigrations(rootStore: RootStore) { let currentVersion = rootStore.version - try { - - if(currentVersion < 6) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v6`) - userSettingsStore.setLogLevel(LogLevel.ERROR) - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - rootStore.setVersion(rootStoreModelVersion) - } - - - if(currentVersion < 7) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v7`) - for (const mint of mintsStore.allMints) { - mint.setStatus(MintStatus.ONLINE) - } - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - rootStore.setVersion(rootStoreModelVersion) - } - - - if(currentVersion < 8) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v8`) - const seedHash = await KeyChain.loadSeedHash() - - if(seedHash && walletProfileStore.pubkey) { - await MinibitsClient.migrateSeedHash( - walletProfileStore.pubkey, - { - seedHash - } - ) - - walletProfileStore.setSeedHash(seedHash) - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - rootStore.setVersion(rootStoreModelVersion) - } - } - - if(currentVersion < 9) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v9`) - - for (const mint of mintsStore.allMints) { - try { - await mint.setShortname() - } catch (e: any) { - continue - } - } - - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - rootStore.setVersion(rootStoreModelVersion) - } - - if(currentVersion < 10) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v10`) - - for (const contact of contactsStore.all) { - if(contact.isExternalDomain === false) { - contact.setLud16(contact.nip05 as string) - } - } - - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - rootStore.setVersion(rootStoreModelVersion) - } - - if(currentVersion < 11) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v11`) - // userSettingsStore.setIsStorageMigrated(true) - rootStore.setVersion(rootStoreModelVersion) - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - } - - if(currentVersion < 12) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v12`) - - for (const mint of mintsStore.allMints) { - try { - mint.addUnit('sat') - mint.resetCounters() - } catch (e: any) { - continue - } - } - - rootStore.setVersion(rootStoreModelVersion) - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - } - - if(currentVersion < 13) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v13`) - - userSettingsStore.setPreferredUnit('sat') - - for (const proof of proofsStore.allProofs) { - try { - if(!proof.unit) { - proof.setUnit('sat') - } - } catch (e: any) { - continue - } - } - - for (const tx of transactionsStore.all) { - try { - if(!tx.unit) { - tx.setUnit('sat') - } - } catch (e: any) { - continue - } - } - - rootStore.setVersion(rootStoreModelVersion) - log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) - } + try { if(currentVersion < 14) { log.trace(`Starting rootStore migrations from version v${currentVersion} -> v14`) @@ -295,7 +178,7 @@ async function _runMigrations(rootStore: RootStore) { log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) } if(currentVersion < 28) { - log.trace(`Starting rootStore migrations from version v${currentVersion} -> v27`) + log.trace(`Starting rootStore migrations from version v${currentVersion} -> v28`) userSettingsStore.setExchangeCurrency(CurrencyCode.USD) userSettingsStore.setTheme(ThemeCode.DEFAULT) @@ -315,6 +198,14 @@ async function _runMigrations(rootStore: RootStore) { } } + rootStore.setVersion(rootStoreModelVersion) + log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) + } + if(currentVersion < 29) { + log.trace(`Starting rootStore migrations from version v${currentVersion} -> v29`) + + transactionsStore.addRecentByUnit() + rootStore.setVersion(rootStoreModelVersion) log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`) } diff --git a/src/screens/DeveloperScreen.tsx b/src/screens/DeveloperScreen.tsx index 546dd692..f1f0c868 100644 --- a/src/screens/DeveloperScreen.tsx +++ b/src/screens/DeveloperScreen.tsx @@ -32,12 +32,12 @@ import {translate} from '../i18n' import AppError from '../utils/AppError' import {Database, KeyChain, NostrClient, log} from '../services' import {MMKVStorage} from '../services' -import {maxTransactionsInModel} from '../models/TransactionsStore' import { LogLevel } from '../services/log/logTypes' import { getSnapshot } from 'mobx-state-tree' import { delay } from '../utils/delay' import RNExitApp from 'react-native-exit-app' import { TransactionStatus } from '../models/Transaction' +import { maxTransactionsInHistory } from '../models/TransactionsStore' // refresh @@ -77,11 +77,11 @@ export const DeveloperScreen: FC> = observ }, []) // Reset of transaction model state and reload from DB - const syncTransactionsFromDb = async function () { + const syncTransactionsFromDb = function () { setIsLoading(true) try { - const result = await Database.getTransactionsAsync( - maxTransactionsInModel, + const result = Database.getTransactions( + maxTransactionsInHistory, 0, ) @@ -89,8 +89,10 @@ export const DeveloperScreen: FC> = observ // remove all from the transactionsStore model transactionsStore.removeAllTransactions() - // Add last 10 from database - transactionsStore.addTransactionsToModel(result._array) + // Add last 10 to history + transactionsStore.addToHistory(maxTransactionsInHistory, 0, false) + // Add recent by unit + transactionsStore.addRecentByUnit() setIsLoading(false) setInfo(translate('resetCompletedDetail', { transCount: result.length })) @@ -131,7 +133,7 @@ export const DeveloperScreen: FC> = observ proofsStore.removeProofs(pending, true, false) } - await syncTransactionsFromDb() + syncTransactionsFromDb() setIsLoading(false) setInfo(`Removed pending transactions from the database and ${pendingCount} proofs from the wallet state`) @@ -142,8 +144,7 @@ export const DeveloperScreen: FC> = observ }, }, ], - ) - + ) } @@ -254,8 +255,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId} subText={userSettingsStore.logLevel.toUpperCase()} leftIcon='faListUl' leftIconColor={colors.palette.iconMagenta200} - leftIconInverse={true} - RightComponent={} + leftIconInverse={true} style={$item} bottomSeparator={true} onPress={toggleLogLevelSelector} @@ -263,14 +263,22 @@ Sentry id: ${userSettingsStore.userSettings.walletId} } - style={$item} + leftIconInverse={true} + style={$item} + bottomSeparator={true} onPress={() => userSettingsStore.setIsOnboarded(false)} /> - + } /> diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx index 47a3ab1c..bea37f79 100644 --- a/src/screens/TranDetailScreen.tsx +++ b/src/screens/TranDetailScreen.tsx @@ -75,9 +75,6 @@ export const TranDetailScreen: FC> = const noteInputRef = useRef(null) const [transaction, setTransaction] = useState() - /* const [proofsByStatus, setProofsByStatus] = useState< - ProofsByStatus | undefined - >(undefined)*/ const [error, setError] = useState() const [isNoteEditing, setIsNoteEditing] = useState(transaction?.noteToSelf ? false : true) const [isDataParsable, setIsDataParsable] = useState(true) diff --git a/src/screens/TranHistoryScreen.tsx b/src/screens/TranHistoryScreen.tsx index 01636195..8b453b53 100644 --- a/src/screens/TranHistoryScreen.tsx +++ b/src/screens/TranHistoryScreen.tsx @@ -27,24 +27,24 @@ import { import {TransactionsStackScreenProps} from '../navigation' import {useHeader} from '../utils/useHeader' import {useStores} from '../models' -import {GroupedByTimeAgo, maxTransactionsInModel} from '../models/TransactionsStore' import {Database, log} from '../services' import AppError from '../utils/AppError' import {TransactionListItem} from './Transactions/TransactionListItem' import { Transaction, TransactionStatus } from '../models/Transaction' import { height } from '@fortawesome/free-solid-svg-icons/faWallet' import { translate } from '../i18n' +import { maxTransactionsInHistory } from '../models/TransactionsStore' if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true) } // Number of transactions held in TransactionsStore model -const limit = maxTransactionsInModel +const limit = maxTransactionsInHistory export const TranHistoryScreen: FC> = observer(function TranHistoryScreen(_props) { const {navigation} = _props - const {transactionsStore, proofsStore, mintsStore} = useStores() + const {transactionsStore, mintsStore} = useStores() useHeader({ leftIcon: 'faArrowLeft', onLeftPress: () => navigation.goBack(), @@ -58,9 +58,7 @@ export const TranHistoryScreen: FC> const [isLoading, setIsLoading] = useState(false) const [isHeaderVisible, setIsHeaderVisible] = useState(true) const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false) - const [offset, setOffset] = useState(transactionsStore.count) // load from db those that are not already displayed - const [pendingOffset, setPendingOffset] = useState(transactionsStore.pending.length) // load from db those that are not already displayed - const [dbCount, setDbCount] = useState(0) + const [totalDbCount, setTotalDbCount] = useState(0) const [pendingDbCount, setPendingDbCount] = useState(0) const [expiredDbCount, setExpiredDbCount] = useState(0) const [erroredDbCount, setErroredDbCount] = useState(0) @@ -71,106 +69,111 @@ export const TranHistoryScreen: FC> useEffect(() => { const init = async () => { setIsLoading(true) - const count = Database.getTransactionsCount() // all - const pendingCount = Database.getTransactionsCount(TransactionStatus.PENDING) - const expiredCount = Database.getTransactionsCount(TransactionStatus.EXPIRED) - const erroredCount = Database.getTransactionsCount(TransactionStatus.ERROR) - const revertedCount = Database.getTransactionsCount(TransactionStatus.REVERTED) + const countByStatus = Database.getTransactionsCount() - log.trace('transaction counts', {count, pendingCount, erroredCount, revertedCount}) + log.trace('Database transaction counts', {countByStatus}) + + setPendingDbCount(countByStatus[TransactionStatus.PENDING] || 0) + setExpiredDbCount(countByStatus[TransactionStatus.EXPIRED] || 0) + setErroredDbCount(countByStatus[TransactionStatus.ERROR] || 0) + setRevertedDbCount(countByStatus[TransactionStatus.REVERTED] || 0) + setTotalDbCount(countByStatus.total) - setDbCount(count) - setPendingDbCount(pendingCount) - setExpiredDbCount(expiredCount) - setErroredDbCount(erroredCount) - setRevertedDbCount(revertedCount) + // Preload transactions to model in case they are not there + if(countByStatus.total > 0) { + if(transactionsStore.historyCount === 0) { + transactionsStore.addToHistory(limit, 0, false) + } + + if(transactionsStore.recentByUnit.length === 0) { + transactionsStore.addRecentByUnit() + } + } setIsLoading(false) - if (count <= limit) { - log.trace('setAll true') + if (countByStatus.total <= limit) { + log.trace('[init] setAll true') setIsAll(true) } - if (pendingCount <= limit) { + /* if (countByStatus[TransactionStatus.PENDING] <= limit) { log.trace('setPendingAll true') setPendingIsAll(true) - } - // Run on component unmount (cleanup) - return () => { - /* When leaving screen we remove all transactions over maxTransactionsByMint - * from the transactionsStore that might have been sourced from sqlite db while browsing older records - */ - for (const mint of mintsStore.allMints) { - transactionsStore.removeOldByMint(mint.mintUrl) - transactionsStore.removeAllWithoutCurrentMint() // avoid that tx from deleted mints remain in model forever - } - } + }*/ } init() - return () => {} + return () => { + if(showPendingOnly) { + // Full clean if filtered, next visit will reload from db + transactionsStore.removeAllHistory() + } else { + // Keep recent in history to load fast on next visit + transactionsStore.pruneHistory() + } + // general cleanup - avoid that tx from deleted mints remain in state forever + transactionsStore.pruneRecentWithoutCurrentMint() + } }, []) const toggleDeleteModal = () => { setIsDeleteModalVisible(previousState => !previousState) } - // TODO debug - const getTransactionsList = async function () { + + const addTransactionsToList = function () { setIsLoading(true) try { - const result = await Database.getTransactionsAsync(limit, offset) + transactionsStore.addToHistory(limit, transactionsStore.historyCount, false) - if (result && result.length > 0) { - // Add new transaction to the transactions store so mobx refreshes UI - transactionsStore.addTransactionsToModel(result._array) + log.trace('[addTransactionsToList]', { + currentOffset: transactionsStore.historyCount, + totalDbCount + }) - setOffset(offset + result.length) - - log.trace({storeCount: transactionsStore.count, dbCount}) - if (transactionsStore.count >= dbCount) { - log.trace('[getTransactionsList] setAll true') - setIsAll(true) - } - } + if (transactionsStore.historyCount >= totalDbCount) { + log.trace('[getTransactionsList] setAll true') + setIsAll(true) + } setIsLoading(false) } catch (e: any) { handleError(e) } - } + } + - - // TODO debug - const getPendingTransactionsList = async function () { + const addPendingTransactionsToList = function () { setIsLoading(true) try { - const result = await Database.getTransactionsAsync(limit, pendingOffset, true) // pending + transactionsStore.addToHistory(limit, transactionsStore.historyCount, true) - if (result && result.length > 0) { - // Add new transaction to the transactions store so that mobx refreshes UI - transactionsStore.addTransactionsToModel(result._array) + log.trace('[addTransactionsToList] onlyPending', { + currentOffset: transactionsStore.historyCount, + pendingDbCount + }) - setOffset(pendingOffset + result.length) - - if (transactionsStore.pending.length === pendingDbCount) { - log.trace('[getTransactionsList] setAll true') - setPendingIsAll(true) - } - } + if (transactionsStore.historyCount >= pendingDbCount) { + log.trace('[getTransactionsList] onlyPending setAll true') + setPendingIsAll(true) + } setIsLoading(false) } catch (e: any) { handleError(e) } - } + } + - const toggleShowPendingOnly = async function () { - if (showPendingOnly) { + const toggleShowPendingOnly = function () { + if (showPendingOnly) { + transactionsStore.removeAllHistory() + addTransactionsToList() setShowPendingOnly(false) } else { - await getPendingTransactionsList() + transactionsStore.removeAllHistory() + addPendingTransactionsToList() // hydrate with onlyPending = true setShowPendingOnly(true) } } @@ -192,59 +195,24 @@ export const TranHistoryScreen: FC> } - const onDeleteExpired = function () { + const onDelete = function (status: TransactionStatus) { try { toggleDeleteModal() setIsLoading(true) - transactionsStore.deleteByStatus(TransactionStatus.EXPIRED) - - const count = Database.getTransactionsCount() // all - const expiredCount = Database.getTransactionsCount(TransactionStatus.EXPIRED) - - setDbCount(count) - setExpiredDbCount(expiredCount) + transactionsStore.deleteByStatus(status) + const countByStatus = Database.getTransactionsCount() + + setPendingDbCount(countByStatus[TransactionStatus.PENDING] || 0) + setExpiredDbCount(countByStatus[TransactionStatus.EXPIRED] || 0) + setErroredDbCount(countByStatus[TransactionStatus.ERROR] || 0) + setRevertedDbCount(countByStatus[TransactionStatus.REVERTED] || 0) + setTotalDbCount(countByStatus.total) setIsLoading(false) } catch (e: any) { handleError(e) } } - - const onDeleteErrored = function () { - try { - toggleDeleteModal() - setIsLoading(true) - transactionsStore.deleteByStatus(TransactionStatus.ERROR) - - const count = Database.getTransactionsCount() // all - const erroredCount = Database.getTransactionsCount(TransactionStatus.ERROR) - - setDbCount(count) - setErroredDbCount(erroredCount) - setIsLoading(false) - } catch (e: any) { - handleError(e) - } - } - - - const onDeleteReverted = function () { - try { - toggleDeleteModal() - setIsLoading(true) - transactionsStore.deleteByStatus(TransactionStatus.REVERTED) - - const count = Database.getTransactionsCount() // all - const revertedCount = Database.getTransactionsCount(TransactionStatus.REVERTED) - - setDbCount(count) - setRevertedDbCount(revertedCount) - setIsLoading(false) - } catch (e: any) { - handleError(e) - } - } - const handleError = function (e: AppError): void { setIsLoading(false) setError(e) @@ -255,12 +223,12 @@ export const TranHistoryScreen: FC> const activeIconColor = useThemeColor('button') const headerTitle = useThemeColor('headerTitle') - const sections = showPendingOnly ? Object.keys(transactionsStore.groupedPendingByTimeAgo).map((timeAgo) => ({ + const sections = showPendingOnly ? Object.keys(transactionsStore.historyPendingByTimeAgo).map((timeAgo) => ({ title: timeAgo, - data: transactionsStore.groupedPendingByTimeAgo[timeAgo], - })) : Object.keys(transactionsStore.groupedByTimeAgo).map((timeAgo) => ({ + data: transactionsStore.historyPendingByTimeAgo[timeAgo], + })) : Object.keys(transactionsStore.historyByTimeAgo).map((timeAgo) => ({ title: timeAgo, - data: transactionsStore.groupedByTimeAgo[timeAgo], + data: transactionsStore.historyByTimeAgo[timeAgo], })) return ( @@ -277,12 +245,12 @@ export const TranHistoryScreen: FC> > /> > ) : (