mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-08-12 09:23:20 +00:00
446 lines
15 KiB
TypeScript
446 lines
15 KiB
TypeScript
import {observer} from 'mobx-react-lite'
|
|
import React, {FC, useEffect, useState} from 'react'
|
|
import {Alert, Platform, ScrollView, TextStyle, View, ViewStyle} from 'react-native'
|
|
import {colors, spacing, useThemeColor} from '../theme'
|
|
import {
|
|
APP_ENV,
|
|
JS_BUNDLE_VERSION,
|
|
COMMIT,
|
|
ANDROID_VERSION_NAME,
|
|
ANDROID_VERSION_CODE,
|
|
} from '@env'
|
|
import packageJson from '../../package.json'
|
|
import {
|
|
ListItem,
|
|
Screen,
|
|
Text,
|
|
Card,
|
|
Loading,
|
|
ErrorModal,
|
|
InfoModal,
|
|
BottomModal,
|
|
Button,
|
|
} from '../components'
|
|
import {useHeader} from '../utils/useHeader'
|
|
import {rootStoreInstance, useStores} from '../models'
|
|
import {translate} from '../i18n'
|
|
import AppError from '../utils/AppError'
|
|
import {Database, KeyChain} from '../services'
|
|
import {MMKVStorage} from '../services'
|
|
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'
|
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
|
|
type Props = StaticScreenProps<undefined>
|
|
|
|
export const DeveloperScreen = observer(function DeveloperScreen({ route }: Props) {
|
|
const navigation = useNavigation()
|
|
useHeader({
|
|
leftIcon: 'faArrowLeft',
|
|
onLeftPress: () => navigation.goBack(),
|
|
})
|
|
|
|
const {transactionsStore, userSettingsStore, proofsStore, walletProfileStore} = useStores()
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [rnVersion, setRnVersion] = useState<string>('')
|
|
const [walletStateSize, setWalletStateSize] = useState<number>(0)
|
|
const [dbVersion, setDbVersion] = useState<number>(0)
|
|
const [isLogLevelSelectorVisible, setIsLogLevelSelectorVisible] = useState<boolean>(false)
|
|
const [selectedLogLevel, setSelectedLogLevel] = useState<LogLevel>(userSettingsStore.logLevel)
|
|
const [error, setError] = useState<AppError | undefined>()
|
|
const [info, setInfo] = useState('')
|
|
|
|
useEffect(() => {
|
|
const init = async () => {
|
|
const rn = packageJson.dependencies['react-native']
|
|
const snapshot = getSnapshot(rootStoreInstance)
|
|
// log.info('[SNAPSHOT]', {snapshot})
|
|
const stateSize = Buffer.byteLength(JSON.stringify(snapshot), 'utf8')
|
|
|
|
const db = Database.getInstance()
|
|
const {version} = Database.getDatabaseVersion(db)
|
|
|
|
setDbVersion(version)
|
|
setWalletStateSize(stateSize)
|
|
setRnVersion(rn)
|
|
}
|
|
init()
|
|
}, [])
|
|
|
|
// Reset of transaction model state and reload from DB
|
|
const syncTransactionsFromDb = function () {
|
|
setIsLoading(true)
|
|
try {
|
|
const dbTransactions = Database.getTransactions(
|
|
maxTransactionsInHistory,
|
|
0,
|
|
)
|
|
|
|
if (dbTransactions && dbTransactions.length > 0) {
|
|
// remove all from the transactionsStore model
|
|
transactionsStore.removeAllTransactions()
|
|
|
|
// Add last 10 to history
|
|
transactionsStore.addToHistory(maxTransactionsInHistory, 0, false)
|
|
// Add recent by unit
|
|
transactionsStore.addRecentByUnit()
|
|
|
|
setIsLoading(false)
|
|
setInfo(translate('resetCompletedDetail', { transCount: dbTransactions.length }))
|
|
return true
|
|
}
|
|
|
|
setInfo(translate("resetAborted"))
|
|
setIsLoading(false)
|
|
return false
|
|
} catch (e: any) {
|
|
handleError(e)
|
|
}
|
|
}
|
|
|
|
|
|
const deletePending = async function () {
|
|
Alert.alert(
|
|
translate("common.confirmAlertTitle"),
|
|
"This action can not be undone. Use only in development or testing.",
|
|
[
|
|
{
|
|
text: translate('common.cancel'),
|
|
style: 'cancel',
|
|
onPress: () => { /* Action canceled */ },
|
|
},
|
|
{
|
|
text: translate('common.confirm'),
|
|
onPress: async () => {
|
|
try {
|
|
setIsLoading(true)
|
|
transactionsStore.deleteByStatus(TransactionStatus.PENDING)
|
|
|
|
const pending = proofsStore.allPendingProofs
|
|
const pendingCount = proofsStore.pendingProofsCount.valueOf()
|
|
|
|
if(pendingCount > 0) {
|
|
// remove pending proofs from state and move them to spent in the db
|
|
proofsStore.removeProofs(pending, true, false)
|
|
}
|
|
|
|
syncTransactionsFromDb()
|
|
|
|
setIsLoading(false)
|
|
setInfo(`Removed pending transactions from the database and ${pendingCount} proofs from the wallet state`)
|
|
|
|
} catch (e: any) {
|
|
handleError(e)
|
|
}
|
|
},
|
|
},
|
|
],
|
|
)
|
|
}
|
|
|
|
|
|
const movePendingToSpendable = async function () {
|
|
Alert.alert(
|
|
translate("common.confirmAlertTitle"),
|
|
"This action may cause transactions failure. Use only as a recovery path agreed with support.",
|
|
[
|
|
{
|
|
text: translate('common.cancel'),
|
|
style: 'cancel',
|
|
onPress: () => { /* Action canceled */ },
|
|
},
|
|
{
|
|
text: translate('common.confirm'),
|
|
onPress: async () => {
|
|
try {
|
|
setIsLoading(true)
|
|
transactionsStore.deleteByStatus(TransactionStatus.PENDING)
|
|
|
|
const pending = proofsStore.allPendingProofs
|
|
const pendingCount = proofsStore.pendingProofsCount.valueOf()
|
|
|
|
if(pendingCount > 0) {
|
|
// force move pending proofs to spendable wallet
|
|
proofsStore.removeProofs(pending, true, true)
|
|
proofsStore.addProofs(pending)
|
|
}
|
|
|
|
setIsLoading(false)
|
|
setInfo(`${pendingCount} pending proofs were moved to spendable balance.`)
|
|
|
|
} catch (e: any) {
|
|
handleError(e)
|
|
}
|
|
},
|
|
},
|
|
],
|
|
)
|
|
}
|
|
|
|
|
|
const toggleLogLevelSelector = () =>
|
|
setIsLogLevelSelectorVisible(previousState => !previousState)
|
|
|
|
|
|
const onLogLevelSelect = function (logLevel: LogLevel) {
|
|
try {
|
|
const result = userSettingsStore.setLogLevel(logLevel)
|
|
setSelectedLogLevel(result)
|
|
} catch (e: any) {
|
|
handleError(e)
|
|
}
|
|
}
|
|
|
|
|
|
const factoryReset = async function () {
|
|
Alert.alert(
|
|
translate("common.confirmAlertTitle"),
|
|
translate("factoryResetUserConfirmDesc"),
|
|
[
|
|
{
|
|
text: translate('common.cancel'),
|
|
style: 'cancel',
|
|
onPress: () => { /* Action canceled */ },
|
|
},
|
|
{
|
|
text: translate('common.confirm'),
|
|
onPress: async () => {
|
|
setIsLoading(true)
|
|
try {
|
|
// Delete database
|
|
Database.cleanAll()
|
|
// Delete wallet keys
|
|
await KeyChain.removeWalletKeys()
|
|
// Delete auth token
|
|
await KeyChain.removeAuthToken()
|
|
// Clean mobx storage
|
|
MMKVStorage.clearAll()
|
|
// recreate db schema
|
|
Database.getInstance()
|
|
setIsLoading(false)
|
|
setInfo(translate("factoryResetSuccess"))
|
|
await delay(1000)
|
|
RNExitApp.exitApp()
|
|
} catch (e: any) {
|
|
handleError(e)
|
|
}
|
|
},
|
|
},
|
|
],
|
|
)
|
|
}
|
|
|
|
const handleError = function (e: AppError): void {
|
|
setIsLoading(false)
|
|
setError(e)
|
|
}
|
|
|
|
const headerBg = useThemeColor('header')
|
|
const iconSelectedColor = useThemeColor('button')
|
|
const iconColor = useThemeColor('textDim')
|
|
const headerTitle = useThemeColor('headerTitle')
|
|
|
|
return (
|
|
<Screen style={$screen} preset='fixed'>
|
|
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
|
<Text
|
|
preset="heading"
|
|
tx="developerScreen.title"
|
|
style={{color: headerTitle}}
|
|
/>
|
|
</View>
|
|
<ScrollView style={$contentContainer}>
|
|
<Card
|
|
style={[$card]}
|
|
HeadingComponent={
|
|
<ListItem
|
|
tx="developerScreen.info"
|
|
subText={`Environment: ${APP_ENV}
|
|
JS Bundle version: ${JS_BUNDLE_VERSION}
|
|
Commit: ${COMMIT}
|
|
DB version: ${dbVersion}
|
|
State size: ${walletStateSize.toLocaleString()} bytes
|
|
React Native: ${rnVersion}
|
|
Sentry id: ${walletProfileStore.walletId}
|
|
`}
|
|
leftIcon='faInfoCircle'
|
|
leftIconColor={colors.palette.iconGreen300}
|
|
leftIconInverse={true}
|
|
RightComponent={<View style={$rightContainer} />}
|
|
style={$item}
|
|
/>
|
|
}
|
|
/>
|
|
<Card
|
|
style={[$card, {marginTop: spacing.medium}]}
|
|
HeadingComponent={
|
|
<>
|
|
<ListItem
|
|
tx="developerScreen.logLevel"
|
|
subText={userSettingsStore.logLevel.toUpperCase()}
|
|
leftIcon='faListUl'
|
|
leftIconColor={colors.palette.iconMagenta200}
|
|
leftIconInverse={true}
|
|
style={$item}
|
|
bottomSeparator={true}
|
|
onPress={toggleLogLevelSelector}
|
|
/>
|
|
<ListItem
|
|
tx="showOnboarding"
|
|
subTx="showOnboardingDesc"
|
|
leftIcon='faInfoCircle'
|
|
leftIconColor={colors.light.tint}
|
|
leftIconInverse={true}
|
|
style={$item}
|
|
bottomSeparator={true}
|
|
onPress={() => userSettingsStore.setIsOnboarded(false)}
|
|
/>
|
|
<ListItem
|
|
text="Resync transactions"
|
|
subText="Refresh recent transactions history from database."
|
|
leftIcon='faRotate'
|
|
leftIconColor={colors.palette.blue200}
|
|
leftIconInverse={true}
|
|
style={$item}
|
|
onPress={syncTransactionsFromDb}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Card
|
|
label='Danger zone'
|
|
labelStyle={{marginTop: spacing.medium}}
|
|
style={[$card, {marginBottom: spacing.large}]}
|
|
HeadingComponent={
|
|
<>
|
|
<ListItem
|
|
text="Force move pending"
|
|
subText="Move pending ecash back to spendable balance."
|
|
leftIcon='faArrowUp'
|
|
leftIconColor={colors.palette.accent200}
|
|
leftIconInverse={true}
|
|
RightComponent={<View style={$rightContainer} />}
|
|
style={$item}
|
|
onPress={movePendingToSpendable}
|
|
/>
|
|
<ListItem
|
|
text="Force delete pending"
|
|
subText="Removes ecash from pending state and deletes all pending transactions."
|
|
leftIcon='faClock'
|
|
leftIconColor={colors.palette.accent400}
|
|
leftIconInverse={true}
|
|
RightComponent={<View style={$rightContainer} />}
|
|
style={$item}
|
|
onPress={deletePending}
|
|
topSeparator
|
|
/>
|
|
<ListItem
|
|
tx="developerScreen.reset"
|
|
subTx="developerScreen.resetDescription"
|
|
leftIcon='faXmark'
|
|
leftIconColor={colors.palette.angry500}
|
|
leftIconInverse={true}
|
|
RightComponent={<View style={$rightContainer} />}
|
|
style={$item}
|
|
onPress={factoryReset}
|
|
topSeparator
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
</ScrollView>
|
|
<BottomModal
|
|
isVisible={isLogLevelSelectorVisible ? true : false}
|
|
style={{alignItems: 'stretch'}}
|
|
ContentComponent={
|
|
<>
|
|
<ListItem
|
|
text={LogLevel.ERROR.toUpperCase()}
|
|
subTx="loglevelErrorDesc"
|
|
leftIcon={selectedLogLevel === LogLevel.ERROR ? 'faCheckCircle' : 'faCircle'}
|
|
leftIconColor={selectedLogLevel === LogLevel.ERROR ? iconSelectedColor as string : iconColor as string}
|
|
onPress={() => onLogLevelSelect(LogLevel.ERROR)}
|
|
style={{paddingHorizontal: spacing.small}}
|
|
bottomSeparator={true}
|
|
/>
|
|
<ListItem
|
|
text={LogLevel.INFO.toUpperCase()}
|
|
subTx="loglevelInfoDesc"
|
|
leftIcon={selectedLogLevel === LogLevel.INFO ? 'faCheckCircle' : 'faCircle'}
|
|
leftIconColor={selectedLogLevel === LogLevel.INFO ? iconSelectedColor as string : iconColor as string}
|
|
onPress={() => onLogLevelSelect(LogLevel.INFO)}
|
|
style={{paddingHorizontal: spacing.small}}
|
|
bottomSeparator={true}
|
|
/>
|
|
<ListItem
|
|
text={LogLevel.DEBUG.toUpperCase()}
|
|
subTx="loglevelDebugDesc"
|
|
leftIcon={selectedLogLevel === LogLevel.DEBUG ? 'faCheckCircle' : 'faCircle'}
|
|
leftIconColor={selectedLogLevel === LogLevel.DEBUG ? iconSelectedColor as string : iconColor as string}
|
|
onPress={() => onLogLevelSelect(LogLevel.DEBUG)}
|
|
style={{paddingHorizontal: spacing.small}}
|
|
bottomSeparator={true}
|
|
/>
|
|
<View style={$buttonContainer}>
|
|
<Button
|
|
preset="secondary"
|
|
tx='common.close'
|
|
onPress={toggleLogLevelSelector}
|
|
/>
|
|
</View>
|
|
|
|
</>
|
|
}
|
|
onBackButtonPress={toggleLogLevelSelector}
|
|
onBackdropPress={toggleLogLevelSelector}
|
|
/>
|
|
{isLoading && <Loading />}
|
|
{error && <ErrorModal error={error} />}
|
|
{info && <InfoModal message={info} />}
|
|
</Screen>
|
|
)
|
|
})
|
|
|
|
const $screen: ViewStyle = {
|
|
flex: 1,
|
|
}
|
|
|
|
const $headerContainer: TextStyle = {
|
|
alignItems: 'center',
|
|
padding: spacing.medium,
|
|
height: spacing.screenHeight * 0.1,
|
|
}
|
|
|
|
const $contentContainer: TextStyle = {
|
|
// flex: 1,
|
|
padding: spacing.extraSmall,
|
|
// alignItems: 'center',
|
|
}
|
|
|
|
const $card: ViewStyle = {
|
|
marginBottom: 0,
|
|
}
|
|
|
|
const $item: ViewStyle = {
|
|
paddingHorizontal: spacing.small,
|
|
paddingLeft: 0,
|
|
}
|
|
|
|
const $rightContainer: ViewStyle = {
|
|
padding: spacing.extraSmall,
|
|
alignSelf: 'center',
|
|
marginLeft: spacing.small,
|
|
}
|
|
|
|
const $buttonContainer: ViewStyle = {
|
|
flexDirection: 'row',
|
|
alignSelf: 'center',
|
|
alignItems: 'center',
|
|
marginTop: spacing.large,
|
|
}
|