diff --git a/__tests__/todos.ts b/__tests__/todos.ts index 6a2e899be..4364c5956 100644 --- a/__tests__/todos.ts +++ b/__tests__/todos.ts @@ -20,7 +20,7 @@ import { transferClosingChannelTodo, supportTodo, inviteTodo, - // fastpayTodo, + quickpayTodo, // discountTodo, } from '../src/store/shapes/todos'; import { createNewWallet } from '../src/utils/startup'; @@ -50,7 +50,7 @@ describe('Todos selector', () => { buyBitcoinTodo, supportTodo, inviteTodo, - // fastpayTodo, + quickpayTodo, slashtagsProfileTodo, // discountTodo, ]); @@ -104,6 +104,7 @@ describe('Todos selector', () => { invite: +new Date(), lightning: +new Date(), pin: +new Date(), + quickpay: +new Date(), slashtagsProfile: +new Date(), support: +new Date(), }; diff --git a/e2e/send.e2e.js b/e2e/send.e2e.js index 801e4ab5a..27ef10805 100644 --- a/e2e/send.e2e.js +++ b/e2e/send.e2e.js @@ -172,6 +172,8 @@ d('Send', () => { // - send to onchain address // - send to lightning invoice // - send to unified invoice + // - quickpay to lightning invoice + // - quickpay to unified invoice if (checkComplete('send-2')) { return; @@ -243,9 +245,9 @@ d('Send', () => { // receive lightning funds await element(by.id('Receive')).tap(); - let { label: invoice1 } = await element(by.id('QRCode')).getAttributes(); - invoice1 = invoice1.replaceAll(/bitcoin.*=/gi, '').toLowerCase(); - await lnd.sendPaymentSync({ paymentRequest: invoice1, amt: 50000 }); + let { label: receive } = await element(by.id('QRCode')).getAttributes(); + receive = receive.replaceAll(/bitcoin.*=/gi, '').toLowerCase(); + await lnd.sendPaymentSync({ paymentRequest: receive, amt: 50000 }); await waitFor(element(by.id('NewTxPrompt'))) .toBeVisible() .withTimeout(10000); @@ -278,8 +280,8 @@ d('Send', () => { .withTimeout(10000); // send to lightning invoice - const { paymentRequest: lnInvoice1 } = await lnd.addInvoice(); - await enterAddress(lnInvoice1); + const { paymentRequest: invoice1 } = await lnd.addInvoice(); + await enterAddress(invoice1); await expect(element(by.id('AssetButton-spending'))).toBeVisible(); await element(by.id('N1').withAncestor(by.id('SendAmountNumberPad'))).tap(); await element( @@ -298,11 +300,9 @@ d('Send', () => { .withTimeout(10000); // send to unified invoice w/ amount - const { paymentRequest: lnInvoice2 } = await lnd.addInvoice({ - value: 10000, - }); + const { paymentRequest: invoice2 } = await lnd.addInvoice({ value: 10000 }); const unified1 = encode(onchainAddress, { - lightning: lnInvoice2, + lightning: invoice2, amount: 10000, }); @@ -319,11 +319,11 @@ d('Send', () => { .withTimeout(10000); // send to unified invoice w/ amount exceeding balance(s) - const { paymentRequest: lnInvoice3 } = await lnd.addInvoice({ + const { paymentRequest: invoice3 } = await lnd.addInvoice({ value: 200000, }); const unified2 = encode(onchainAddress, { - lightning: lnInvoice3, + lightning: invoice3, amount: 200000, }); @@ -369,8 +369,8 @@ d('Send', () => { .withTimeout(10000); // send to unified invoice w/o amount (lightning) - const { paymentRequest: lnInvoice4 } = await lnd.addInvoice(); - const unified4 = encode(onchainAddress, { lightning: lnInvoice4 }); + const { paymentRequest: invoice4 } = await lnd.addInvoice(); + const unified4 = encode(onchainAddress, { lightning: invoice4 }); await enterAddress(unified4); // max amount (lightning) @@ -396,8 +396,8 @@ d('Send', () => { .withTimeout(10000); // send to unified invoice w/o amount (switch to onchain) - const { paymentRequest: lnInvoice5 } = await lnd.addInvoice(); - const unified5 = encode(onchainAddress, { lightning: lnInvoice5 }); + const { paymentRequest: invoice5 } = await lnd.addInvoice(); + const unified5 = encode(onchainAddress, { lightning: invoice5 }); await enterAddress(unified5); @@ -433,6 +433,52 @@ d('Send', () => { .toHaveText('78 506') .withTimeout(10000); + // send to lightning invoice w/ amount (quickpay) + const { paymentRequest: invoice6 } = await lnd.addInvoice({ value: 1000 }); + + // enable quickpay + await element(by.id('Settings')).tap(); + await element(by.id('GeneralSettings')).tap(); + await element(by.id('QuickpaySettings')).tap(); + await element(by.id('QuickpayIntro-button')).tap(); + await element(by.id('QuickpayToggle')).tap(); + await element(by.id('NavigationClose')).tap(); + + await enterAddress(invoice6); + await waitFor(element(by.id('SendSuccess'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('Close')).tap(); + await waitFor( + element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), + ) + .toHaveText('77 506') + .withTimeout(10000); + + // send to unified invoice w/ amount (quickpay) + const { paymentRequest: invoice7 } = await lnd.addInvoice({ value: 1000 }); + const unified7 = encode(onchainAddress, { + lightning: invoice7, + amount: 1000, + }); + + await enterAddress(unified7); + await waitFor(element(by.id('SendSuccess'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('Close')).tap(); + await waitFor( + element(by.id('MoneyText').withAncestor(by.id('TotalBalance'))), + ) + .toHaveText('76 506') + .withTimeout(10000); + + // send to lightning invoice w/ amount (skip quickpay for large amounts) + const { paymentRequest: invoice8 } = await lnd.addInvoice({ value: 10000 }); + await enterAddress(invoice8); + await expect(element(by.id('ReviewAmount'))).toBeVisible(); + await element(by.id('SendSheet')).swipe('down'); + markComplete('send-2'); }); }); diff --git a/src/assets/illustrations/coin-stack-4.png b/src/assets/illustrations/coin-stack-4.png new file mode 100644 index 000000000..d2d7af7df Binary files /dev/null and b/src/assets/illustrations/coin-stack-4.png differ diff --git a/src/assets/illustrations/fast-forward.png b/src/assets/illustrations/fast-forward.png new file mode 100644 index 000000000..0a40abc92 Binary files /dev/null and b/src/assets/illustrations/fast-forward.png differ diff --git a/src/components/HourglassSpinner.tsx b/src/components/HourglassSpinner.tsx index 9ecb13c0c..d211ed631 100644 --- a/src/components/HourglassSpinner.tsx +++ b/src/components/HourglassSpinner.tsx @@ -1,5 +1,12 @@ import React, { ReactElement } from 'react'; -import { View, StyleSheet, StyleProp, ViewStyle, Image } from 'react-native'; +import { + View, + StyleSheet, + StyleProp, + ViewStyle, + Image, + ImageSourcePropType, +} from 'react-native'; import { Easing, withRepeat, withTiming } from 'react-native-reanimated'; import { AnimatedView } from '../styles/components'; @@ -8,9 +15,11 @@ import { __E2E__ } from '../constants/env'; const imageSrc = require('../assets/illustrations/hourglass.png'); const HourglassSpinner = ({ + image = imageSrc, imageSize = 256, style, }: { + image?: ImageSourcePropType; imageSize?: number; style?: StyleProp; }): ReactElement => { @@ -47,7 +56,7 @@ const HourglassSpinner = ({ color="transparent" testID="HourglassSpinner"> - + ); diff --git a/src/components/LightningSyncing.tsx b/src/components/LightningSyncing.tsx index a0b50f772..823028237 100644 --- a/src/components/LightningSyncing.tsx +++ b/src/components/LightningSyncing.tsx @@ -1,13 +1,11 @@ import React, { ReactElement, useEffect, useState } from 'react'; import { Image, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; -import Animated, { +import { Easing, cancelAnimation, runOnJS, useSharedValue, - withDelay, withRepeat, - withSequence, withTiming, } from 'react-native-reanimated'; import { useTranslation } from 'react-i18next'; @@ -17,13 +15,12 @@ import { AnimatedView } from '../styles/components'; import SafeAreaInset from './SafeAreaInset'; import BottomSheetNavigationHeader from './BottomSheetNavigationHeader'; import GradientView from './GradientView'; +import SyncSpinner from './SyncSpinner'; import { useAppSelector } from '../hooks/redux'; import { isLDKReadySelector } from '../store/reselect/ui'; import { __E2E__ } from '../constants/env'; const imageSrc = require('../assets/illustrations/lightning.png'); -const imageSyncSmall = require('../assets/illustrations/ln-sync-small.png'); -const imageSyncLarge = require('../assets/illustrations/ln-sync-large.png'); const LightningSyncing = ({ title, @@ -69,62 +66,6 @@ const LightningSyncing = ({ return <>; } - const animationLarge = (): { initialValues: {}; animations: {} } => { - 'worklet'; - const initialValues = { transform: [{ rotate: '0deg' }] }; - const animations = { - transform: [ - { - rotate: withRepeat( - withSequence( - withTiming('-180deg', { - duration: 1500, - easing: Easing.inOut(Easing.ease), - }), - withDelay( - 100, - withTiming('-360deg', { - duration: 1500, - easing: Easing.inOut(Easing.ease), - }), - ), - ), - -1, - ), - }, - ], - }; - return { initialValues, animations }; - }; - - const animationSmall = (): { initialValues: {}; animations: {} } => { - 'worklet'; - const initialValues = { transform: [{ rotate: '0deg' }] }; - const animations = { - transform: [ - { - rotate: withRepeat( - withSequence( - withTiming('180deg', { - duration: 1500, - easing: Easing.inOut(Easing.ease), - }), - withDelay( - 100, - withTiming('360deg', { - duration: 1500, - easing: Easing.inOut(Easing.ease), - }), - ), - ), - -1, - ), - }, - ], - }; - return { initialValues, animations }; - }; - return ( {t('wait_text_top')} - - - - + @@ -182,28 +112,6 @@ const styles = StyleSheet.create({ flex: 1, resizeMode: 'contain', }, - animation: { - justifyContent: 'center', - alignItems: 'center', - alignSelf: 'center', - position: 'absolute', - width: 311, - aspectRatio: 1, - }, - circleSmall: { - flex: 1, - position: 'absolute', - resizeMode: 'contain', - width: 207, - aspectRatio: 1, - }, - circleLarge: { - flex: 1, - position: 'absolute', - resizeMode: 'contain', - width: 311, - aspectRatio: 1, - }, bottom: { textAlign: 'center', marginTop: 'auto', diff --git a/src/components/Slider.tsx b/src/components/Slider.tsx new file mode 100644 index 000000000..f22337126 --- /dev/null +++ b/src/components/Slider.tsx @@ -0,0 +1,175 @@ +import React, { ReactElement, useEffect, useMemo, useState } from 'react'; +import { View, StyleSheet } from 'react-native'; +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withSpring, + clamp, + interpolate, + runOnJS, +} from 'react-native-reanimated'; + +import { Text13UP } from '../styles/text'; +import { View as ThemedView } from '../styles/components'; +import useColors from '../hooks/colors'; + +const KNOB_SIZE = 32; + +const Slider = ({ + steps, + value, + onValueChange, +}: { + steps: number[]; + value: number; + onValueChange: (value: number) => void; +}): ReactElement => { + const colors = useColors(); + const [sliderWidth, setSliderWidth] = useState(0); + const panX = useSharedValue(0); + const prevPanX = useSharedValue(0); + + // Convert steps to evenly spaced positions on the slider + const stepPositions = useMemo(() => { + return steps.map((_, index) => { + const numSteps = steps.length - 1; + // Calculate position based on index, using full width + return (index / numSteps) * sliderWidth; + }); + }, [steps, sliderWidth]); + + // Set initial position when slider width changes or value changes + useEffect(() => { + if (sliderWidth === 0) { + return; + } + + // Find index of current value in steps array + const valueIndex = steps.indexOf(value); + panX.value = stepPositions[valueIndex]; + }, [sliderWidth, value, steps, stepPositions, panX]); + + const gesture = Gesture.Pan() + .onStart(() => { + prevPanX.value = panX.value; + }) + .onUpdate((event) => { + panX.value = clamp(prevPanX.value + event.translationX, 0, sliderWidth); + }) + .onEnd(() => { + const findClosestStep = (currentPosition: number): number => { + return stepPositions.reduce((prev, curr) => { + return Math.abs(curr - currentPosition) < + Math.abs(prev - currentPosition) + ? curr + : prev; + }); + }; + + const closestStep = findClosestStep(panX.value); + panX.value = withSpring(closestStep); + + if (onValueChange) { + const stepIndex = stepPositions.indexOf(closestStep); + runOnJS(onValueChange)(steps[stepIndex]); + } + }); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: panX.value - KNOB_SIZE / 2 }], + })); + + const trailStyle = useAnimatedStyle(() => ({ + width: interpolate(panX.value, [0, sliderWidth], [0, sliderWidth]), + backgroundColor: colors.green, + })); + + return ( + setSliderWidth(e.nativeEvent.layout.width)}> + + + + {stepPositions.map((pos, index) => ( + + + + ${steps[index]} + + + ))} + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginTop: 16, + flex: 1, + }, + sliderContainer: { + height: KNOB_SIZE, + flexDirection: 'row', + position: 'relative', + alignItems: 'center', + justifyContent: 'center', + }, + track: { + borderRadius: 3, + height: 8, + width: '100%', + position: 'absolute', + left: 0, + }, + stepContainer: { + position: 'absolute', + alignItems: 'center', + height: '100%', + justifyContent: 'center', + }, + stepMarker: { + width: 4, + height: 16, + borderRadius: 5, + }, + stepLabel: { + position: 'absolute', + top: '100%', + marginTop: 4, + width: 30, + textAlign: 'center', + }, + knob: { + width: KNOB_SIZE, + height: KNOB_SIZE, + borderRadius: KNOB_SIZE / 2, + position: 'absolute', + left: 0, + }, + knobOuter: { + borderRadius: KNOB_SIZE, + height: KNOB_SIZE, + width: KNOB_SIZE, + alignItems: 'center', + justifyContent: 'center', + }, + knobInner: { + borderRadius: 16, + height: 16, + width: 16, + }, +}); + +export default Slider; diff --git a/src/components/Suggestions.tsx b/src/components/Suggestions.tsx index 97e162c80..7d70caa1b 100644 --- a/src/components/Suggestions.tsx +++ b/src/components/Suggestions.tsx @@ -17,7 +17,10 @@ import { ITodo, TTodoType } from '../store/types/todos'; import { channelsNotificationsShown, hideTodo } from '../store/slices/todos'; import { showBottomSheet } from '../store/utils/ui'; import { pinSelector } from '../store/reselect/settings'; -import { transferIntroSeenSelector } from '../store/reselect/user'; +import { + quickpayIntroSeenSelector, + transferIntroSeenSelector, +} from '../store/reselect/user'; import { newChannelsNotificationsSelector, todosFullSelector, @@ -33,6 +36,7 @@ const Suggestions = (): ReactElement => { const { width } = useWindowDimensions(); const dispatch = useAppDispatch(); const pinTodoDone = useAppSelector(pinSelector); + const quickpayIntroSeen = useAppSelector(quickpayIntroSeenSelector); const suggestions = useAppSelector(todosFullSelector); const newChannels = useAppSelector(newChannelsNotificationsSelector); const transferIntroSeen = useAppSelector(transferIntroSeenSelector); @@ -106,6 +110,14 @@ const Suggestions = (): ReactElement => { onShare(); } + if (id === 'quickpay') { + if (quickpayIntroSeen) { + navigation.navigate('Settings', { screen: 'QuickpaySettings' }); + } else { + navigation.navigate('Settings', { screen: 'QuickpayIntro' }); + } + } + if (id === 'support') { navigation.navigate('Settings', { screen: 'SupportSettings' }); } @@ -117,7 +129,7 @@ const Suggestions = (): ReactElement => { }); } }, - [navigation, transferIntroSeen, pinTodoDone, onShare], + [navigation, transferIntroSeen, quickpayIntroSeen, pinTodoDone, onShare], ); const handleRenderItem = useCallback( diff --git a/src/components/SwitchRow.tsx b/src/components/SwitchRow.tsx index bc8d8d722..2cf1ac54c 100644 --- a/src/components/SwitchRow.tsx +++ b/src/components/SwitchRow.tsx @@ -14,12 +14,14 @@ const SwitchRow = ({ color, isEnabled, style, + testID, onPress, }: { children: ReactNode; color?: keyof IThemeColors; isEnabled: boolean; style?: StyleProp; + testID?: string; onPress: () => void; }): ReactElement => { return ( @@ -27,6 +29,7 @@ const SwitchRow = ({ {children} diff --git a/src/components/SyncSpinner.tsx b/src/components/SyncSpinner.tsx new file mode 100644 index 000000000..d9d4829fe --- /dev/null +++ b/src/components/SyncSpinner.tsx @@ -0,0 +1,103 @@ +import React, { ReactElement } from 'react'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import Animated, { + Easing, + withDelay, + withRepeat, + withSequence, + withTiming, +} from 'react-native-reanimated'; + +import { __E2E__ } from '../constants/env'; + +const imageSyncSmall = require('../assets/illustrations/ln-sync-small.png'); +const imageSyncLarge = require('../assets/illustrations/ln-sync-large.png'); + +const easing = Easing.inOut(Easing.ease); +const duration = 1500; + +type TKeyframe = { initialValues: {}; animations: {} }; + +const clockwiseAnimation = (): TKeyframe => { + 'worklet'; + const initialValues = { transform: [{ rotate: '0deg' }] }; + const animations = { + transform: [ + { + rotate: withRepeat( + withSequence( + withTiming('180deg', { duration, easing }), + withDelay(100, withTiming('360deg', { duration, easing })), + ), + -1, + ), + }, + ], + }; + + return { initialValues, animations }; +}; + +const counterClockwiseAnimation = (): TKeyframe => { + 'worklet'; + const initialValues = { transform: [{ rotate: '0deg' }] }; + const animations = { + transform: [ + { + rotate: withRepeat( + withSequence( + withTiming('-180deg', { duration, easing }), + withDelay(100, withTiming('-360deg', { duration, easing })), + ), + -1, + ), + }, + ], + }; + + return { initialValues, animations }; +}; + +const SyncSpinner = ({ + style, +}: { + style?: StyleProp; +}): ReactElement => { + return ( + + + + + ); +}; + +const styles = StyleSheet.create({ + root: { + justifyContent: 'center', + alignItems: 'center', + alignSelf: 'center', + position: 'absolute', + width: 311, + aspectRatio: 1, + }, + circle: { + flex: 1, + position: 'absolute', + resizeMode: 'contain', + width: 207, + aspectRatio: 1, + }, + circleLarge: { + width: 311, + }, +}); + +export default SyncSpinner; diff --git a/src/navigation/bottom-sheet/SendNavigation.tsx b/src/navigation/bottom-sheet/SendNavigation.tsx index 842ccf60f..c9c7d073e 100644 --- a/src/navigation/bottom-sheet/SendNavigation.tsx +++ b/src/navigation/bottom-sheet/SendNavigation.tsx @@ -17,6 +17,7 @@ import Tags from '../../screens/Wallets/Send/Tags'; import AutoRebalance from '../../screens/Wallets/Send/AutoRebalance'; import PinCheck from '../../screens/Wallets/Send/PinCheck'; import Pending from '../../screens/Wallets/Send/Pending'; +import Quickpay from '../../screens/Wallets/Send/Quickpay'; import Success from '../../screens/Wallets/Send/Success'; import Error from '../../screens/Wallets/Send/Error'; import Contacts from '../../screens/Wallets/Send/Contacts'; @@ -60,6 +61,7 @@ export type SendStackParamList = { Tags: undefined; AutoRebalance: undefined; Pending: { txId: string }; + Quickpay: { invoice: string; amount: number }; Success: { type: EActivityType; amount: number; txId: string }; Error: { errorMessage: string }; LNURLAmount: { pParams: LNURLPayParams; url: string }; @@ -108,9 +110,11 @@ const SendNavigation = (): ReactElement => { const lightningBalance = useLightningBalance(false); const selectedWallet = useAppSelector(selectedWalletSelector); const selectedNetwork = useAppSelector(selectedNetworkSelector); - const { isOpen, screen, pParams, amount, url } = useAppSelector((state) => { - return viewControllerSelector(state, 'sendNavigation'); - }); + const { isOpen, screen, pParams, invoice, amount, url } = useAppSelector( + (state) => { + return viewControllerSelector(state, 'sendNavigation'); + }, + ); const transaction = useAppSelector(transactionSelector); const initialRouteName = screen ?? 'Recipient'; @@ -152,6 +156,11 @@ const SendNavigation = (): ReactElement => { + { /> + + diff --git a/src/screens/Settings/General/index.tsx b/src/screens/Settings/General/index.tsx index 53ec19112..1a98a97cd 100644 --- a/src/screens/Settings/General/index.tsx +++ b/src/screens/Settings/General/index.tsx @@ -5,8 +5,9 @@ import { EItemType, IListData, ItemData } from '../../../components/List'; import SettingsView from './../SettingsView'; import { useAppSelector } from '../../../hooks/redux'; import type { SettingsScreenProps } from '../../../navigation/types'; -import { lastUsedTagsSelector } from '../../../store/reselect/metadata'; import { EUnit } from '../../../store/types/wallet'; +import { lastUsedTagsSelector } from '../../../store/reselect/metadata'; +import { quickpayIntroSeenSelector } from '../../../store/reselect/user'; import { unitSelector, selectedCurrencySelector, @@ -21,6 +22,7 @@ const GeneralSettings = ({ const selectedTransactionSpeed = useAppSelector(transactionSpeedSelector); const selectedCurrency = useAppSelector(selectedCurrencySelector); const selectedUnit = useAppSelector(unitSelector); + const quickpayIntroSeen = useAppSelector(quickpayIntroSeenSelector); const listData: IListData[] = useMemo(() => { const transactionSpeeds = { @@ -61,6 +63,18 @@ const GeneralSettings = ({ testID: 'WidgetsSettings', onPress: (): void => navigation.navigate('WidgetSettings'), }, + { + title: t('quickpay.nav_title'), + type: EItemType.button, + testID: 'QuickpaySettings', + onPress: (): void => { + if (quickpayIntroSeen) { + navigation.navigate('QuickpaySettings'); + } else { + navigation.navigate('QuickpayIntro'); + } + }, + }, ]; if (lastUsedTags.length) { @@ -71,12 +85,14 @@ const GeneralSettings = ({ onPress: (): void => navigation.navigate('TagsSettings'), }); } + return [{ data }]; }, [ lastUsedTags, selectedCurrency, selectedUnit, selectedTransactionSpeed, + quickpayIntroSeen, navigation, t, ]); diff --git a/src/screens/Settings/Quickpay/QuickpayIntro.tsx b/src/screens/Settings/Quickpay/QuickpayIntro.tsx new file mode 100644 index 000000000..873a3de19 --- /dev/null +++ b/src/screens/Settings/Quickpay/QuickpayIntro.tsx @@ -0,0 +1,44 @@ +import React, { ReactElement } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { Display } from '../../../styles/text'; +import OnboardingScreen from '../../../components/OnboardingScreen'; +import { useAppDispatch } from '../../../hooks/redux'; +import { updateUser } from '../../../store/slices/user'; +import type { SettingsScreenProps } from '../../../navigation/types'; + +const imageSrc = require('../../../assets/illustrations/fast-forward.png'); + +const QuickpayIntro = ({ + navigation, +}: SettingsScreenProps<'QuickpayIntro'>): ReactElement => { + const { t } = useTranslation('settings'); + const dispatch = useAppDispatch(); + + return ( + }} + /> + } + description={t('quickpay.intro.description')} + image={imageSrc} + imagePosition="center" + buttonText={t('continue')} + testID="QuickpayIntro" + onClosePress={(): void => { + navigation.navigate('Wallet'); + }} + onButtonPress={(): void => { + dispatch(updateUser({ quickpayIntroSeen: true })); + navigation.navigate('QuickpaySettings'); + }} + /> + ); +}; + +export default QuickpayIntro; diff --git a/src/screens/Settings/Quickpay/QuickpaySettings.tsx b/src/screens/Settings/Quickpay/QuickpaySettings.tsx new file mode 100644 index 000000000..443a3bd23 --- /dev/null +++ b/src/screens/Settings/Quickpay/QuickpaySettings.tsx @@ -0,0 +1,115 @@ +import React, { memo, ReactElement, useMemo } from 'react'; +import { Image, StyleSheet } from 'react-native'; +import { useTranslation } from 'react-i18next'; + +import { BodyM, BodyS, Caption13Up } from '../../../styles/text'; +import { View as ThemedView, View } from '../../../styles/components'; +import SwitchRow from '../../../components/SwitchRow'; +import SafeAreaInset from '../../../components/SafeAreaInset'; +import NavigationHeader from '../../../components/NavigationHeader'; +import { useAppDispatch, useAppSelector } from '../../../hooks/redux'; +import { updateSettings } from '../../../store/slices/settings'; +import { + enableQuickpaySelector, + quickpayAmountSelector, +} from '../../../store/reselect/settings'; +import { SettingsScreenProps } from '../../../navigation/types'; +import Slider from '../../../components/Slider'; + +const imageSrc = require('../../../assets/illustrations/fast-forward.png'); + +const QuickpaySettings = ({ + navigation, +}: SettingsScreenProps<'QuickpaySettings'>): ReactElement => { + const { t } = useTranslation('settings'); + const dispatch = useAppDispatch(); + const enabledQuickpay = useAppSelector(enableQuickpaySelector); + const amount = useAppSelector(quickpayAmountSelector); + + const sliderSteps = useMemo(() => [1, 5, 10, 20, 50], []); + + const onToggle = (): void => { + dispatch(updateSettings({ enableQuickpay: !enabledQuickpay })); + }; + + const onChange = (value: number): void => { + dispatch(updateSettings({ quickpayAmount: value })); + }; + + return ( + + + { + navigation.navigate('Wallet'); + }} + /> + + + + {t('quickpay.settings.toggle')} + + + + {t('quickpay.settings.text', { amount })} + + + + + {t('quickpay.settings.label')} + + + + + + + + + {t('quickpay.settings.note')} + + + + + ); +}; + +const styles = StyleSheet.create({ + root: { + flex: 1, + }, + content: { + flex: 1, + paddingTop: 16, + paddingHorizontal: 16, + }, + switch: { + borderBottomWidth: 1, + borderBottomColor: 'rgba(255, 255, 255, 0.1)', + }, + text: { + marginTop: 16, + }, + sliderContainer: { + marginTop: 32, + }, + imageContainer: { + flexShrink: 1, + alignItems: 'center', + alignSelf: 'center', + width: 256, + aspectRatio: 1, + marginTop: 'auto', + marginBottom: 'auto', + }, + image: { + flex: 1, + resizeMode: 'contain', + }, +}); + +export default memo(QuickpaySettings); diff --git a/src/screens/Wallets/Send/Amount.tsx b/src/screens/Wallets/Send/Amount.tsx index 0bb2bf57b..9112cc82d 100644 --- a/src/screens/Wallets/Send/Amount.tsx +++ b/src/screens/Wallets/Send/Amount.tsx @@ -256,7 +256,7 @@ const Amount = ({ navigation }: SendScreenProps<'Amount'>): ReactElement => { - + {t('send_available')} diff --git a/src/screens/Wallets/Send/Quickpay.tsx b/src/screens/Wallets/Send/Quickpay.tsx new file mode 100644 index 000000000..b8d5e9bfe --- /dev/null +++ b/src/screens/Wallets/Send/Quickpay.tsx @@ -0,0 +1,129 @@ +import React, { memo, ReactElement, useCallback } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Trans, useTranslation } from 'react-i18next'; +import { useFocusEffect } from '@react-navigation/native'; + +import { Display } from '../../../styles/text'; +import BottomSheetNavigationHeader from '../../../components/BottomSheetNavigationHeader'; +import SafeAreaInset from '../../../components/SafeAreaInset'; +import GradientView from '../../../components/GradientView'; +import AmountToggle from '../../../components/AmountToggle'; +import SyncSpinner from '../../../components/SyncSpinner'; +import HourglassSpinner from '../../../components/HourglassSpinner'; +import LightningSyncing from '../../../components/LightningSyncing'; +import { useAppDispatch } from '../../../hooks/redux'; +import { addPendingPayment } from '../../../store/slices/lightning'; +import { EActivityType } from '../../../store/types/activity'; +import type { SendScreenProps } from '../../../navigation/types'; +import { + decodeLightningInvoice, + payLightningInvoice, +} from '../../../utils/lightning'; + +const imageSrc = require('../../../assets/illustrations/coin-stack-4.png'); + +const Quickpay = ({ + navigation, + route, +}: SendScreenProps<'Quickpay'>): ReactElement => { + const { t } = useTranslation('wallet'); + const { invoice, amount } = route.params; + const dispatch = useAppDispatch(); + + useFocusEffect( + useCallback(() => { + const pay = async (): Promise => { + const decodeResult = await decodeLightningInvoice(invoice); + if (decodeResult.isErr()) { + navigation.navigate('Error', { + errorMessage: t('send_error_create_tx'), + }); + return; + } + + const { payment_hash } = decodeResult.value; + const payResult = await payLightningInvoice({ invoice }); + + if (payResult.isErr()) { + const errorMessage = payResult.error.message; + if (errorMessage === 'Timed Out.') { + dispatch(addPendingPayment({ payment_hash, amount })); + navigation.navigate('Pending', { txId: payment_hash }); + return; + } + + console.error(errorMessage); + navigation.navigate('Error', { + errorMessage: t('send_error_create_tx'), + }); + return; + } + + navigation.navigate('Success', { + type: EActivityType.lightning, + txId: payment_hash, + amount, + }); + }; + + pay(); + }, [invoice, amount, dispatch, navigation, t]), + ); + + return ( + + + + + + + + + + + + + }} + /> + + + + + + + ); +}; + +const styles = StyleSheet.create({ + root: { + flex: 1, + }, + content: { + flex: 1, + paddingHorizontal: 16, + }, + imageContainer: { + flexShrink: 1, + justifyContent: 'center', + alignItems: 'center', + alignSelf: 'center', + width: 311, + aspectRatio: 1, + marginTop: 'auto', + marginBottom: 'auto', + }, + syncing: { + ...StyleSheet.absoluteFillObject, + }, +}); + +export default memo(Quickpay); diff --git a/src/screens/Wallets/Send/Recipient.tsx b/src/screens/Wallets/Send/Recipient.tsx index 6cb26d42c..68a7b33db 100644 --- a/src/screens/Wallets/Send/Recipient.tsx +++ b/src/screens/Wallets/Send/Recipient.tsx @@ -24,6 +24,7 @@ import { useBottomSheetBackPress } from '../../../hooks/bottomSheet'; import type { SendScreenProps } from '../../../navigation/types'; import { lastPaidSelector } from '../../../store/reselect/slashtags'; import { selectedNetworkSelector } from '../../../store/reselect/wallet'; +import GradientView from '../../../components/GradientView'; const imageSrc = require('../../../assets/illustrations/coin-stack-logo.png'); @@ -107,7 +108,7 @@ const Recipient = ({ }; return ( - + - + ); }; const styles = StyleSheet.create({ - container: { + root: { flex: 1, }, content: { diff --git a/src/store/index.ts b/src/store/index.ts index 39fd4b371..86c1ae7e0 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -32,7 +32,7 @@ const persistConfig = { key: 'root', storage: reduxStorage, // increase version after store shape changes - version: 47, + version: 48, stateReconciler: autoMergeLevel2, blacklist: ['receive', 'ui'], migrate: createMigrate(migrations, { debug: __ENABLE_MIGRATION_DEBUG__ }), diff --git a/src/store/migrations/index.ts b/src/store/migrations/index.ts index dd5f30988..343ec9193 100644 --- a/src/store/migrations/index.ts +++ b/src/store/migrations/index.ts @@ -54,6 +54,20 @@ const migrations = { }, }; }, + 48: (state): PersistedState => { + return { + ...state, + settings: { + ...state.settings, + enableQuickpay: false, + quickpayAmount: 5, + }, + user: { + ...state.user, + quickpayIntroSeen: false, + }, + }; + }, }; export default migrations; diff --git a/src/store/reselect/settings.ts b/src/store/reselect/settings.ts index 67566d861..2f784d0cd 100644 --- a/src/store/reselect/settings.ts +++ b/src/store/reselect/settings.ts @@ -40,11 +40,17 @@ export const hideBalanceSelector = (state: RootState): boolean => { export const hideBalanceOnOpenSelector = (state: RootState): boolean => { return state.settings.hideBalanceOnOpen; }; +export const enableDevOptionsSelector = (state: RootState): boolean => { + return state.settings.enableDevOptions; +}; export const enableOfflinePaymentsSelector = (state: RootState): boolean => { return state.settings.enableOfflinePayments; }; -export const enableDevOptionsSelector = (state: RootState): boolean => { - return state.settings.enableDevOptions; +export const enableQuickpaySelector = (state: RootState): boolean => { + return state.settings.enableQuickpay; +}; +export const quickpayAmountSelector = (state: RootState): number => { + return state.settings.quickpayAmount; }; export const pinSelector = (state: RootState): boolean => { return state.settings.pin; diff --git a/src/store/reselect/todos.ts b/src/store/reselect/todos.ts index 67c1d7e6c..736fd44cf 100644 --- a/src/store/reselect/todos.ts +++ b/src/store/reselect/todos.ts @@ -9,6 +9,7 @@ import { lightningSettingUpTodo, lightningTodo, pinTodo, + quickpayTodo, slashtagsProfileTodo, supportTodo, transferPendingTodo, @@ -136,9 +137,9 @@ export const todosFullSelector = createShallowEqualSelector( if (!hide.invite) { res.push(inviteTodo); } - // if (!hide.fastpay) { - // res.push(fastpayTodo); - // } + if (!hide.quickpay) { + res.push(quickpayTodo); + } if (!hide.slashtagsProfile && onboardingStep !== 'Done') { res.push(slashtagsProfileTodo); } diff --git a/src/store/reselect/user.ts b/src/store/reselect/user.ts index 942292f32..4c33fb5ee 100644 --- a/src/store/reselect/user.ts +++ b/src/store/reselect/user.ts @@ -24,6 +24,10 @@ export const lightningSettingUpStepSelector = createSelector( (user): number => user.lightningSettingUpStep, ); +export const quickpayIntroSeenSelector = (state: RootState): boolean => { + return state.user.quickpayIntroSeen; +}; + export const requiresRemoteRestoreSelector = createSelector( [userState], (user): boolean => user.requiresRemoteRestore, diff --git a/src/store/shapes/settings.ts b/src/store/shapes/settings.ts index 053f600ec..e4ebbfe2e 100644 --- a/src/store/shapes/settings.ts +++ b/src/store/shapes/settings.ts @@ -93,7 +93,10 @@ export const initialSettingsState: TSettings = { coinSelectAuto: true, coinSelectPreference: 'small', receivePreference: defaultReceivePreference, + enableDevOptions: __DEV__, enableOfflinePayments: false, + enableQuickpay: false, + quickpayAmount: 5, showWidgets: true, showWidgetTitles: false, transactionSpeed: ETransactionSpeed.normal, @@ -101,7 +104,6 @@ export const initialSettingsState: TSettings = { hideBalance: false, hideBalanceOnOpen: false, hideOnboardingMessage: false, - enableDevOptions: __DEV__, treasureChests: [], orangeTickets: [], webRelay: __WEB_RELAY__, diff --git a/src/store/shapes/todos.ts b/src/store/shapes/todos.ts index f8cd951a3..ed4caae7f 100644 --- a/src/store/shapes/todos.ts +++ b/src/store/shapes/todos.ts @@ -9,6 +9,7 @@ const imageLightbulb = require('../../assets/illustrations/lightbulb.png'); const imageBitcoin = require('../../assets/illustrations/b-emboss.png'); const imageGift = require('../../assets/illustrations/gift.png'); const imageGroup = require('../../assets/illustrations/group.png'); +const imageFastForward = require('../../assets/illustrations/fast-forward.png'); export const backupSeedPhraseTodo: ITodo = { id: 'backupSeedPhrase', @@ -22,12 +23,6 @@ export const discountTodo: ITodo = { image: imageGift, dismissable: true, }; -export const fastpayTodo: ITodo = { - id: 'fastpay', - color: 'green24', - image: imageLightning, - dismissable: true, -}; export const inviteTodo: ITodo = { id: 'invite', color: 'blue24', @@ -52,6 +47,12 @@ export const lightningReadyTodo: ITodo = { image: imageLightning, dismissable: false, }; +export const quickpayTodo: ITodo = { + id: 'quickpay', + color: 'green24', + image: imageFastForward, + dismissable: true, +}; export const transferPendingTodo: ITodo = { id: 'transferPending', color: 'brand24', diff --git a/src/store/slices/settings.ts b/src/store/slices/settings.ts index 5db71883e..ed0eac59f 100644 --- a/src/store/slices/settings.ts +++ b/src/store/slices/settings.ts @@ -33,7 +33,10 @@ export type TSettings = { coinSelectAuto: boolean; coinSelectPreference: TCoinSelectPreference; receivePreference: TReceiveOption[]; + enableDevOptions: boolean; enableOfflinePayments: boolean; + enableQuickpay: boolean; + quickpayAmount: number; showWidgets: boolean; showWidgetTitles: boolean; transactionSpeed: ETransactionSpeed; @@ -41,7 +44,6 @@ export type TSettings = { hideBalance: boolean; hideBalanceOnOpen: boolean; hideOnboardingMessage: boolean; - enableDevOptions: boolean; treasureChests: TChest[]; orangeTickets: string[]; webRelay: string; diff --git a/src/store/slices/user.ts b/src/store/slices/user.ts index 636fec143..57896ff31 100644 --- a/src/store/slices/user.ts +++ b/src/store/slices/user.ts @@ -10,6 +10,7 @@ export type TUser = { ignoreHighBalanceTimestamp: number; isGeoBlocked: boolean; lightningSettingUpStep: number; + quickpayIntroSeen: boolean; requiresRemoteRestore: boolean; startCoopCloseTimestamp: number; ignoresHideBalanceToast: boolean; @@ -28,6 +29,7 @@ export const initialUserState: TUser = { ignoreHighBalanceTimestamp: 0, isGeoBlocked: false, lightningSettingUpStep: 0, + quickpayIntroSeen: false, requiresRemoteRestore: false, startCoopCloseTimestamp: 0, ignoresHideBalanceToast: false, diff --git a/src/store/types/todos.ts b/src/store/types/todos.ts index 8b97cf461..abba3d860 100644 --- a/src/store/types/todos.ts +++ b/src/store/types/todos.ts @@ -6,12 +6,12 @@ export type TTodoType = | 'btFailed' | 'buyBitcoin' | 'discount' - | 'fastpay' | 'invite' | 'lightning' | 'lightningSettingUp' | 'lightningConnecting' | 'lightningReady' + | 'quickpay' | 'slashtagsProfile' | 'support' | 'transferPending' diff --git a/src/store/types/ui.ts b/src/store/types/ui.ts index a99d609aa..21deadd57 100644 --- a/src/store/types/ui.ts +++ b/src/store/types/ui.ts @@ -25,6 +25,7 @@ export type ViewControllerParamList = { receiveNavigation: { receiveScreen: keyof ReceiveStackParamList } | undefined; sendNavigation: | { screen: keyof SendStackParamList } + | { screen: 'Quickpay'; invoice: string; amount: number } | { screen: 'LNURLAmount'; pParams: LNURLPayParams; url: string } // prettier-ignore | { screen: 'LNURLConfirm'; pParams: LNURLPayParams; url: string; amount?: number; } @@ -62,6 +63,7 @@ export type IViewControllerData = { url?: string; wParams?: LNURLWithdrawParams; pParams?: LNURLPayParams; + invoice?: string; amount?: number; }; diff --git a/src/styles/colors.ts b/src/styles/colors.ts index c0f6580ba..e8cfb16d9 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -36,6 +36,7 @@ export interface IColors { brand50: string; green16: string; green24: string; + green32: string; purple50: string; purple16: string; purple24: string; @@ -84,6 +85,7 @@ const colors: IColors = { brand50: 'rgba(255, 68, 0, 0.50)', green16: 'rgba(117, 191, 114, 0.16)', green24: 'rgba(117, 191, 114, 0.24)', + green32: 'rgba(117, 191, 114, 0.32)', purple16: 'rgba(185, 92, 232, 0.16)', purple24: 'rgba(185, 92, 232, 0.24)', purple32: 'rgba(185, 92, 232, 0.32)', diff --git a/src/utils/i18n/locales/ca/cards.json b/src/utils/i18n/locales/ca/cards.json index 19d5403c4..57c3b8d96 100644 --- a/src/utils/i18n/locales/ca/cards.json +++ b/src/utils/i18n/locales/ca/cards.json @@ -12,7 +12,7 @@ "string": "Balanç de despesa" } }, - "fastpay": { + "quickpay": { "title": { "string": "Pagament ràpid" }, diff --git a/src/utils/i18n/locales/cs/cards.json b/src/utils/i18n/locales/cs/cards.json index fc61a1ca0..1f2f91e20 100644 --- a/src/utils/i18n/locales/cs/cards.json +++ b/src/utils/i18n/locales/cs/cards.json @@ -15,9 +15,9 @@ "string": "Dostupný zůstatek" } }, - "fastpay": { + "quickpay": { "title": { - "string": "Fastpay" + "string": "QuickPay" }, "description": { "string": "Naskenuj a zaplať" diff --git a/src/utils/i18n/locales/de/cards.json b/src/utils/i18n/locales/de/cards.json index 0b8c8f8a5..e0697c801 100644 --- a/src/utils/i18n/locales/de/cards.json +++ b/src/utils/i18n/locales/de/cards.json @@ -15,9 +15,9 @@ "string": "Ausgabenkonto" } }, - "fastpay": { + "quickpay": { "title": { - "string": "Fastpay" + "string": "QuickPay" }, "description": { "string": "Scanne und zahle" diff --git a/src/utils/i18n/locales/en/cards.json b/src/utils/i18n/locales/en/cards.json index 962515c95..7e84fca7a 100644 --- a/src/utils/i18n/locales/en/cards.json +++ b/src/utils/i18n/locales/en/cards.json @@ -18,9 +18,9 @@ "string": "Spending Balance" } }, - "fastpay": { + "quickpay": { "title": { - "string": "Fastpay" + "string": "QuickPay" }, "description": { "string": "Scan and pay" diff --git a/src/utils/i18n/locales/en/settings.json b/src/utils/i18n/locales/en/settings.json index 38b814b04..fca90d9a2 100644 --- a/src/utils/i18n/locales/en/settings.json +++ b/src/utils/i18n/locales/en/settings.json @@ -1,696 +1,723 @@ { - "settings": { - "string": "Settings" - }, - "dev_enabled_title": { - "string": "Dev Options Enabled" - }, - "dev_enabled_message": { - "string": "Developer options are now enabled throughout the app." - }, - "dev_disabled_title": { - "string": "Dev Options Disabled" - }, - "dev_disabled_message": { - "string": "Developer options are now disabled throughout the app." - }, - "general_title": { - "string": "General" - }, - "security_title": { - "string": "Security and Privacy" - }, - "backup_title": { - "string": "Back up or Restore" - }, - "advanced_title": { - "string": "Advanced" - }, - "about_title": { - "string": "About" - }, - "support_title": { - "string": "Support" - }, - "about": { - "title": { - "string": "About Bitkit" - }, - "text": { - "string": "Thank you for being a responsible Bitcoiner.\nChange your wallet, change the world.\n\nBitkit hands you the keys to your money, profile, contacts, and web accounts.\n\nBitkit was crafted by Synonym Software Ltd." - }, - "legal": { - "string": "Legal" - }, - "share": { - "string": "Share" - }, - "version": { - "string": "Version" - }, - "shareText": { - "string": "Change your wallet, change the world. Download Bitkit for iPhone {appStoreUrl} or Android {playStoreUrl}" - } - }, - "dev_title": { - "string": "Dev Settings" - }, - "general": { - "currency_local": { - "string": "Local currency" - }, - "currency_local_title": { - "string": "Local Currency" - }, - "currency_footer": { - "string": "Prices powered by Bitfinex & CoinGecko." - }, - "currency_most_used": { - "string": "Most Used" - }, - "currency_other": { - "string": "Other Currencies" - }, - "unit": { - "string": "Default unit" - }, - "unit_title": { - "string": "Default Unit" - }, - "unit_display": { - "string": "Display amounts in" - }, - "unit_bitcoin": { - "string": "Bitcoin" - }, - "unit_note": { - "string": "Tip: Quickly toggle between Bitcoin and {currency} by tapping on your wallet balance." - }, - "denomination_label": { - "string": "Bitcoin denomination" - }, - "denomination_modern": { - "string": "Modern (₿ 10 000)" - }, - "denomination_classic": { - "string": "Classic (₿ 0.00010000)" - }, - "speed": { - "string": "Transaction speed" - }, - "speed_title": { - "string": "Transaction Speed" - }, - "speed_default": { - "string": "Default Transaction Speed" - }, - "speed_fee_custom": { - "string": "Set Custom Fee" - }, - "speed_fee_total": { - "string": "₿ {feeSats} for the average transaction" - }, - "speed_fee_total_fiat": { - "string": "₿ {feeSats} for the average transaction ({fiatSymbol}{fiatFormatted})" - }, - "tags": { - "string": "Tags" - }, - "tags_previously": { - "string": "Previously used tags" - } - }, - "widgets": { - "nav_title": { - "string": "Widgets" - }, - "showWidgets": { - "string": "Widgets" - }, - "showWidgetTitles": { - "string": "Show Widget Titles" - } - }, - "security": { - "title": { - "string": "Security And Privacy" - }, - "swipe_balance_to_hide": { - "string": "Swipe balance to hide" - }, - "hide_balance_on_open": { - "string": "Hide balance on open" - }, - "clipboard": { - "string": "Read clipboard for ease of use" - }, - "warn_100": { - "string": "Warn when sending over $100" - }, - "pin": { - "string": "PIN Code" - }, - "pin_change": { - "string": "Change PIN Code" - }, - "pin_launch": { - "string": "Require PIN on launch" - }, - "pin_idle": { - "string": "Require PIN when idle" - }, - "pin_payments": { - "string": "Require PIN for payments" - }, - "pin_enabled": { - "string": "Enabled" - }, - "pin_disabled": { - "string": "Disabled" - }, - "use_bio": { - "string": "Use {biometryTypeName} instead" - }, - "footer": { - "string": "When enabled, you can use {biometryTypeName} instead of your PIN code to unlock your wallet or send payments." - } - }, - "backup": { - "title": { - "string": "Back Up Or Restore" - }, - "wallet": { - "string": "Back up your wallet" - }, - "export": { - "string": "Export wallet data to phone" - }, - "reset": { - "string": "Reset and restore wallet" - }, - "failed_title": { - "string": "Data Backup Failure" - }, - "failed_message": { - "string": "Bitkit failed to back up wallet data. Retrying in {interval, plural, one {# minute} other {# minutes}}." - }, - "latest": { - "string": "latest data backups" - }, - "status_failed": { - "string": "Failed Backup: {time}" - }, - "status_success": { - "string": "Latest Backup: {time}" - }, - "category_connections": { - "string": "Connections" - }, - "category_connection_receipts": { - "string": "Connection Receipts" - }, - "category_transaction_log": { - "string": "Transaction Log" - }, - "category_settings": { - "string": "Settings" - }, - "category_widgets": { - "string": "Widgets" - }, - "category_tags": { - "string": "Tags" - }, - "category_profile": { - "string": "Profile" - }, - "category_contacts": { - "string": "Contacts" - } - }, - "support": { - "title": { - "string": "Support" - }, - "text": { - "string": "Need help? Report your issue from within Bitkit, visit the help center, check the status, or reach out to us on our social channels." - }, - "report": { - "string": "Report Issue" - }, - "help": { - "string": "Help Center" - }, - "status": { - "string": "App Status" - }, - "report_text": { - "string": "Please describe the issue you are experiencing or ask a general question." - }, - "label_address": { - "string": "Email address" - }, - "label_message": { - "string": "Issue or question" - }, - "placeholder_address": { - "string": "satoshi@satoshi.com" - }, - "placeholder_message": { - "string": "Describe the issue or ask a question" - }, - "text_button": { - "string": "Send" - }, - "title_success": { - "string": "Sent Successfully" - }, - "text_success": { - "string": "Thank you for contacting us! We will try to get back to you as soon as possible." - }, - "text_success_button": { - "string": "OK" - }, - "title_unsuccess": { - "string": "Failed To Send" - }, - "text_unsuccess": { - "string": "Something went wrong while trying to send your issue or question. Please try again." - }, - "text_unsuccess_button": { - "string": "Try Again" - } - }, - "status": { - "title": { - "string": "App Status" - }, - "internet": { - "title": { - "string": "Internet" - }, - "ready": { - "string": "Connected" - }, - "pending": { - "string": "Reconnecting..." - }, - "error": { - "string": "Disconnected" - } - }, - "bitcoin_node": { - "title": { - "string": "Bitcoin Node" - }, - "ready": { - "string": "Connected" - }, - "pending": { - "string": "Connecting..." - }, - "error": { - "string": "Could not connect to Electrum" - } - }, - "lightning_node": { - "title": { - "string": "Lightning Node" - }, - "ready": { - "string": "Synced" - }, - "pending": { - "string": "Syncing..." - }, - "error": { - "string": "Could not initiate" - } - }, - "lightning_connection": { - "title": { - "string": "Lightning Connection" - }, - "ready": { - "string": "Open" - }, - "pending": { - "string": "Opening..." - }, - "error": { - "string": "No open connections" - } - }, - "full_backup": { - "title": { - "string": "Latest Full Data Backup" - }, - "ready": { - "string": "Backed up" - }, - "pending": { - "string": "Backing up..." - }, - "error": { - "string": "Failed to complete a full backup" - } - } - }, - "adv": { - "section_payments": { - "string": "Payments" - }, - "section_networks": { - "string": "Networks" - }, - "section_other": { - "string": "Other" - }, - "address_type": { - "string": "Bitcoin Address Type" - }, - "monitored_address_types": { - "string": "Monitored Address Types" - }, - "monitored_address_types_update_title": { - "string": "Monitored Address Types Updated" - }, - "monitored_address_types_update_description": { - "string": "Changes will take full effect after app restarts." - }, - "gap_limit": { - "string": "Address Gap Limit" - }, - "coin_selection": { - "string": "Coin Selection" - }, - "cs_method": { - "string": "Coin Selection Method" - }, - "cs_manual": { - "string": "Manual" - }, - "cs_auto": { - "string": "Autopilot" - }, - "cs_auto_mode": { - "string": "Autopilot Mode" - }, - "cs_max": { - "string": "More Private" - }, - "cs_max_description": { - "string": "Sort by and use smallest UTXO first. Higher fee, but hides your largest UTXO's." - }, - "cs_min": { - "string": "Lowest Fee" - }, - "cs_min_description": { - "string": "Sort by and use largest UTXO first. Lowest fee, but reveals your largest UTXO's." - }, - "cs_consolidate": { - "string": "Consolidate" - }, - "cs_consolidate_description": { - "string": "Use all available UTXO's regardless of the amount being sent. Use this method when fees are low in order to reduce fees in future transactions." - }, - "payment_preference": { - "string": "Payment Preference" - }, - "pp_header": { - "string": "Choose how you prefer to receive money when users send funds to your profile key." - }, - "pp_footer": { - "string": "* This requires sharing payment data." - }, - "pp_drag": { - "string": "Payment preference (drag to reorder)" - }, - "pp_contacts": { - "string": "Pay to/from contacts" - }, - "pp_contacts_switch": { - "string": "Enable payments with contacts*" - }, - "address_viewer": { - "string": "Address Viewer" - }, - "rescan": { - "string": "Rescan Addresses" - }, - "suggestions_reset": { - "string": "Reset Suggestions" - }, - "reset_title": { - "string": "Reset Suggestions?" - }, - "reset_desc": { - "string": "Are you sure you want to reset the suggestions? They will reappear in case you have removed them from your Bitkit wallet overview." - }, - "reset_confirm": { - "string": "Yes, Reset" - }, - "lightning_connections": { - "string": "Lightning Connections" - }, - "lightning_node": { - "string": "Lightning Node" - }, - "electrum_server": { - "string": "Electrum Server" - }, - "rgs_server": { - "string": "Rapid-Gossip-Sync" - }, - "web_relay": { - "string": "Slashtags Web Relay" - }, - "bitcoin_network": { - "string": "Bitcoin Network" - } - }, - "fee": { - "fast": { - "label": { - "string": "Fast (more expensive)" - }, - "value": { - "string": "Fast" - }, - "description": { - "string": "± 10-20 minutes" - } - }, - "normal": { - "label": { - "string": "Normal" - }, - "value": { - "string": "Normal" - }, - "description": { - "string": "± 20-60 minutes" - } - }, - "slow": { - "label": { - "string": "Slow (cheaper)" - }, - "value": { - "string": "Slow " - }, - "description": { - "string": "± 1-2 hours" - } - }, - "custom": { - "label": { - "string": "Custom" - }, - "value": { - "string": "Custom" - }, - "description": { - "string": "Depends on fee" - } - } - }, - "addr": { - "no_addrs": { - "string": "No Addresses To Display" - }, - "loading": { - "string": "Loading Addresses..." - }, - "no_funds_receiving": { - "string": "No funds found under the {addressType} address type, receiving addresses up to index {index}." - }, - "no_funds_change": { - "string": "No funds found under the {addressType} address type, change addresses up to index {index}." - }, - "no_addrs_with_funds": { - "string": "No addresses with funds found when searching for \"{searchTxt}\"" - }, - "no_addrs_str": { - "string": "No addresses found when searching for \"{searchTxt}\"" - }, - "rescan_error": { - "string": "Rescan Failed" - }, - "rescan_error_description": { - "string": "Bitkit was not able to check the address balances." - }, - "spend_number": { - "string": "{count, plural, one {Spend ₿ {fundsToSpend} From # address} other {Spend ₿ {fundsToSpend} From # addresses}}" - }, - "spend_all": { - "string": "{count, plural, one {Spend All Funds From # address} other {Spend All Funds From # addresses}}" - }, - "index": { - "string": "Index: {index}" - }, - "path": { - "string": "Path: {path}" - }, - "private_hide": { - "string": "Hide Private Key" - }, - "private_view": { - "string": "View Private Key" - }, - "private_key": { - "string": "Private Key: {privateKey}" - }, - "addr_change": { - "string": "Change Addresses" - }, - "addr_receiving": { - "string": "Receiving Addresses" - }, - "sats_found": { - "string": "₿ {totalBalance} found" - }, - "gen_20": { - "string": "Generate 20 More" - }, - "check_balances": { - "string": "Check Balances" - }, - "copied": { - "string": "Copied to clipboard" - } - }, - "es": { - "error_host_port": { - "string": "Please specify a host and port to connect to." - }, - "error_host": { - "string": "Please specify a host to connect to." - }, - "error_port": { - "string": "Please specify a port to connect to." - }, - "error_port_invalid": { - "string": "Please specify a valid port." - }, - "error_invalid_http": { - "string": "Please specify a valid url." - }, - "error_peer": { - "string": "Electrum Error" - }, - "server_updated_title": { - "string": "Electrum Server Updated" - }, - "server_updated_message": { - "string": "Successfully connected to {host}:{port}" - }, - "server_error": { - "string": "Electrum Connection Failed" - }, - "server_error_description": { - "string": "Bitkit could not establish a connection to Electrum." - }, - "connected_to": { - "string": "Currently connected to" - }, - "disconnected": { - "string": "disconnected" - }, - "host": { - "string": "Host" - }, - "port": { - "string": "Port" - }, - "protocol": { - "string": "Protocol" - }, - "button_reset": { - "string": "Reset To Default" - }, - "button_connect": { - "string": "Connect To Host" - } - }, - "gap": { - "save": { - "string": "Save" - }, - "reset": { - "string": "Reset" - }, - "gap_limit_update_title": { - "string": "Address Gap Limit Updated" - }, - "gap_limit_update_description": { - "string": "Changes will take full effect after app restarts." - }, - "look_behind": { - "string": "Look Behind" - }, - "look_ahead": { - "string": "Look Ahead" - }, - "look_behind_change": { - "string": "Look Behind Change" - }, - "look_ahead_change": { - "string": "Look Ahead Change" - } - }, - "rgs": { - "server_url": { - "string": "Rapid-Gossip-Sync Server URL" - }, - "button_connect": { - "string": "Connect" - }, - "update_success_title": { - "string": "Rapid-Gossip-Sync Server Updated" - }, - "update_success_description": { - "string": "You may need to restart the app once or twice for this change to take effect." - } - }, - "wr": { - "error_wr": { - "string": "Web Relay Error" - }, - "error_url": { - "string": "Please specify a URL to connect to." - }, - "error_https": { - "string": "Not a valid HTTPS url." - }, - "error_healthcheck": { - "string": "Healthcheck Failed" - }, - "url_updated_title": { - "string": "Web Relay Updated" - }, - "url_updated_message": { - "string": "Successfully connected to {url}" - } - } + "settings": { + "string": "Settings" + }, + "dev_enabled_title": { + "string": "Dev Options Enabled" + }, + "dev_enabled_message": { + "string": "Developer options are now enabled throughout the app." + }, + "dev_disabled_title": { + "string": "Dev Options Disabled" + }, + "dev_disabled_message": { + "string": "Developer options are now disabled throughout the app." + }, + "general_title": { + "string": "General" + }, + "security_title": { + "string": "Security and Privacy" + }, + "backup_title": { + "string": "Back up or Restore" + }, + "advanced_title": { + "string": "Advanced" + }, + "about_title": { + "string": "About" + }, + "support_title": { + "string": "Support" + }, + "about": { + "title": { + "string": "About Bitkit" + }, + "text": { + "string": "Thank you for being a responsible Bitcoiner.\nChange your wallet, change the world.\n\nBitkit hands you the keys to your money, profile, contacts, and web accounts.\n\nBitkit was crafted by Synonym Software Ltd." + }, + "legal": { + "string": "Legal" + }, + "share": { + "string": "Share" + }, + "version": { + "string": "Version" + }, + "shareText": { + "string": "Change your wallet, change the world. Download Bitkit for iPhone {appStoreUrl} or Android {playStoreUrl}" + } + }, + "dev_title": { + "string": "Dev Settings" + }, + "general": { + "currency_local": { + "string": "Local currency" + }, + "currency_local_title": { + "string": "Local Currency" + }, + "currency_footer": { + "string": "Prices powered by Bitfinex & CoinGecko." + }, + "currency_most_used": { + "string": "Most Used" + }, + "currency_other": { + "string": "Other Currencies" + }, + "unit": { + "string": "Default unit" + }, + "unit_title": { + "string": "Default Unit" + }, + "unit_display": { + "string": "Display amounts in" + }, + "unit_bitcoin": { + "string": "Bitcoin" + }, + "unit_note": { + "string": "Tip: Quickly toggle between Bitcoin and {currency} by tapping on your wallet balance." + }, + "denomination_label": { + "string": "Bitcoin denomination" + }, + "denomination_modern": { + "string": "Modern (₿ 10 000)" + }, + "denomination_classic": { + "string": "Classic (₿ 0.00010000)" + }, + "speed": { + "string": "Transaction speed" + }, + "speed_title": { + "string": "Transaction Speed" + }, + "speed_default": { + "string": "Default Transaction Speed" + }, + "speed_fee_custom": { + "string": "Set Custom Fee" + }, + "speed_fee_total": { + "string": "₿ {feeSats} for the average transaction" + }, + "speed_fee_total_fiat": { + "string": "₿ {feeSats} for the average transaction ({fiatSymbol}{fiatFormatted})" + }, + "tags": { + "string": "Tags" + }, + "tags_previously": { + "string": "Previously used tags" + } + }, + "widgets": { + "nav_title": { + "string": "Widgets" + }, + "showWidgets": { + "string": "Widgets" + }, + "showWidgetTitles": { + "string": "Show Widget Titles" + } + }, + "quickpay": { + "nav_title": { + "string": "QuickPay" + }, + "intro": { + "title": { + "string": "Frictionless\npayments" + }, + "description": { + "string": "Bitkit QuickPay makes checking out faster by automatically paying QR codes when scanned." + } + }, + "settings": { + "toggle": { + "string": "Enable QuickPay" + }, + "text": { + "string": "If enabled, scanned invoices below ${amount} will be paid automatically without requiring your confirmation or PIN*." + }, + "label": { + "string": "Quickpay threshold" + }, + "note": { + "string": "* Bitkit QuickPay exclusively supports payments from your Spending Balance." + } + } + }, + "security": { + "title": { + "string": "Security And Privacy" + }, + "swipe_balance_to_hide": { + "string": "Swipe balance to hide" + }, + "hide_balance_on_open": { + "string": "Hide balance on open" + }, + "clipboard": { + "string": "Read clipboard for ease of use" + }, + "warn_100": { + "string": "Warn when sending over $100" + }, + "pin": { + "string": "PIN Code" + }, + "pin_change": { + "string": "Change PIN Code" + }, + "pin_launch": { + "string": "Require PIN on launch" + }, + "pin_idle": { + "string": "Require PIN when idle" + }, + "pin_payments": { + "string": "Require PIN for payments" + }, + "pin_enabled": { + "string": "Enabled" + }, + "pin_disabled": { + "string": "Disabled" + }, + "use_bio": { + "string": "Use {biometryTypeName} instead" + }, + "footer": { + "string": "When enabled, you can use {biometryTypeName} instead of your PIN code to unlock your wallet or send payments." + } + }, + "backup": { + "title": { + "string": "Back Up Or Restore" + }, + "wallet": { + "string": "Back up your wallet" + }, + "export": { + "string": "Export wallet data to phone" + }, + "reset": { + "string": "Reset and restore wallet" + }, + "failed_title": { + "string": "Data Backup Failure" + }, + "failed_message": { + "string": "Bitkit failed to back up wallet data. Retrying in {interval, plural, one {# minute} other {# minutes}}." + }, + "latest": { + "string": "latest data backups" + }, + "status_failed": { + "string": "Failed Backup: {time}" + }, + "status_success": { + "string": "Latest Backup: {time}" + }, + "category_connections": { + "string": "Connections" + }, + "category_connection_receipts": { + "string": "Connection Receipts" + }, + "category_transaction_log": { + "string": "Transaction Log" + }, + "category_settings": { + "string": "Settings" + }, + "category_widgets": { + "string": "Widgets" + }, + "category_tags": { + "string": "Tags" + }, + "category_profile": { + "string": "Profile" + }, + "category_contacts": { + "string": "Contacts" + } + }, + "support": { + "title": { + "string": "Support" + }, + "text": { + "string": "Need help? Report your issue from within Bitkit, visit the help center, check the status, or reach out to us on our social channels." + }, + "report": { + "string": "Report Issue" + }, + "help": { + "string": "Help Center" + }, + "status": { + "string": "App Status" + }, + "report_text": { + "string": "Please describe the issue you are experiencing or ask a general question." + }, + "label_address": { + "string": "Email address" + }, + "label_message": { + "string": "Issue or question" + }, + "placeholder_address": { + "string": "satoshi@satoshi.com" + }, + "placeholder_message": { + "string": "Describe the issue or ask a question" + }, + "text_button": { + "string": "Send" + }, + "title_success": { + "string": "Sent Successfully" + }, + "text_success": { + "string": "Thank you for contacting us! We will try to get back to you as soon as possible." + }, + "text_success_button": { + "string": "OK" + }, + "title_unsuccess": { + "string": "Failed To Send" + }, + "text_unsuccess": { + "string": "Something went wrong while trying to send your issue or question. Please try again." + }, + "text_unsuccess_button": { + "string": "Try Again" + } + }, + "status": { + "title": { + "string": "App Status" + }, + "internet": { + "title": { + "string": "Internet" + }, + "ready": { + "string": "Connected" + }, + "pending": { + "string": "Reconnecting..." + }, + "error": { + "string": "Disconnected" + } + }, + "bitcoin_node": { + "title": { + "string": "Bitcoin Node" + }, + "ready": { + "string": "Connected" + }, + "pending": { + "string": "Connecting..." + }, + "error": { + "string": "Could not connect to Electrum" + } + }, + "lightning_node": { + "title": { + "string": "Lightning Node" + }, + "ready": { + "string": "Synced" + }, + "pending": { + "string": "Syncing..." + }, + "error": { + "string": "Could not initiate" + } + }, + "lightning_connection": { + "title": { + "string": "Lightning Connection" + }, + "ready": { + "string": "Open" + }, + "pending": { + "string": "Opening..." + }, + "error": { + "string": "No open connections" + } + }, + "full_backup": { + "title": { + "string": "Latest Full Data Backup" + }, + "ready": { + "string": "Backed up" + }, + "pending": { + "string": "Backing up..." + }, + "error": { + "string": "Failed to complete a full backup" + } + } + }, + "adv": { + "section_payments": { + "string": "Payments" + }, + "section_networks": { + "string": "Networks" + }, + "section_other": { + "string": "Other" + }, + "address_type": { + "string": "Bitcoin Address Type" + }, + "monitored_address_types": { + "string": "Monitored Address Types" + }, + "monitored_address_types_update_title": { + "string": "Monitored Address Types Updated" + }, + "monitored_address_types_update_description": { + "string": "Changes will take full effect after app restarts." + }, + "gap_limit": { + "string": "Address Gap Limit" + }, + "coin_selection": { + "string": "Coin Selection" + }, + "cs_method": { + "string": "Coin Selection Method" + }, + "cs_manual": { + "string": "Manual" + }, + "cs_auto": { + "string": "Autopilot" + }, + "cs_auto_mode": { + "string": "Autopilot Mode" + }, + "cs_max": { + "string": "More Private" + }, + "cs_max_description": { + "string": "Sort by and use smallest UTXO first. Higher fee, but hides your largest UTXO's." + }, + "cs_min": { + "string": "Lowest Fee" + }, + "cs_min_description": { + "string": "Sort by and use largest UTXO first. Lowest fee, but reveals your largest UTXO's." + }, + "cs_consolidate": { + "string": "Consolidate" + }, + "cs_consolidate_description": { + "string": "Use all available UTXO's regardless of the amount being sent. Use this method when fees are low in order to reduce fees in future transactions." + }, + "payment_preference": { + "string": "Payment Preference" + }, + "pp_header": { + "string": "Choose how you prefer to receive money when users send funds to your profile key." + }, + "pp_footer": { + "string": "* This requires sharing payment data." + }, + "pp_drag": { + "string": "Payment preference (drag to reorder)" + }, + "pp_contacts": { + "string": "Pay to/from contacts" + }, + "pp_contacts_switch": { + "string": "Enable payments with contacts*" + }, + "address_viewer": { + "string": "Address Viewer" + }, + "rescan": { + "string": "Rescan Addresses" + }, + "suggestions_reset": { + "string": "Reset Suggestions" + }, + "reset_title": { + "string": "Reset Suggestions?" + }, + "reset_desc": { + "string": "Are you sure you want to reset the suggestions? They will reappear in case you have removed them from your Bitkit wallet overview." + }, + "reset_confirm": { + "string": "Yes, Reset" + }, + "lightning_connections": { + "string": "Lightning Connections" + }, + "lightning_node": { + "string": "Lightning Node" + }, + "electrum_server": { + "string": "Electrum Server" + }, + "rgs_server": { + "string": "Rapid-Gossip-Sync" + }, + "web_relay": { + "string": "Slashtags Web Relay" + }, + "bitcoin_network": { + "string": "Bitcoin Network" + } + }, + "fee": { + "fast": { + "label": { + "string": "Fast (more expensive)" + }, + "value": { + "string": "Fast" + }, + "description": { + "string": "± 10-20 minutes" + } + }, + "normal": { + "label": { + "string": "Normal" + }, + "value": { + "string": "Normal" + }, + "description": { + "string": "± 20-60 minutes" + } + }, + "slow": { + "label": { + "string": "Slow (cheaper)" + }, + "value": { + "string": "Slow " + }, + "description": { + "string": "± 1-2 hours" + } + }, + "custom": { + "label": { + "string": "Custom" + }, + "value": { + "string": "Custom" + }, + "description": { + "string": "Depends on fee" + } + } + }, + "addr": { + "no_addrs": { + "string": "No Addresses To Display" + }, + "loading": { + "string": "Loading Addresses..." + }, + "no_funds_receiving": { + "string": "No funds found under the {addressType} address type, receiving addresses up to index {index}." + }, + "no_funds_change": { + "string": "No funds found under the {addressType} address type, change addresses up to index {index}." + }, + "no_addrs_with_funds": { + "string": "No addresses with funds found when searching for \"{searchTxt}\"" + }, + "no_addrs_str": { + "string": "No addresses found when searching for \"{searchTxt}\"" + }, + "rescan_error": { + "string": "Rescan Failed" + }, + "rescan_error_description": { + "string": "Bitkit was not able to check the address balances." + }, + "spend_number": { + "string": "{count, plural, one {Spend ₿ {fundsToSpend} From # address} other {Spend ₿ {fundsToSpend} From # addresses}}" + }, + "spend_all": { + "string": "{count, plural, one {Spend All Funds From # address} other {Spend All Funds From # addresses}}" + }, + "index": { + "string": "Index: {index}" + }, + "path": { + "string": "Path: {path}" + }, + "private_hide": { + "string": "Hide Private Key" + }, + "private_view": { + "string": "View Private Key" + }, + "private_key": { + "string": "Private Key: {privateKey}" + }, + "addr_change": { + "string": "Change Addresses" + }, + "addr_receiving": { + "string": "Receiving Addresses" + }, + "sats_found": { + "string": "₿ {totalBalance} found" + }, + "gen_20": { + "string": "Generate 20 More" + }, + "check_balances": { + "string": "Check Balances" + }, + "copied": { + "string": "Copied to clipboard" + } + }, + "es": { + "error_host_port": { + "string": "Please specify a host and port to connect to." + }, + "error_host": { + "string": "Please specify a host to connect to." + }, + "error_port": { + "string": "Please specify a port to connect to." + }, + "error_port_invalid": { + "string": "Please specify a valid port." + }, + "error_invalid_http": { + "string": "Please specify a valid url." + }, + "error_peer": { + "string": "Electrum Error" + }, + "server_updated_title": { + "string": "Electrum Server Updated" + }, + "server_updated_message": { + "string": "Successfully connected to {host}:{port}" + }, + "server_error": { + "string": "Electrum Connection Failed" + }, + "server_error_description": { + "string": "Bitkit could not establish a connection to Electrum." + }, + "connected_to": { + "string": "Currently connected to" + }, + "disconnected": { + "string": "disconnected" + }, + "host": { + "string": "Host" + }, + "port": { + "string": "Port" + }, + "protocol": { + "string": "Protocol" + }, + "button_reset": { + "string": "Reset To Default" + }, + "button_connect": { + "string": "Connect To Host" + } + }, + "gap": { + "save": { + "string": "Save" + }, + "reset": { + "string": "Reset" + }, + "gap_limit_update_title": { + "string": "Address Gap Limit Updated" + }, + "gap_limit_update_description": { + "string": "Changes will take full effect after app restarts." + }, + "look_behind": { + "string": "Look Behind" + }, + "look_ahead": { + "string": "Look Ahead" + }, + "look_behind_change": { + "string": "Look Behind Change" + }, + "look_ahead_change": { + "string": "Look Ahead Change" + } + }, + "rgs": { + "server_url": { + "string": "Rapid-Gossip-Sync Server URL" + }, + "button_connect": { + "string": "Connect" + }, + "update_success_title": { + "string": "Rapid-Gossip-Sync Server Updated" + }, + "update_success_description": { + "string": "You may need to restart the app once or twice for this change to take effect." + } + }, + "wr": { + "error_wr": { + "string": "Web Relay Error" + }, + "error_url": { + "string": "Please specify a URL to connect to." + }, + "error_https": { + "string": "Not a valid HTTPS url." + }, + "error_healthcheck": { + "string": "Healthcheck Failed" + }, + "url_updated_title": { + "string": "Web Relay Updated" + }, + "url_updated_message": { + "string": "Successfully connected to {url}" + } + } } diff --git a/src/utils/i18n/locales/en/wallet.json b/src/utils/i18n/locales/en/wallet.json index 7fcef6444..24ba32990 100644 --- a/src/utils/i18n/locales/en/wallet.json +++ b/src/utils/i18n/locales/en/wallet.json @@ -115,6 +115,14 @@ "send_pending": { "string": "Payment Pending" }, + "send_quickpay": { + "nav_title": { + "string": "QuickPay" + }, + "title": { + "string": "Paying\ninvoice..." + } + }, "send_pending_note": { "string": "This payment is taking a bit longer than expected. You can continue using Bitkit." }, diff --git a/src/utils/i18n/locales/es_419/cards.json b/src/utils/i18n/locales/es_419/cards.json index 0780a5cd7..cb5c5f76a 100644 --- a/src/utils/i18n/locales/es_419/cards.json +++ b/src/utils/i18n/locales/es_419/cards.json @@ -12,7 +12,7 @@ "string": "Saldo de gastos" } }, - "fastpay": { + "quickpay": { "description": { "string": "Escanear y pagar" } diff --git a/src/utils/i18n/locales/fr/cards.json b/src/utils/i18n/locales/fr/cards.json index 5867498f7..81112beb7 100644 --- a/src/utils/i18n/locales/fr/cards.json +++ b/src/utils/i18n/locales/fr/cards.json @@ -12,7 +12,7 @@ "string": "Solde des dépenses" } }, - "fastpay": { + "quickpay": { "title": { "string": "Paiement rapide" }, diff --git a/src/utils/i18n/locales/it/cards.json b/src/utils/i18n/locales/it/cards.json index 5fad1bf04..d594ecdd8 100644 --- a/src/utils/i18n/locales/it/cards.json +++ b/src/utils/i18n/locales/it/cards.json @@ -12,7 +12,7 @@ "string": "Conto Spendibile" } }, - "fastpay": { + "quickpay": { "title": { "string": "Pagamento veloce" }, diff --git a/src/utils/i18n/locales/nl/cards.json b/src/utils/i18n/locales/nl/cards.json index ff2afdc23..90a737a21 100644 --- a/src/utils/i18n/locales/nl/cards.json +++ b/src/utils/i18n/locales/nl/cards.json @@ -12,9 +12,9 @@ "string": "Bestedingssaldo" } }, - "fastpay": { + "quickpay": { "title": { - "string": "Fastpay" + "string": "QuickPay" }, "description": { "string": "Scan en betaal" diff --git a/src/utils/i18n/locales/pt_BR/cards.json b/src/utils/i18n/locales/pt_BR/cards.json index da4325215..f3ea2bbbc 100644 --- a/src/utils/i18n/locales/pt_BR/cards.json +++ b/src/utils/i18n/locales/pt_BR/cards.json @@ -15,7 +15,7 @@ "string": "Saldo de Gastos" } }, - "fastpay": { + "quickpay": { "title": { "string": "Pagamentos rápidos" }, diff --git a/src/utils/i18n/locales/ru/cards.json b/src/utils/i18n/locales/ru/cards.json index ebbfe992a..8430bf2f6 100644 --- a/src/utils/i18n/locales/ru/cards.json +++ b/src/utils/i18n/locales/ru/cards.json @@ -12,7 +12,7 @@ "string": "Баланс Расходов" } }, - "fastpay": { + "quickpay": { "title": { "string": "Быстроплата" }, diff --git a/src/utils/scanner/scanner.ts b/src/utils/scanner/scanner.ts index b99313741..386152d13 100644 --- a/src/utils/scanner/scanner.ts +++ b/src/utils/scanner/scanner.ts @@ -22,7 +22,7 @@ import { getOnchainTransactionData, getTransactionInputValue, } from '../wallet/transactions'; -import { dispatch } from '../../store/helpers'; +import { dispatch, getSettingsStore } from '../../store/helpers'; import { showToast } from '../notifications'; import { resetSendTransaction, @@ -46,6 +46,7 @@ import { rootNavigation } from '../../navigation/root/RootNavigator'; import { findLnUrl, handleLnurlAuth, isLnurlAddress } from '../lnurl'; import i18n from '../i18n'; import { getBitcoinDisplayValues } from '../displayValues'; +import { fiatToBitcoinUnit } from '../conversion'; import { EQRDataType, paymentTypes, @@ -594,6 +595,7 @@ const handleData = async ({ return ok(''); } case EQRDataType.unified: { + const { enableQuickpay, quickpayAmount } = getSettingsStore(); const { address, amount, @@ -603,6 +605,22 @@ const handleData = async ({ preferredPaymentMethod, } = data; + const quickpayAmountSats = fiatToBitcoinUnit({ + amount: quickpayAmount, + currency: 'USD', + }); + + if (enableQuickpay && amount && amount < quickpayAmountSats) { + const screen = 'Quickpay'; + const params = { invoice: lightningInvoice, amount }; + // If BottomSheet is not open yet (MainScanner) + showBottomSheet('sendNavigation', { screen, ...params }); + // If BottomSheet is already open (SendScanner) + sendNavigation.navigate(screen, params); + + return ok(''); + } + // Reset existing transaction state and prepare for a new one. await resetSendTransaction(); await setupOnChainTransaction(); @@ -611,8 +629,7 @@ const handleData = async ({ const paymentMethod = preferredPaymentMethod ?? 'lightning'; dispatch(updateUi({ paymentMethod })); - const invoiceAmount = amount ?? 0; - const screen = invoiceAmount ? 'ReviewAndSend' : 'Amount'; + const screen = amount ? 'ReviewAndSend' : 'Amount'; // If BottomSheet is not open yet (MainScanner) showBottomSheet('sendNavigation', { screen }); // If BottomSheet is already open (SendScanner) @@ -651,6 +668,22 @@ const handleData = async ({ } case EQRDataType.lightning: { const { lightningInvoice, amount, slashTagsUrl } = data; + const { enableQuickpay, quickpayAmount } = getSettingsStore(); + const quickpayAmountSats = fiatToBitcoinUnit({ + amount: quickpayAmount, + currency: 'USD', + }); + + if (enableQuickpay && amount && amount < quickpayAmountSats) { + const screen = 'Quickpay'; + const params = { invoice: lightningInvoice, amount }; + // If BottomSheet is not open yet (MainScanner) + showBottomSheet('sendNavigation', { screen, ...params }); + // If BottomSheet is already open (SendScanner) + sendNavigation.navigate(screen, params); + + return ok(''); + } dispatch(updateUi({ paymentMethod: 'lightning' })); @@ -662,13 +695,7 @@ const handleData = async ({ sendNavigation.navigate(screen); updateSendTransaction({ - outputs: [ - { - address: '', - value: invoiceAmount, - index: 0, - }, - ], + outputs: [{ address: '', value: invoiceAmount, index: 0 }], lightningInvoice, slashTagsUrl, });