mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-06 03:38:24 +00:00
commit9163bd479aAuthor: minibits-cash <support@minibits.cash> Date: Fri Dec 8 22:04:52 2023 +0100 Improve lnurl address handling commita8a7840f70Author: minibits-cash <support@minibits.cash> Date: Fri Dec 8 22:00:11 2023 +0100 Improve lnurl address handling commit7f32a15e57Author: minibits-cash <support@minibits.cash> Date: Thu Dec 7 21:48:22 2023 +0100 Beta.4 release for alpha testers commit1c44c15ccdAuthor: minibits-cash <support@minibits.cash> Date: Thu Dec 7 19:37:54 2023 +0100 Upgrade deps commit2c9ab1b71cAuthor: minibits-cash <support@minibits.cash> Date: Thu Dec 7 11:13:43 2023 +0100 Reset cached mints on seed creation commit5fd9e95d2aAuthor: minibits-cash <support@minibits.cash> Date: Wed Dec 6 00:59:09 2023 +0100 Patch cashu-ts error handling commit436c670e7eAuthor: minibits-cash <support@minibits.cash> Date: Wed Dec 6 00:52:09 2023 +0100 Clean unused deps commitf6174bc607Author: minibits-cash <support@minibits.cash> Date: Wed Dec 6 00:51:45 2023 +0100 Remove debug commit868506a14cAuthor: minibits-cash <support@minibits.cash> Date: Wed Dec 6 00:50:33 2023 +0100 Improve receive error handling commite1bf087565Author: minibits-cash <support@minibits.cash> Date: Tue Dec 5 23:06:36 2023 +0100 Final small UX tuning commit875218d648Author: minibits-cash <support@minibits.cash> Date: Tue Dec 5 16:14:34 2023 +0100 Fix QR code error message commitc37b3e5ffeAuthor: minibits-cash <support@minibits.cash> Date: Tue Dec 5 12:53:23 2023 +0100 New beta release v0.1.5 - Off-device seed backup commit95811817faAuthor: minibits-cash <support@minibits.cash> Date: Tue Dec 5 09:35:21 2023 +0100 Align cashu-ts patch commitf61ad99e64Author: minibits-cash <support@minibits.cash> Date: Mon Dec 4 18:46:41 2023 +0100 Recovery - final tweaks commit6fa384c8efAuthor: minibits-cash <support@minibits.cash> Date: Mon Dec 4 00:10:32 2023 +0100 Fix counters in restore. commit50e9887405Author: minibits-cash <support@minibits.cash> Date: Sat Dec 2 23:53:37 2023 +0100 Seed recovery for testing commit00605c49afAuthor: minibits-cash <support@minibits.cash> Date: Tue Nov 28 00:57:08 2023 +0100 SeedRecovery before first tests commitaaefbe0e63Author: minibits-cash <support@minibits.cash> Date: Thu Nov 23 11:46:08 2023 +0100 Recovery screen - wip commitc019f63f48Author: minibits-cash <support@minibits.cash> Date: Mon Nov 20 00:44:35 2023 +0100 Remote backup screen with seed phrase commitada2f36070Author: minibits-cash <support@minibits.cash> Date: Sun Nov 19 22:03:06 2023 +0100 Generate seed
476 lines
15 KiB
TypeScript
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,
|
|
}
|