Files
minibits_wallet/src/screens/ReceiveScreen.tsx
T
minibits-cash 64fee85221 Implement seed recovery:
commit 9163bd479a
Author: minibits-cash <support@minibits.cash>
Date:   Fri Dec 8 22:04:52 2023 +0100

    Improve lnurl address handling

commit a8a7840f70
Author: minibits-cash <support@minibits.cash>
Date:   Fri Dec 8 22:00:11 2023 +0100

    Improve lnurl address handling

commit 7f32a15e57
Author: minibits-cash <support@minibits.cash>
Date:   Thu Dec 7 21:48:22 2023 +0100

    Beta.4 release for alpha testers

commit 1c44c15ccd
Author: minibits-cash <support@minibits.cash>
Date:   Thu Dec 7 19:37:54 2023 +0100

    Upgrade deps

commit 2c9ab1b71c
Author: minibits-cash <support@minibits.cash>
Date:   Thu Dec 7 11:13:43 2023 +0100

    Reset cached mints on seed creation

commit 5fd9e95d2a
Author: minibits-cash <support@minibits.cash>
Date:   Wed Dec 6 00:59:09 2023 +0100

    Patch cashu-ts error handling

commit 436c670e7e
Author: minibits-cash <support@minibits.cash>
Date:   Wed Dec 6 00:52:09 2023 +0100

    Clean unused deps

commit f6174bc607
Author: minibits-cash <support@minibits.cash>
Date:   Wed Dec 6 00:51:45 2023 +0100

    Remove debug

commit 868506a14c
Author: minibits-cash <support@minibits.cash>
Date:   Wed Dec 6 00:50:33 2023 +0100

    Improve receive error handling

commit e1bf087565
Author: minibits-cash <support@minibits.cash>
Date:   Tue Dec 5 23:06:36 2023 +0100

    Final small UX tuning

commit 875218d648
Author: minibits-cash <support@minibits.cash>
Date:   Tue Dec 5 16:14:34 2023 +0100

    Fix QR code error message

commit c37b3e5ffe
Author: minibits-cash <support@minibits.cash>
Date:   Tue Dec 5 12:53:23 2023 +0100

    New beta release v0.1.5 - Off-device seed backup

commit 95811817fa
Author: minibits-cash <support@minibits.cash>
Date:   Tue Dec 5 09:35:21 2023 +0100

    Align cashu-ts patch

commit f61ad99e64
Author: minibits-cash <support@minibits.cash>
Date:   Mon Dec 4 18:46:41 2023 +0100

    Recovery - final tweaks

commit 6fa384c8ef
Author: minibits-cash <support@minibits.cash>
Date:   Mon Dec 4 00:10:32 2023 +0100

    Fix counters in restore.

commit 50e9887405
Author: minibits-cash <support@minibits.cash>
Date:   Sat Dec 2 23:53:37 2023 +0100

    Seed recovery for testing

commit 00605c49af
Author: minibits-cash <support@minibits.cash>
Date:   Tue Nov 28 00:57:08 2023 +0100

    SeedRecovery before first tests

commit aaefbe0e63
Author: minibits-cash <support@minibits.cash>
Date:   Thu Nov 23 11:46:08 2023 +0100

    Recovery screen - wip

commit c019f63f48
Author: minibits-cash <support@minibits.cash>
Date:   Mon Nov 20 00:44:35 2023 +0100

    Remote backup screen with seed phrase

commit ada2f36070
Author: minibits-cash <support@minibits.cash>
Date:   Sun Nov 19 22:03:06 2023 +0100

    Generate seed
2023-12-10 21:48:04 +01:00

476 lines
15 KiB
TypeScript

