mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
New POS mode implementation
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "minibits_wallet",
|
||||
"version": "0.3.4-beta.19",
|
||||
"version": "0.3.4-beta.20",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android:clean": "cd android && ./gradlew clean",
|
||||
|
||||
@@ -8,6 +8,7 @@ import Animated, {
|
||||
} from 'react-native-reanimated'
|
||||
import { spacing, typography, useThemeColor } from '../theme'
|
||||
import { translate, TxKeyPath } from '../i18n'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
|
||||
export interface AnimatedHeaderProps {
|
||||
/**
|
||||
@@ -107,6 +108,7 @@ const $headerContainer: ViewStyle = {
|
||||
}
|
||||
|
||||
const $headerTitle: TextStyle = {
|
||||
fontSize: spacing.extraLarge,
|
||||
fontSize: verticalScale(32),
|
||||
lineHeight: verticalScale(44),
|
||||
fontFamily: typography.primary?.medium,
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import { faXmark } from '@fortawesome/free-solid-svg-icons/faXmark'
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons/faInfoCircle'
|
||||
import { faBug } from '@fortawesome/free-solid-svg-icons/faBug'
|
||||
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons/faCheckCircle'
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck'
|
||||
import { faArrowTurnUp } from '@fortawesome/free-solid-svg-icons/faArrowTurnUp'
|
||||
import { faArrowTurnDown } from '@fortawesome/free-solid-svg-icons/faArrowTurnDown'
|
||||
import { faPencil } from '@fortawesome/free-solid-svg-icons/faPencil'
|
||||
@@ -108,7 +109,7 @@ export type IconTypes = keyof typeof iconRegistry
|
||||
|
||||
// TODO remove need for manual iconregistry?
|
||||
// would be best to just import all of them, i guess, or figure out something smart
|
||||
export const iconRegistry = { faWifi, faNfcSymbol, faAddressCard, faAddressBook, faWallet, faQrcode, faClipboard, faSliders, faCoins, faEllipsisVertical, faEllipsis, faArrowUp, faArrowDown, faArrowLeft, faXmark, faInfoCircle, faBug, faCheckCircle, faArrowTurnUp, faArrowTurnDown, faPencil, faTags, faShareFromSquare, faRotate, faCode, faBan, faCircle, faPaperPlane, faBolt, faArrowUpFromBracket, faArrowRightToBracket, faPlus, faShieldHalved, faCloudArrowUp, faPaintbrush, faCopy, faBurst, faUserShield, faLock, faLockOpen, faTriangleExclamation, faDownload, faUpload, faRecycle, faListUl, faExpand, faFingerprint, faWandMagicSparkles, faCircleUser, faComment, faKey, faCircleNodes, faBullseye, faEyeSlash, faUpRightFromSquare, faShareNodes, faPaste, faKeyboard, faMoneyBill1, faGears, faTag, faBank, faChevronDown, faChevronUp, faCircleExclamation, faCircleQuestion, faEnvelope, faTwitter, faTelegramPlane, faDiscord, faGithub, faReddit, faCircleArrowUp, faCircleArrowDown, faGlobe, faCubes, faClock, faArrowRotateLeft, faHeartPulse, faSeedling, faChevronLeft, faArrowRightArrowLeft }
|
||||
export const iconRegistry = { faWifi, faNfcSymbol, faAddressCard, faAddressBook, faWallet, faQrcode, faClipboard, faSliders, faCoins, faEllipsisVertical, faEllipsis, faArrowUp, faArrowDown, faArrowLeft, faXmark, faInfoCircle, faBug, faCheckCircle, faCheck, faArrowTurnUp, faArrowTurnDown, faPencil, faTags, faShareFromSquare, faRotate, faCode, faBan, faCircle, faPaperPlane, faBolt, faArrowUpFromBracket, faArrowRightToBracket, faPlus, faShieldHalved, faCloudArrowUp, faPaintbrush, faCopy, faBurst, faUserShield, faLock, faLockOpen, faTriangleExclamation, faDownload, faUpload, faRecycle, faListUl, faExpand, faFingerprint, faWandMagicSparkles, faCircleUser, faComment, faKey, faCircleNodes, faBullseye, faEyeSlash, faUpRightFromSquare, faShareNodes, faPaste, faKeyboard, faMoneyBill1, faGears, faTag, faBank, faChevronDown, faChevronUp, faCircleExclamation, faCircleQuestion, faEnvelope, faTwitter, faTelegramPlane, faDiscord, faGithub, faReddit, faCircleArrowUp, faCircleArrowDown, faGlobe, faCubes, faClock, faArrowRotateLeft, faHeartPulse, faSeedling, faChevronLeft, faArrowRightArrowLeft }
|
||||
|
||||
|
||||
interface IconProps extends TouchableOpacityProps {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react'
|
||||
import { View, ViewStyle, TextStyle, Pressable } from 'react-native'
|
||||
import { verticalScale, moderateScale } from '@gocodingnow/rn-size-matters'
|
||||
import { spacing, useThemeColor, typography } from '../theme'
|
||||
import { Text } from './Text'
|
||||
import { Icon } from './Icon'
|
||||
|
||||
export interface NumericKeypadProps {
|
||||
onKeyPress: (key: string) => void
|
||||
onClear?: () => void
|
||||
onLongPressBackspace?: () => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const KEYS = [
|
||||
['1', '2', '3'],
|
||||
['4', '5', '6'],
|
||||
['7', '8', '9'],
|
||||
['clear', '0', 'backspace'],
|
||||
]
|
||||
|
||||
export function NumericKeypad({ onKeyPress, onClear, onLongPressBackspace, disabled = false }: NumericKeypadProps) {
|
||||
const keyBg = useThemeColor('card')
|
||||
const keyBgPressed = useThemeColor('buttonSecondary')
|
||||
const textColor = useThemeColor('text')
|
||||
const textDimColor = useThemeColor('textDim')
|
||||
|
||||
const renderKey = (key: string) => {
|
||||
const isBackspace = key === 'backspace'
|
||||
const isClear = key === 'clear'
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={key}
|
||||
onPress={() => {
|
||||
if (disabled) return
|
||||
if (isClear && onClear) {
|
||||
onClear()
|
||||
} else {
|
||||
onKeyPress(key)
|
||||
}
|
||||
}}
|
||||
onLongPress={isBackspace && onLongPressBackspace ? onLongPressBackspace : undefined}
|
||||
delayLongPress={500}
|
||||
disabled={disabled}
|
||||
style={({ pressed }) => [
|
||||
$key,
|
||||
{ backgroundColor: pressed ? keyBgPressed : keyBg },
|
||||
disabled && $keyDisabled,
|
||||
]}
|
||||
>
|
||||
{isBackspace ? (
|
||||
<Icon
|
||||
icon="faChevronLeft"
|
||||
size={moderateScale(24)}
|
||||
color={disabled ? textDimColor : textColor}
|
||||
/>
|
||||
) : isClear ? (
|
||||
<Icon
|
||||
icon="faXmark"
|
||||
size={moderateScale(24)}
|
||||
color={disabled ? textDimColor : textColor}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={[
|
||||
$keyText,
|
||||
{ color: disabled ? textDimColor : textColor },
|
||||
]}
|
||||
preset='heading'
|
||||
>
|
||||
{key}
|
||||
</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={$container}>
|
||||
{KEYS.map((row, rowIndex) => (
|
||||
<View key={rowIndex} style={$row}>
|
||||
{row.map(renderKey)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const $container: ViewStyle = {
|
||||
width: '100%',
|
||||
paddingHorizontal: spacing.medium,
|
||||
}
|
||||
|
||||
const $row: ViewStyle = {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginVertical: spacing.small,
|
||||
}
|
||||
|
||||
const $key: ViewStyle = {
|
||||
width: moderateScale(80),
|
||||
height: verticalScale(56),
|
||||
borderRadius: spacing.small,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: spacing.small,
|
||||
}
|
||||
|
||||
const $keyDisabled: ViewStyle = {
|
||||
opacity: 0.5,
|
||||
}
|
||||
|
||||
const $keyText: TextStyle = {
|
||||
//fontFamily: typography.primary.medium,
|
||||
//fontSize: moderateScale(28),
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export const PosIcon = `<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="256px" height="256px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="6.5" y="4.5" width="11" height="15" rx="1.5" />
|
||||
<rect x="8.5" y="6.5" width="7" height="5" stroke-linejoin="round"/>
|
||||
<path d="M8.5 17.5H9.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.5 17.5H15.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5 17.5H12.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 15.5H9.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.5 15.5H15.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5 15.5H12.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 13.5H9.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.5 13.5H15.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5 13.5H12.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>`
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react'
|
||||
import { TextStyle, View, ViewStyle } from 'react-native'
|
||||
import Animated, {
|
||||
SharedValue,
|
||||
useAnimatedStyle,
|
||||
interpolate,
|
||||
Extrapolation,
|
||||
} from 'react-native-reanimated'
|
||||
import { spacing, typography, useThemeColor } from '../theme'
|
||||
import { translate, TxKeyPath } from '../i18n'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { Text } from './Text'
|
||||
|
||||
export interface StaticHeaderProps {
|
||||
/**
|
||||
* Title text to display.
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Title text which is looked up via i18n.
|
||||
*/
|
||||
titleTx?: TxKeyPath
|
||||
|
||||
height?: number
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function StaticHeader(props: StaticHeaderProps) {
|
||||
const {
|
||||
title,
|
||||
titleTx,
|
||||
height = spacing.screenHeight * 0.15,
|
||||
} = props
|
||||
|
||||
const headerBg = useThemeColor('header')
|
||||
const headerTitleColor = useThemeColor('headerTitle')
|
||||
|
||||
|
||||
const titleContent = titleTx ? translate(titleTx) : title
|
||||
|
||||
return (
|
||||
<View style={[$headerContainer, { height, backgroundColor: headerBg }]}>
|
||||
<Text style={[$headerTitle, { color: headerTitleColor }]}>
|
||||
{titleContent}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const $headerContainer: ViewStyle = {
|
||||
alignItems: 'center',
|
||||
paddingBottom: spacing.medium,
|
||||
}
|
||||
|
||||
const $headerTitle: TextStyle = {
|
||||
fontSize: verticalScale(32),
|
||||
lineHeight: verticalScale(44),
|
||||
fontFamily: typography.primary?.medium,
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export function Text(props: TextProps) {
|
||||
}
|
||||
|
||||
export const $sizeStyles = {
|
||||
xxl: { fontSize: verticalScale(36), lineHeight: verticalScale(44) } as TextStyle,
|
||||
xxl: { fontSize: verticalScale(32), lineHeight: verticalScale(44) } as TextStyle,
|
||||
xl: { fontSize: verticalScale(24), lineHeight: verticalScale(34) } as TextStyle,
|
||||
lg: { fontSize: verticalScale(20), lineHeight: verticalScale(32) } as TextStyle,
|
||||
md: { fontSize: verticalScale(18), lineHeight: verticalScale(26) } as TextStyle,
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from "./Button"
|
||||
export * from "./Card"
|
||||
export * from "./Header"
|
||||
export * from "./AnimatedHeader"
|
||||
export * from "./StaticHeader"
|
||||
export * from "./Icon"
|
||||
export * from "./ListItem"
|
||||
export * from "./Screen"
|
||||
@@ -25,4 +26,5 @@ export * from "./MintIcon"
|
||||
export * from "./HeaderBg"
|
||||
export * from "./NwcIcon"
|
||||
export * from "./ContactlessIcon"
|
||||
|
||||
export * from "./PosIcon"
|
||||
export * from "./NumericKeypad"
|
||||
|
||||
@@ -310,6 +310,9 @@
|
||||
"payCommon_requestPayment": "Request payment",
|
||||
"payCommon_send": "Send",
|
||||
"payCommon_withdraw": "Withdraw",
|
||||
"pos_exchangeRateUnavailable": "Exchange rate unavailable",
|
||||
"pos_newTransaction": "New transaction",
|
||||
"pos_waitingForPayment": "Waiting for payment...",
|
||||
"payerMemo": "Memo for the payer",
|
||||
"payWithLightningDesc": "Enter or paste Lightning address, invoice or LNURL pay code you want to pay to.",
|
||||
"pictureRetrieveFail": "Could not retrieve pictures from the server, try again later.",
|
||||
|
||||
@@ -309,6 +309,9 @@
|
||||
"payCommon_requestPayment": "Solicitar pago",
|
||||
"payCommon_send": "Enviar",
|
||||
"payCommon_withdraw": "Retirar",
|
||||
"pos_exchangeRateUnavailable": "Tipo de cambio no disponible",
|
||||
"pos_newTransaction": "Nueva transacción",
|
||||
"pos_waitingForPayment": "Esperando el pago...",
|
||||
"payerMemo": "Memo para el pagador",
|
||||
"payWithLightningDesc": "Ingrese o pegue la dirección Lightning, la factura o el código de pago LNURL al que desea pagar.",
|
||||
"pictureRetrieveFail": "No se pudieron recuperar las imágenes del servidor, inténtelo nuevamente más tarde.",
|
||||
|
||||
@@ -310,6 +310,9 @@
|
||||
"payCommon_requestPayment": "Solicitar pagamento",
|
||||
"payCommon_send": "Enviar",
|
||||
"payCommon_withdraw": "Retirar",
|
||||
"pos_exchangeRateUnavailable": "Taxa de câmbio indisponível",
|
||||
"pos_newTransaction": "Nova transação",
|
||||
"pos_waitingForPayment": "Aguardando pagamento...",
|
||||
"payerMemo": "Memorando para o pagador",
|
||||
"payWithLightningDesc": "Insira ou cole endereço Lightning, invoice ou código de pagamento LNURL para pagar.",
|
||||
"pictureRetrieveFail": "Não foi possível recuperar imagens do servidor, tente mais tarde.",
|
||||
|
||||
@@ -310,6 +310,9 @@
|
||||
"payCommon_requestPayment": "Vyžiadaj platbu",
|
||||
"payCommon_send": "Pošli",
|
||||
"payCommon_withdraw": "Vyber",
|
||||
"pos_exchangeRateUnavailable": "Výmenný kurz nie je k dispozícii",
|
||||
"pos_newTransaction": "Nová transakcia",
|
||||
"pos_waitingForPayment": "Čakám na platbu...",
|
||||
"payerMemo": "Poznámka pre príjemcu",
|
||||
"payWithLightningDesc": "Zadaj Lightning adresu, invoice alebo LNURL kód ktorý chceš zaplatiť.",
|
||||
"pictureRetrieveFail": "Nejde načítať obrázky zo servera, skús neskôr.",
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Proof } from './Proof'
|
||||
import { InFlightRequest, Mint } from './Mint'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
import { Transaction } from './Transaction'
|
||||
// refresh refresh
|
||||
//
|
||||
|
||||
/*
|
||||
Not persisted, in-memory only model of the cashu-ts wallet instances and wallet keys persisted in the device secure store.
|
||||
|
||||
@@ -37,7 +37,8 @@ import {
|
||||
TokenReceiveScreen,
|
||||
NwcScreen,
|
||||
CashuPaymentRequestScreen,
|
||||
RecoveryOptionsScreen
|
||||
RecoveryOptionsScreen,
|
||||
POSScreen
|
||||
} from "../screens"
|
||||
import { colors, spacing, typography } from "../theme"
|
||||
|
||||
@@ -74,6 +75,7 @@ const WalletStack = createNativeStackNavigator({
|
||||
Transfer: TransferScreen,
|
||||
Topup: TopupScreen,
|
||||
CashuPaymentRequest: CashuPaymentRequestScreen,
|
||||
POS: POSScreen,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -352,6 +352,11 @@ export const PrivateContacts = observer(function (props: {
|
||||
}
|
||||
value={newContactName}
|
||||
autoCapitalize="none"
|
||||
autoComplete='off'
|
||||
importantForAutofill='no'
|
||||
autoCorrect={false}
|
||||
spellCheck={false}
|
||||
smartInsertDelete={false}
|
||||
keyboardType="email-address"
|
||||
maxLength={80}
|
||||
selectTextOnFocus={true}
|
||||
|
||||
@@ -34,7 +34,7 @@ import RNExitApp from 'react-native-exit-app'
|
||||
import { TransactionStatus } from '../models/Transaction'
|
||||
import { maxTransactionsInHistory } from '../models/TransactionsStore'
|
||||
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
||||
// refresh
|
||||
//
|
||||
|
||||
type Props = StaticScreenProps<undefined>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import { TouchableOpacity } from "react-native"
|
||||
import { Header, Text } from "../../components"
|
||||
import { spacing, useThemeColor } from "../../theme"
|
||||
import { Mint } from "../../models/Mint"
|
||||
@@ -15,44 +16,38 @@ export const MintHeader = observer(function(props: {
|
||||
mint?: Mint
|
||||
hideBalance?: boolean
|
||||
onBackPress?: () => void
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
leftIconColor?: string
|
||||
}
|
||||
) {
|
||||
const navigation = useNavigation()
|
||||
const {mint, unit, hideBalance, onBackPress} = props
|
||||
|
||||
const getActiveUnitColor = () => {
|
||||
/* switch (props.unit) {
|
||||
case 'usd':
|
||||
return useThemeColor('usd')
|
||||
case 'eur':
|
||||
return useThemeColor('eur')
|
||||
default:
|
||||
return useThemeColor('btc')
|
||||
} */
|
||||
|
||||
return useThemeColor('headerTitle')
|
||||
|
||||
}
|
||||
const {mint, unit, hideBalance, onBackPress, backgroundColor, textColor, leftIconColor} = props
|
||||
const [isBalanceHidden, setIsBalanceHidden] = useState(false)
|
||||
|
||||
const tabWidth = moderateScale(80)
|
||||
const headerTitle = useThemeColor('headerTitle')
|
||||
|
||||
const resolvedTextColor = textColor || 'white'
|
||||
|
||||
return (
|
||||
<Header
|
||||
backgroundColor={backgroundColor}
|
||||
leftIconColor={leftIconColor}
|
||||
TitleActionComponent={
|
||||
<>
|
||||
{mint && (<Text
|
||||
text={mint && mint.shortname}
|
||||
style={{color: headerTitle}}
|
||||
style={{color: textColor || headerTitle}}
|
||||
size='xxs'
|
||||
/>)}
|
||||
<CurrencySign
|
||||
mintUnit={unit}
|
||||
textStyle={{color: 'white'}}
|
||||
textStyle={{color: resolvedTextColor}}
|
||||
containerStyle={{
|
||||
borderBottomWidth: 2,
|
||||
paddingVertical: mint ? spacing.tiny : spacing.small,
|
||||
borderBottomColor: getActiveUnitColor(),
|
||||
borderBottomColor: resolvedTextColor,
|
||||
width: tabWidth
|
||||
}}
|
||||
/>
|
||||
@@ -63,14 +58,26 @@ export const MintHeader = observer(function(props: {
|
||||
onBackPress ? onBackPress() : navigation.goBack()
|
||||
}}
|
||||
RightActionComponent={mint && unit && !hideBalance ? (
|
||||
<CurrencyAmount
|
||||
mintUnit={unit}
|
||||
amount={mint?.balances?.balances[unit as MintUnit] || 0}
|
||||
amountStyle={{color: 'white'}}
|
||||
symbolStyle={{color: 'white'}}
|
||||
containerStyle={{marginRight: spacing.medium}}
|
||||
size='medium'
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsBalanceHidden(!isBalanceHidden)}
|
||||
style={{marginRight: spacing.medium}}
|
||||
>
|
||||
{isBalanceHidden ? (
|
||||
<Text
|
||||
text="***"
|
||||
style={{color: resolvedTextColor}}
|
||||
size='md'
|
||||
/>
|
||||
) : (
|
||||
<CurrencyAmount
|
||||
mintUnit={unit}
|
||||
amount={mint?.balances?.balances[unit as MintUnit] || 0}
|
||||
amountStyle={{color: resolvedTextColor}}
|
||||
symbolStyle={{color: resolvedTextColor}}
|
||||
size='medium'
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
) : undefined}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import React, { useEffect, useState, useRef } from 'react'
|
||||
import {
|
||||
View,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
} from 'react-native'
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withSequence,
|
||||
withTiming,
|
||||
withDelay,
|
||||
Easing,
|
||||
} from 'react-native-reanimated'
|
||||
import { spacing, useThemeColor, colors, typography } from '../theme'
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Screen,
|
||||
Loading,
|
||||
InfoModal,
|
||||
ErrorModal,
|
||||
BottomModal,
|
||||
Text,
|
||||
NumericKeypad,
|
||||
} from '../components'
|
||||
import { TransactionStatus, Transaction } from '../models/Transaction'
|
||||
import { useStores } from '../models'
|
||||
import {
|
||||
HANDLE_PENDING_TOPUP_TASK,
|
||||
TransactionTaskResult,
|
||||
WalletTask,
|
||||
} from '../services'
|
||||
import { log } from '../services/logService'
|
||||
import AppError from '../utils/AppError'
|
||||
import { MintBalance } from '../models/Mint'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
||||
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
||||
import { infoMessage } from '../utils/utils'
|
||||
import { round } from '../utils/number'
|
||||
import { verticalScale, moderateScale } from '@gocodingnow/rn-size-matters'
|
||||
import {
|
||||
CurrencyCode,
|
||||
MintUnit,
|
||||
convertToFromSats,
|
||||
Currencies,
|
||||
formatCurrency,
|
||||
} from '../services/wallet/currency'
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { QRCodeBlock } from './Wallet/QRCode'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
type Props = StaticScreenProps<{
|
||||
unit: MintUnit
|
||||
mintUrl?: string
|
||||
}>
|
||||
|
||||
export const POSScreen = observer(function POSScreen({ route }: Props) {
|
||||
const navigation = useNavigation()
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
|
||||
const {
|
||||
proofsStore,
|
||||
mintsStore,
|
||||
walletStore,
|
||||
userSettingsStore,
|
||||
} = useStores()
|
||||
|
||||
const unitRef = useRef<MintUnit>('sat')
|
||||
|
||||
// Amount state - stored as integer cents (e.g., 123 = $1.23)
|
||||
const [amountCents, setAmountCents] = useState<number>(0)
|
||||
const [satsAmount, setSatsAmount] = useState<number>(0)
|
||||
|
||||
// Max digits for amount entry (e.g., 7 = max $99,999.99)
|
||||
const MAX_CENTS_DIGITS = 7
|
||||
|
||||
// Mint selection
|
||||
const [mintBalanceToTopup, setMintBalanceToTopup] = useState<MintBalance | undefined>()
|
||||
|
||||
// Transaction state
|
||||
const [transactionStatus, setTransactionStatus] = useState<TransactionStatus | undefined>()
|
||||
const [transactionId, setTransactionId] = useState<number | undefined>()
|
||||
const [transaction, setTransaction] = useState<Transaction | undefined>()
|
||||
const [invoiceToPay, setInvoiceToPay] = useState<string>('')
|
||||
|
||||
// UI state
|
||||
const [info, setInfo] = useState('')
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// Success icon animation
|
||||
const iconScale = useSharedValue(0.8)
|
||||
|
||||
// Get fiat currency settings
|
||||
const fiatCode = userSettingsStore.exchangeCurrency || CurrencyCode.USD
|
||||
const fiatCurrency = Currencies[fiatCode]
|
||||
const fiatSymbol = fiatCurrency?.symbol || '$'
|
||||
const fiatPrecision = fiatCurrency?.precision ?? 100
|
||||
const fiatMantissa = fiatCurrency?.mantissa ?? 2
|
||||
|
||||
// Initialize unit from route params
|
||||
useEffect(() => {
|
||||
if (route.params?.unit) {
|
||||
unitRef.current = route.params.unit
|
||||
}
|
||||
}, [route.params?.unit])
|
||||
|
||||
// Convert cents to sats whenever amountCents or exchange rate changes
|
||||
useEffect(() => {
|
||||
if (!walletStore.exchangeRate) {
|
||||
setSatsAmount(0)
|
||||
return
|
||||
}
|
||||
|
||||
const sats = convertToFromSats(amountCents, fiatCode, walletStore.exchangeRate)
|
||||
setSatsAmount(round(sats, 0))
|
||||
}, [amountCents, walletStore.exchangeRate, fiatCode])
|
||||
|
||||
// Trigger success icon animation when transaction completes
|
||||
useEffect(() => {
|
||||
if (transactionStatus === TransactionStatus.COMPLETED) {
|
||||
iconScale.value = 0.8
|
||||
iconScale.value = withDelay(
|
||||
300,
|
||||
withSequence(
|
||||
withTiming(1.15, { duration: 400, easing: Easing.out(Easing.ease) }),
|
||||
withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
||||
)
|
||||
)
|
||||
}
|
||||
}, [transactionStatus])
|
||||
|
||||
const iconAnimatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: iconScale.value }],
|
||||
}))
|
||||
|
||||
// Payment detection via EventEmitter
|
||||
useEffect(() => {
|
||||
const handlePendingTopupTaskResult = (result: TransactionTaskResult) => {
|
||||
log.trace('[POSScreen] handlePendingTopupTaskResult triggered')
|
||||
|
||||
if (!transactionId) return
|
||||
if (result.transaction?.id !== transactionId) return
|
||||
if (result.transaction.status !== TransactionStatus.COMPLETED) return
|
||||
|
||||
log.trace('[POSScreen] Invoice paid, proofs received')
|
||||
|
||||
setTransactionStatus(TransactionStatus.COMPLETED)
|
||||
setTransaction(result.transaction)
|
||||
}
|
||||
|
||||
if (transactionId) {
|
||||
EventEmitter.on(
|
||||
`ev_${HANDLE_PENDING_TOPUP_TASK}_result`,
|
||||
handlePendingTopupTaskResult
|
||||
)
|
||||
}
|
||||
|
||||
return () => {
|
||||
EventEmitter.off(
|
||||
`ev_${HANDLE_PENDING_TOPUP_TASK}_result`,
|
||||
handlePendingTopupTaskResult
|
||||
)
|
||||
}
|
||||
}, [transactionId])
|
||||
|
||||
// Handle keypad input - cents-first entry (each digit shifts left)
|
||||
const handleKeyPress = (key: string) => {
|
||||
if (transactionStatus === TransactionStatus.PENDING) return
|
||||
|
||||
if (key === 'backspace') {
|
||||
// Shift right - remove last digit
|
||||
setAmountCents(prev => Math.floor(prev / 10))
|
||||
} else {
|
||||
// Only process digit keys
|
||||
const digit = parseInt(key, 10)
|
||||
if (isNaN(digit)) return
|
||||
|
||||
// Check max digits limit
|
||||
if (amountCents.toString().length >= MAX_CENTS_DIGITS) return
|
||||
|
||||
// Shift left and add new digit
|
||||
setAmountCents(prev => prev * 10 + digit)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear amount (also used for long-press backspace)
|
||||
const onClearAmount = () => {
|
||||
setAmountCents(0)
|
||||
setSatsAmount(0)
|
||||
}
|
||||
|
||||
// Confirm amount and create invoice immediately with default mint
|
||||
const onConfirmAmount = async () => {
|
||||
if (!isInternetReachable) {
|
||||
infoMessage(translate('commonOfflinePretty'))
|
||||
return
|
||||
}
|
||||
|
||||
if (satsAmount <= 0) {
|
||||
infoMessage(translate('payCommon_amountZeroOrNegative'))
|
||||
return
|
||||
}
|
||||
|
||||
const availableBalances = proofsStore.getMintBalancesWithUnit(unitRef.current)
|
||||
|
||||
if (availableBalances.length === 0) {
|
||||
infoMessage(
|
||||
translate('topup_missingMintAddFirst'),
|
||||
translate('topup_missingMintAddFirstDesc')
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Use first available mint (highest balance) and create invoice immediately
|
||||
const selectedMint = availableBalances[0]
|
||||
setMintBalanceToTopup(selectedMint)
|
||||
setIsLoading(true)
|
||||
|
||||
const result = await WalletTask.topupQueueAwaitable(
|
||||
selectedMint,
|
||||
satsAmount,
|
||||
unitRef.current,
|
||||
'' // empty memo for POS
|
||||
)
|
||||
|
||||
await handleTopupTaskResult(result)
|
||||
}
|
||||
|
||||
// Handle invoice creation result
|
||||
const handleTopupTaskResult = async (result: TransactionTaskResult) => {
|
||||
log.trace('[POSScreen] handleTopupTaskResult')
|
||||
|
||||
setIsLoading(false)
|
||||
|
||||
const { status, id } = result.transaction as Transaction
|
||||
setTransactionStatus(status)
|
||||
setTransactionId(id)
|
||||
|
||||
if (result.encodedInvoice) {
|
||||
setInvoiceToPay(result.encodedInvoice)
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Reset for new transaction
|
||||
const resetState = () => {
|
||||
setAmountCents(0)
|
||||
setSatsAmount(0)
|
||||
setTransactionStatus(undefined)
|
||||
setTransactionId(undefined)
|
||||
setTransaction(undefined)
|
||||
setInvoiceToPay('')
|
||||
//setMintBalanceToTopup(undefined)
|
||||
}
|
||||
|
||||
|
||||
const gotoWallet = () => {
|
||||
resetState()
|
||||
navigation.goBack()
|
||||
}
|
||||
|
||||
const handleError = (e: AppError) => {
|
||||
setError(e)
|
||||
}
|
||||
|
||||
// Get colors
|
||||
const headerBg = useThemeColor('background')
|
||||
const textColor = useThemeColor('text')
|
||||
const textDimColor = useThemeColor('textDim')
|
||||
const mainButtonColor = useThemeColor('button')
|
||||
|
||||
// Get selected mint info
|
||||
const selectedMint = mintBalanceToTopup
|
||||
? mintsStore.findByUrl(mintBalanceToTopup.mintUrl)
|
||||
: undefined
|
||||
|
||||
// Format display amount from cents to decimal string (e.g., 123 → "1.23")
|
||||
const formatCentsToDisplay = (cents: number): string => {
|
||||
const divisor = fiatPrecision // e.g., 100 for USD
|
||||
const whole = Math.floor(cents / divisor)
|
||||
const fraction = cents % divisor
|
||||
return `${whole}.${fraction.toString().padStart(fiatMantissa, '0')}`
|
||||
}
|
||||
|
||||
const displayFiatAmount = formatCentsToDisplay(amountCents)
|
||||
|
||||
return (
|
||||
<Screen preset="fixed" contentContainerStyle={$screen}>
|
||||
<MintHeader
|
||||
mint={selectedMint}
|
||||
unit={unitRef.current}
|
||||
onBackPress={gotoWallet}
|
||||
textColor={textColor as string}
|
||||
backgroundColor={headerBg as string}
|
||||
leftIconColor={textColor as string}
|
||||
/>
|
||||
|
||||
{/* Amount Display */}
|
||||
<View style={[$amountContainer]}>
|
||||
<Text
|
||||
style={[$fiatAmountText, { color: textColor }]}
|
||||
text={`${fiatSymbol} ${displayFiatAmount}`}
|
||||
/>
|
||||
{walletStore.exchangeRate && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[$satsAmountText, { color: textDimColor }]}
|
||||
text={`${Currencies[CurrencyCode.SAT]!.symbol} ${formatCurrency(satsAmount, CurrencyCode.SAT, true)}`}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Content Area */}
|
||||
<View style={$contentContainer}>
|
||||
{/* Show keypad when no invoice yet */}
|
||||
{!invoiceToPay && (
|
||||
<View style={$keypadContainer}>
|
||||
<NumericKeypad
|
||||
onKeyPress={handleKeyPress}
|
||||
onClear={onClearAmount}
|
||||
onLongPressBackspace={onClearAmount}
|
||||
disabled={transactionStatus === TransactionStatus.PENDING}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faCheck'
|
||||
color={'white'}
|
||||
size={spacing.large}
|
||||
/>
|
||||
)}
|
||||
onPress={onConfirmAmount}
|
||||
style={$buttonSend}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Show QR code when invoice is created */}
|
||||
{invoiceToPay && transactionStatus === TransactionStatus.PENDING && (
|
||||
<View style={$qrContainer}>
|
||||
<QRCodeBlock
|
||||
qrCodeData={invoiceToPay}
|
||||
title={`Lightning invoice`}
|
||||
type="Bolt11Invoice"
|
||||
size={spacing.screenWidth - spacing.large * 4}
|
||||
/>
|
||||
<Button
|
||||
text={translate('commonCancel')}
|
||||
preset="secondary"
|
||||
onPress={resetState}
|
||||
style={{ marginTop: spacing.medium, minHeight: verticalScale(40), paddingVertical: verticalScale(spacing.tiny) }}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Show success when completed */}
|
||||
{transactionStatus === TransactionStatus.COMPLETED && (
|
||||
<>
|
||||
<View style={$successContainer}>
|
||||
<Animated.View style={iconAnimatedStyle}>
|
||||
<Icon
|
||||
icon="faCheckCircle"
|
||||
size={80}
|
||||
color={mainButtonColor}
|
||||
/>
|
||||
</Animated.View>
|
||||
<Text
|
||||
style={[$successText, { color: textColor }]}
|
||||
text={translate('payCommon_completed')}
|
||||
/>
|
||||
</View>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={[$buttonContainer]}>
|
||||
<Button
|
||||
text={translate('pos_newTransaction')}
|
||||
preset="default"
|
||||
onPress={resetState}
|
||||
style={[$buttonSend, { marginTop: spacing.large }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
|
||||
{/* Error/Info modals */}
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
{isLoading && <Loading />}
|
||||
</Screen>
|
||||
)
|
||||
})
|
||||
|
||||
const $screen: ViewStyle = {
|
||||
flex: 1,
|
||||
}
|
||||
|
||||
const $amountContainer: ViewStyle = {
|
||||
alignItems: 'center',
|
||||
paddingVertical: spacing.extraLarge,
|
||||
paddingHorizontal: spacing.medium,
|
||||
}
|
||||
|
||||
const $fiatAmountText: TextStyle = {
|
||||
fontFamily: typography.primary?.medium,
|
||||
fontSize: moderateScale(48),
|
||||
lineHeight: moderateScale(56),
|
||||
}
|
||||
|
||||
const $satsAmountText: TextStyle = {
|
||||
fontFamily: typography.primary?.normal,
|
||||
fontSize: moderateScale(16),
|
||||
marginTop: spacing.tiny,
|
||||
}
|
||||
|
||||
const $contentContainer: ViewStyle = {
|
||||
flex: 1,
|
||||
//paddingTop: spacing.medium,
|
||||
}
|
||||
|
||||
const $keypadContainer: ViewStyle = {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
}
|
||||
|
||||
|
||||
const $qrContainer: ViewStyle = {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.medium,
|
||||
}
|
||||
|
||||
const $waitingText: TextStyle = {
|
||||
marginTop: spacing.small,
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const $successContainer: ViewStyle = {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: spacing.large,
|
||||
//paddingTop: spacing.large,
|
||||
}
|
||||
|
||||
const $successText: TextStyle = {
|
||||
fontFamily: typography.primary?.medium,
|
||||
fontSize: moderateScale(20),
|
||||
marginTop: spacing.medium,
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'center',
|
||||
marginVertical: spacing.medium,
|
||||
}
|
||||
|
||||
const $buttonSend: ViewStyle = {
|
||||
borderRadius: verticalScale(60 / 2),
|
||||
height: verticalScale(60),
|
||||
minWidth: verticalScale(140),
|
||||
}
|
||||
|
||||
const $bottomContainer: ViewStyle = {
|
||||
// position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
marginBottom: spacing.medium,
|
||||
alignSelf: 'stretch',
|
||||
// opacity: 0,
|
||||
}
|
||||
@@ -10,10 +10,10 @@ import {colors, spacing, useThemeColor} from '../theme'
|
||||
import {log} from '../services/logService'
|
||||
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { Button, Card, ErrorModal, Icon, ListItem, ScanIcon, Screen, Text } from '../components'
|
||||
import { Button, Card, ErrorModal, Icon, ListItem, PosIcon, ScanIcon, Screen, StaticHeader, Text } from '../components'
|
||||
import { infoMessage } from '../utils/utils'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { SvgXml } from 'react-native-svg'
|
||||
import Svg, { SvgXml } from 'react-native-svg'
|
||||
import { useStores } from '../models'
|
||||
import { Mint } from '../models/Mint'
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
@@ -139,6 +139,14 @@ export const TokenReceiveScreen = function TokenReceiveScreen({ route }: Props)
|
||||
})
|
||||
}
|
||||
|
||||
const gotoPOS = async function () {
|
||||
//@ts-ignore
|
||||
navigation.navigate('POS', {
|
||||
unit,
|
||||
mintUrl: mint?.mintUrl
|
||||
})
|
||||
}
|
||||
|
||||
const handleError = function(e: AppError): void {
|
||||
setError(e)
|
||||
}
|
||||
@@ -158,13 +166,9 @@ export const TokenReceiveScreen = function TokenReceiveScreen({ route }: Props)
|
||||
mint={mint}
|
||||
unit={unit!}
|
||||
/>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
preset="heading"
|
||||
tx="payCommon_receiveEcash"
|
||||
style={{color: headerTitle}}
|
||||
/>
|
||||
</View>
|
||||
<StaticHeader
|
||||
titleTx="payCommon_receiveEcash"
|
||||
/>
|
||||
<View style={$contentContainer}>
|
||||
<Card
|
||||
ContentComponent={
|
||||
@@ -233,27 +237,49 @@ export const TokenReceiveScreen = function TokenReceiveScreen({ route }: Props)
|
||||
style={$card}
|
||||
//style={{marginBottom: spacing.medium}}
|
||||
/>
|
||||
<Button
|
||||
tx="tokenReceiveScreen_topupWithLightning"
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faBolt'
|
||||
color={hintText}
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
textStyle={{fontSize: 14, color: hintText}}
|
||||
preset='secondary'
|
||||
onPress={gotoTopup}
|
||||
style={{
|
||||
minHeight: verticalScale(40),
|
||||
paddingVertical: verticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny,
|
||||
alignSelf: 'center',
|
||||
marginTop: spacing.medium
|
||||
}}
|
||||
/>
|
||||
|
||||
<View style={[$buttonContainer, {marginTop: spacing.medium}]}>
|
||||
<Button
|
||||
tx="tokenReceiveScreen_topupWithLightning"
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faBolt'
|
||||
color={hintText}
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
textStyle={{fontSize: 14, color: hintText}}
|
||||
preset='secondary'
|
||||
onPress={gotoTopup}
|
||||
style={{
|
||||
minHeight: verticalScale(40),
|
||||
paddingVertical: verticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny,
|
||||
alignSelf: 'center',
|
||||
marginTop: spacing.medium
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="POS mode"
|
||||
LeftAccessory={() => (
|
||||
<SvgXml
|
||||
width={spacing.large}
|
||||
height={spacing.large}
|
||||
xml={PosIcon}
|
||||
stroke={hintText}
|
||||
/>
|
||||
)}
|
||||
textStyle={{fontSize: 14, color: hintText}}
|
||||
preset='secondary'
|
||||
onPress={gotoPOS}
|
||||
style={{
|
||||
minHeight: verticalScale(40),
|
||||
paddingVertical: verticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny,
|
||||
alignSelf: 'center',
|
||||
marginTop: spacing.medium
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={$buttonContainer}>
|
||||
@@ -289,12 +315,6 @@ const $contentContainer: ViewStyle = {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
const $headerContainer: TextStyle = {
|
||||
alignItems: 'center',
|
||||
paddingBottom: spacing.medium,
|
||||
height: spacing.screenHeight * 0.15,
|
||||
}
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
//marginTop: spacing.large,
|
||||
flexDirection: 'row',
|
||||
|
||||
+17
-24
@@ -275,34 +275,27 @@ export const TopupScreen = observer(function TopupScreen({ route }: Props) {
|
||||
const handlePendingTopupTaskResult = (result: TransactionTaskResult) => {
|
||||
log.trace('[handlePendingTopupTaskResult] event handler triggered')
|
||||
|
||||
if (!transactionId) {
|
||||
return
|
||||
}
|
||||
if (!transactionId) return
|
||||
if (result.transaction?.id !== transactionId) return
|
||||
if (result.transaction.status !== TransactionStatus.COMPLETED) return
|
||||
|
||||
// Filter and handle events only for this topup transactionId
|
||||
if (result.transaction?.id === transactionId) {
|
||||
// Show result modal only on completed topup
|
||||
if (result.transaction.status !== TransactionStatus.COMPLETED) {
|
||||
return
|
||||
}
|
||||
log.trace(
|
||||
'[handlePendingTopupTaskResult]',
|
||||
'Invoice has been paid and new proofs received',
|
||||
)
|
||||
|
||||
log.trace(
|
||||
'[handlePendingTopupTaskResult]',
|
||||
'Invoice has been paid and new proofs received',
|
||||
)
|
||||
setResultModalInfo({
|
||||
status: result.transaction.status,
|
||||
message: result.message,
|
||||
})
|
||||
|
||||
setResultModalInfo({
|
||||
status: result.transaction.status,
|
||||
message: result.message,
|
||||
})
|
||||
setTransactionStatus(TransactionStatus.COMPLETED)
|
||||
setTransaction(result.transaction)
|
||||
setIsQRModalVisible(false)
|
||||
setIsNostrDMModalVisible(false)
|
||||
setIsWithdrawModalVisible(false)
|
||||
setIsResultModalVisible(true)
|
||||
|
||||
setTransactionStatus(TransactionStatus.COMPLETED)
|
||||
setTransaction(result.transaction)
|
||||
setIsQRModalVisible(false)
|
||||
setIsNostrDMModalVisible(false)
|
||||
setIsWithdrawModalVisible(false)
|
||||
setIsResultModalVisible(true)
|
||||
}
|
||||
}
|
||||
|
||||
if(transactionId) {
|
||||
|
||||
@@ -7,12 +7,11 @@ import { infoMessage } from "../../utils/utils"
|
||||
import { Button, Card, Icon, ListItem } from "../../components"
|
||||
import QRCode from "react-native-qrcode-svg"
|
||||
import Clipboard from "@react-native-clipboard/clipboard"
|
||||
import { moderateScale, verticalScale } from "@gocodingnow/rn-size-matters"
|
||||
import { verticalScale } from "@gocodingnow/rn-size-matters"
|
||||
import { colors, spacing } from "../../theme"
|
||||
import { translate, TxKeyPath } from "../../i18n"
|
||||
import { log } from "../../services"
|
||||
import { Token, getDecodedToken, getEncodedToken } from '@cashu/cashu-ts'
|
||||
import { isSupported } from '@react-native-firebase/messaging';
|
||||
import { NfcService } from '../../services/nfcService';
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export * from './LightningPayScreen'
|
||||
export * from './TokenReceiveScreen'
|
||||
export * from './CashuPaymentRequestScreen'
|
||||
export * from './NfcPayScreen'
|
||||
export * from './POSScreen'
|
||||
|
||||
// ContactsNavigator
|
||||
export * from './ContactsScreen'
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Platform } from "react-native"
|
||||
import AppError, { Err } from "../utils/AppError"
|
||||
|
||||
const { userSettingsStore } = rootStoreInstance
|
||||
//
|
||||
// refresh
|
||||
|
||||
if (!__DEV__) {
|
||||
Sentry.init({
|
||||
|
||||
Reference in New Issue
Block a user