import {observer} from 'mobx-react-lite'
import React, {FC, useState, useCallback} from 'react'
import {CommonActions, useFocusEffect} from '@react-navigation/native'
import {Alert, TextInput, TextStyle, View, ViewStyle} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import {spacing, useThemeColor, colors, typography} from '../theme'
import {WalletStackScreenProps} from '../navigation'
import {
Button,
Icon,
Card,
Screen,
Loading,
InfoModal,
ErrorModal,
ListItem,
BottomModal,
Text,
} from '../components'
import {Mint} from '../models/Mint'
import {Token} from '../models/Token'
import {Transaction, TransactionStatus} from '../models/Transaction'
import {useStores} from '../models'
import {useHeader} from '../utils/useHeader'
import {Wallet} from '../services'
import {log} from '../services/logService'
import AppError from '../utils/AppError'
import {CashuUtils} from '../services/cashu/cashuUtils'
import {ResultModalInfo} from './Wallet/ResultModalInfo'
import {MintListItem} from './Mints/MintListItem'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { resolveTxt } from 'dns'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { CurrencyCode, CurrencySign } from './Wallet/CurrencySign'
export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
function ReceiveScreen({route, navigation}) {
useHeader({
leftIcon: 'faArrowLeft',
onLeftPress: () => navigation.goBack(),
})
const isInternetReachable = useIsInternetReachable()
const {mintsStore} = useStores()
const [token, setToken] = useState<Token | undefined>()
const [encodedToken, setEncodedToken] = useState<string | undefined>()
const [amountToReceive, setAmountToReceive] = useState<number>(0)
const [receivedAmount, setReceivedAmount] = useState<number>(0)
const [transactionStatus, setTransactionStatus] = useState<
TransactionStatus | undefined
>()
const [memo, setMemo] = useState('')
const [info, setInfo] = useState('')
const [error, setError] = useState<AppError | undefined>()
const [isLoading, setIsLoading] = useState(false)
const [isResultModalVisible, setIsResultModalVisible] = useState(false)
const [resultModalInfo, setResultModalInfo] = useState<
{status: TransactionStatus; title?: string, message: string} | undefined
>()
useFocusEffect(
useCallback(() => {
if (!route.params?.encodedToken) {
log.trace('nothing scanned')
return
}
const encoded = route.params?.encodedToken
setEncodedToken(encoded)
onEncodedToken(encoded)
}, [route.params?.encodedToken]),
)
const resetState = function () {
setToken(undefined)
setEncodedToken(undefined)
setAmountToReceive(0)
setReceivedAmount(0)
setTransactionStatus(undefined)
setMemo('')
setInfo('')
setError(undefined)
setIsLoading(false)
setIsResultModalVisible(false)
setResultModalInfo(undefined)
}
const toggleResultModal = () =>
setIsResultModalVisible(previousState => !previousState)
const onEncodedToken = async function (encoded: string) {
try {
navigation.setParams({encodedToken: undefined})
const decoded: Token = CashuUtils.decodeToken(encoded)
const tokenAmounts = CashuUtils.getTokenAmounts(decoded)
log.trace('decoded token', decoded)
log.trace('tokenAmounts', tokenAmounts)
setToken(decoded)
setAmountToReceive(tokenAmounts.totalAmount)
if (decoded.memo && decoded.memo.length > 0) {
setMemo(decoded.memo as string)
}
} catch (e: any) {
handleError(e)
}
}
const receiveToken = async function () {
setIsLoading(true)
const {transaction, message, error, receivedAmount} =
await Wallet.receive(
token as Token,
amountToReceive,
memo,
encodedToken as string,
)
const {status} = transaction as Transaction
setTransactionStatus(status)
if (error) {
setResultModalInfo({
status,
title: error.message || 'Receive failed',
message: JSON.parse(error.params).message || error.message,
})
} else {
setResultModalInfo({
status,
message,
})
}
if (receivedAmount) {
setReceivedAmount(receivedAmount)
}
setIsLoading(false)
toggleResultModal()
}
const receiveOfflineToken = async function () {
setIsLoading(true)
const {transaction, message, error} =
await Wallet.receiveOfflinePrepare(
token as Token,
amountToReceive,
memo,
encodedToken as string,
)
const {status} = transaction as Transaction
setTransactionStatus(status)
if (error) {
setResultModalInfo({
status,
title: error.params?.message ? error.message : 'Receive failed',
message: error.params?.message || error.message,
})
} else {
setResultModalInfo({
status,
message,
})
}
setIsLoading(false)
toggleResultModal()
}
const gotoWallet = function() {
resetState()
navigation.popToTop()
}
const handleError = function (e: AppError): void {
resetState()
log.error(e.name, e.message)
setError(e)
}
const headerBg = useThemeColor('header')
const iconColor = useThemeColor('textDim')
const satsColor = colors.palette.primary200
return (
<Screen preset="auto" contentContainerStyle={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text
preset="subheading"
tx={receivedAmount > 0 ? "receiveScreen.received" : "receiveScreen.toReceive"}
style={{color: 'white'}}
/>
{receivedAmount > 0 ? (
<View style={$amountContainer}>
<CurrencySign
currencyCode={CurrencyCode.SATS}
/>
<TextInput
value={receivedAmount.toLocaleString()}
style={$amountToReceive}
maxLength={9}
editable={false}
/>
</View>
) : (
<View style={$amountContainer}>
<TextInput
value={amountToReceive.toLocaleString()}
style={$amountToReceive}
maxLength={9}
editable={false}
/>
<CurrencySign
currencyCode={CurrencyCode.SATS}
/>
</View>
)}
</View>
<View style={$contentContainer}>
{token && amountToReceive > 0 && (
<>
{memo && (
<Card
style={[$card, {minHeight: 0}]}
ContentComponent={
<ListItem
text={memo}
LeftComponent={
<Icon
containerStyle={$iconContainer}
icon="faPencil"
size={spacing.medium}
color={iconColor}
/>
}
style={$item}
/>
}
/>
)}
<Card
style={$card}
heading={
transactionStatus === TransactionStatus.COMPLETED
? 'Received from'
: 'Receive from'
}
headingStyle={{textAlign: 'center', padding: spacing.small}}
ContentComponent={
<>
{CashuUtils.getMintsFromToken(token).map((mintUrl, index) => {
const mint = mintsStore.findByUrl(mintUrl)
if (!mint) {
return (
<ListItem
key={mintUrl}
text={new URL(mintUrl).hostname}
topSeparator={true}
RightComponent={<Text text='New' style={{alignSelf: 'center', color: colors.palette.accent300}}/>}
/>
)
} else {
return (
<MintListItem
key={mintUrl}
mint={mint as Mint}
separator={'top'}
// mintBalance={mintBalance}
isSelectable={false}
/>
)
}
})}
</>
}
/>
{transactionStatus === TransactionStatus.COMPLETED ? (
<View style={$bottomContainer}>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
onPress={gotoWallet}
/>
</View>
</View>
) : (
<View style={$bottomContainer}>
<View style={$buttonContainer}>
{isInternetReachable ? (
<Button
tx={'receiveScreen.receive'}
onPress={receiveToken}
style={{marginRight: spacing.medium}}
LeftAccessory={() => (
<Icon
icon="faArrowDown"
color="white"
size={spacing.medium}
/>
)}
/>
) : (
<Button
tx={'receiveScreen.receiveOffline'}
onPress={receiveOfflineToken}
style={{marginRight: spacing.medium}}
LeftAccessory={() => (
<Icon
icon="faArrowDown"
color="white"
size={spacing.medium}
/>
)}
/>
)}
<Button
preset="secondary"
tx={'common.cancel'}
onPress={gotoWallet}
/>
</View>
</View>
)}
</>
)}
{isLoading && <Loading />}
</View>
<BottomModal
isVisible={isResultModalVisible ? true : false}
ContentComponent={
<>
{resultModalInfo?.status === TransactionStatus.COMPLETED && (
<>
<ResultModalInfo
icon={'faCheckCircle'}
iconColor={colors.palette.success200}
title="Success!"
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
onPress={gotoWallet}
/>
</View>
</>
)}
{resultModalInfo?.status === TransactionStatus.PREPARED_OFFLINE && (
<>
<ResultModalInfo
icon={'faTriangleExclamation'}
iconColor={colors.palette.accent400}
title="Attention!"
message={resultModalInfo?.message}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
onPress={gotoWallet}
/>
</View>
</>
)}
{(resultModalInfo?.status === TransactionStatus.ERROR ||
resultModalInfo?.status === TransactionStatus.BLOCKED) && (
<>
<ResultModalInfo
icon="faTriangleExclamation"
iconColor={colors.palette.angry500}
title={resultModalInfo?.title as string || 'Receive failed'}
message={resultModalInfo?.message as string}
/>
<View style={$buttonContainer}>
<Button
preset="secondary"
tx={'common.close'}
onPress={toggleResultModal}
/>
</View>
</>
)}
</>
}
onBackButtonPress={toggleResultModal}
onBackdropPress={toggleResultModal}
/>
{error && <ErrorModal error={error} />}
{info && <InfoModal message={info} />}
</Screen>
)
},
)
const $screen: ViewStyle = {
flex: 1,
}
const $headerContainer: TextStyle = {
alignItems: 'center',
padding: spacing.extraSmall,
paddingTop: 0,
height: spacing.screenHeight * 0.18,
}
const $amountContainer: ViewStyle = {
}
const $amountToReceive: TextStyle = {
borderRadius: spacing.small,
margin: 0,
padding: 0,
fontSize: 52,
fontWeight: '400',
textAlign: 'center',
color: 'white',
}
const $contentContainer: TextStyle = {
flex: 1,
padding: spacing.extraSmall,
// alignItems: 'center',
}
const $card: ViewStyle = {
// marginTop: - spacing.extraLarge * 2,
marginBottom: spacing.small,
paddingTop: 0,
}
const $item: ViewStyle = {
paddingHorizontal: spacing.small,
paddingLeft: 0,
}
const $iconContainer: ViewStyle = {
padding: spacing.extraSmall,
alignSelf: 'center',
marginRight: spacing.medium,
}
const $buttonContainer: ViewStyle = {
flexDirection: 'row',
alignSelf: 'center',
}
const $bottomContainer: ViewStyle = {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
flex: 1,
justifyContent: 'flex-end',
marginBottom: spacing.medium,
alignSelf: 'stretch',
// opacity: 0,
}