check NFC support and enabled state before NFC operations

This commit is contained in:
myxmaster
2026-04-01 11:21:35 +02:00
parent ddcd2a54ec
commit 691b842e1d
9 changed files with 145 additions and 41 deletions
+35 -5
View File
@@ -1,6 +1,7 @@
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { inject, observer } from 'mobx-react';
import NfcManager from 'react-native-nfc-manager';
import Button from '../Button';
import ModalBox from '../ModalBox';
@@ -24,7 +25,11 @@ export default class AndroidNfcModal extends React.Component<
> {
render() {
const { ModalStore } = this.props;
const { showAndroidNfcModal, toggleAndroidNfcModal } = ModalStore;
const {
showAndroidNfcModal,
toggleAndroidNfcModal,
androidNfcModalIsNfcEnabled
} = ModalStore;
return (
<ModalBox
@@ -67,9 +72,15 @@ export default class AndroidNfcModal extends React.Component<
marginBottom: 30
}}
>
{localeString('components.AndroidNfcModal.ready')}
{androidNfcModalIsNfcEnabled
? localeString(
'components.AndroidNfcModal.ready'
)
: localeString(
'components.AndroidNfcModal.nfcDisabled'
)}
</Text>
<NFC />
{androidNfcModalIsNfcEnabled && <NFC />}
<Text
style={{
fontSize: 18,
@@ -79,14 +90,33 @@ export default class AndroidNfcModal extends React.Component<
textAlign: 'center'
}}
>
{localeString('components.AndroidNfcModal.hold')}
{androidNfcModalIsNfcEnabled
? localeString(
'components.AndroidNfcModal.hold'
)
: localeString(
'components.AndroidNfcModal.enableInstructions'
)}
</Text>
<View style={styles.buttons}>
{!androidNfcModalIsNfcEnabled && (
<View style={styles.button}>
<Button
title={localeString(
'general.openNfcSettings'
)}
onPress={() => {
toggleAndroidNfcModal(false);
NfcManager.goToNfcSetting();
}}
quaternary
></Button>
</View>
)}
<View style={styles.button}>
<Button
title={localeString('general.cancel')}
onPress={() => toggleAndroidNfcModal(false)}
quaternary
></Button>
</View>
</View>
+14 -1
View File
@@ -1,5 +1,7 @@
import * as React from 'react';
import { Platform, TouchableOpacity } from 'react-native';
import { inject, observer } from 'mobx-react';
import { checkNfcEnabled } from '../utils/NFCUtils';
import HCESession, { NFCContentType, NFCTagType4 } from 'react-native-hce';
@@ -7,6 +9,8 @@ import NfcIcon from '../assets/images/SVG/NFC-alt.svg';
import Button from './../components/Button';
import ModalStore from '../stores/ModalStore';
import { localeString } from '../utils/LocaleUtils';
import { themeColor } from '../utils/ThemeUtils';
@@ -15,12 +19,15 @@ interface NFCButtonProps {
icon?: any;
noUppercase?: boolean;
iconOnly?: boolean;
ModalStore?: ModalStore;
}
interface NFCButtonState {
nfcBroadcast: boolean;
}
@inject('ModalStore')
@observer
export default class NFCButton extends React.Component<
NFCButtonProps,
NFCButtonState
@@ -43,7 +50,13 @@ export default class NFCButton extends React.Component<
this.stopSimulation();
}
}
toggleNfc = () => {
toggleNfc = async () => {
const { ModalStore } = this.props;
if (!this.state.nfcBroadcast) {
if (!(await checkNfcEnabled(ModalStore!))) return;
}
if (this.state.nfcBroadcast) {
this.stopSimulation();
} else {
+3
View File
@@ -6,6 +6,7 @@
"general.request": "Request",
"general.scan": "Scan",
"general.enableNfc": "Enable NFC",
"general.openNfcSettings": "Open NFC Settings",
"general.receiveNfc": "Receive via NFC",
"general.payNfc": "Pay via NFC",
"general.confirm": "Confirm",
@@ -288,6 +289,8 @@
"components.ExternalLinkModal.copied": "Copied!",
"components.AndroidNfcModal.ready": "Ready to scan",
"components.AndroidNfcModal.hold": "Hold your Android phone near an NFC tag to read it",
"components.AndroidNfcModal.nfcDisabled": "NFC is disabled",
"components.AndroidNfcModal.enableInstructions": "Please enable NFC in your device settings to use this feature",
"components.QRCodeScanner.chooseFromGallery": "Choose from gallery",
"components.QRCodeScanner.flashOn": "Flash on",
"components.QRCodeScanner.flashOff": "Flash off",
+8 -1
View File
@@ -10,6 +10,7 @@ import {
export default class ModalStore {
@observable public showExternalLinkModal: boolean = false;
@observable public showAndroidNfcModal: boolean = false;
@observable public androidNfcModalIsNfcEnabled: boolean = false;
@observable public showInfoModal: boolean = false;
@observable public showAlertModal: boolean = false;
@observable public showShareModal: boolean = false;
@@ -181,8 +182,14 @@ export default class ModalStore {
/* Android NFC Modal */
@action
public toggleAndroidNfcModal = (status: boolean) => {
public toggleAndroidNfcModal = (
status: boolean,
nfcEnabled: boolean = true
) => {
this.showAndroidNfcModal = status;
if (status) {
this.androidNfcModalIsNfcEnabled = nfcEnabled;
}
};
@action
+20
View File
@@ -1,3 +1,23 @@
import NfcManager from 'react-native-nfc-manager';
import ModalStore from '../stores/ModalStore';
/**
* Checks whether NFC is enabled on the device.
* If not, shows the AndroidNfcModal with the disabled state and returns false.
* On iOS, NfcManager.isEnabled() always returns true, so this function
* only ever returns false on Android.
*/
export async function checkNfcEnabled(
modalStore: ModalStore
): Promise<boolean> {
const nfcEnabled = await NfcManager.isEnabled();
if (!nfcEnabled) {
modalStore.toggleAndroidNfcModal(true, false);
return false;
}
return true;
}
class NFCUtils {
nfcUtf8ArrayToStr = (data: any) => {
const extraByteMap = [1, 1, 1, 1, 2, 2, 3, 0];
+4 -1
View File
@@ -56,7 +56,7 @@ import UnitsStore from '../../stores/UnitsStore';
import CashuInvoice from '../../models/CashuInvoice';
import { localeString } from '../../utils/LocaleUtils';
import NFCUtils from '../../utils/NFCUtils';
import NFCUtils, { checkNfcEnabled } from '../../utils/NFCUtils';
import { themeColor } from '../../utils/ThemeUtils';
import { getAmountFromSats } from '../../utils/AmountUtils';
@@ -269,6 +269,9 @@ export default class ReceiveEcash extends React.Component<
enableNfc = async () => {
const { ModalStore } = this.props;
if (!(await checkNfcEnabled(ModalStore))) return;
this.disableNfc();
await NfcManager.start().catch((e) => console.warn(e.message));
+31 -18
View File
@@ -30,7 +30,7 @@ import TextInput from '../components/TextInput';
import UTXOPicker from '../components/UTXOPicker';
import handleAnything from '../utils/handleAnything';
import NFCUtils from '../utils/NFCUtils';
import NFCUtils, { checkNfcEnabled } from '../utils/NFCUtils';
import NodeUriUtils from '../utils/NodeUriUtils';
import BackendUtils from '../utils/BackendUtils';
import ValidationUtils from '../utils/ValidationUtils';
@@ -91,6 +91,7 @@ interface OpenChannelState {
additionalChannels: Array<AdditionalChannel>;
isNodePubkeyValid: boolean;
isNodeHostValid: boolean;
nfcSupported: boolean;
}
@inject(
@@ -132,7 +133,8 @@ export default class OpenChannel extends React.Component<
account: 'default',
additionalChannels: [],
isNodePubkeyValid: true,
isNodeHostValid: true
isNodeHostValid: true,
nfcSupported: false
};
}
@@ -173,6 +175,9 @@ export default class OpenChannel extends React.Component<
if (this.props.ChannelsStore.channelsView === ChannelsView.Peers) {
this.setState({ connectPeerOnly: true });
}
const nfcSupported = await NfcManager.isSupported();
this.setState({ nfcSupported });
}
disableNfc = () => {
@@ -182,6 +187,9 @@ export default class OpenChannel extends React.Component<
enableNfc = async () => {
const { ModalStore } = this.props;
if (!(await checkNfcEnabled(ModalStore))) return;
this.disableNfc();
await NfcManager.start();
@@ -336,7 +344,8 @@ export default class OpenChannel extends React.Component<
advancedSettingsToggle,
additionalChannels,
isNodePubkeyValid,
isNodeHostValid
isNodeHostValid,
nfcSupported
} = this.state;
const { implementation } = SettingsStore;
@@ -1361,21 +1370,25 @@ export default class OpenChannel extends React.Component<
/>
</View>
<View style={styles.button}>
<Button
title={localeString('general.enableNfc')}
icon={
<NfcIcon
stroke={themeColor('highlight')}
width={25}
height={25}
style={{ marginRight: 10 }}
/>
}
onPress={() => this.enableNfc()}
secondary
/>
</View>
{nfcSupported && (
<View style={styles.button}>
<Button
title={localeString(
'general.enableNfc'
)}
icon={
<NfcIcon
stroke={themeColor('highlight')}
width={25}
height={25}
style={{ marginRight: 10 }}
/>
}
onPress={() => this.enableNfc()}
secondary
/>
</View>
)}
</View>
</ScrollView>
)}
+5 -1
View File
@@ -79,7 +79,7 @@ import BalanceStore from '../stores/BalanceStore';
import { localeString } from '../utils/LocaleUtils';
import BackendUtils from '../utils/BackendUtils';
import Base64Utils from '../utils/Base64Utils';
import NFCUtils from '../utils/NFCUtils';
import NFCUtils, { checkNfcEnabled } from '../utils/NFCUtils';
import { themeColor } from '../utils/ThemeUtils';
import { SATS_PER_BTC } from '../utils/UnitsUtils';
import { getAmountFromSats } from '../utils/AmountUtils';
@@ -193,6 +193,7 @@ const LOCKED_EXPIRY_SECONDS = '3600';
@inject(
'ChannelsStore',
'InvoicesStore',
'ModalStore',
'SettingsStore',
'UnitsStore',
'PosStore',
@@ -680,6 +681,9 @@ export default class Receive extends React.Component<
enableNfc = async () => {
const { ModalStore } = this.props;
if (!(await checkNfcEnabled(ModalStore))) return;
this.disableNfc();
await NfcManager.start().catch((e) => console.warn(e.message));
+25 -14
View File
@@ -53,7 +53,7 @@ import UTXOPicker from '../components/UTXOPicker';
import BackendUtils from '../utils/BackendUtils';
import { errorToUserFriendly } from '../utils/ErrorUtils';
import NFCUtils from '../utils/NFCUtils';
import NFCUtils, { checkNfcEnabled } from '../utils/NFCUtils';
import { localeString } from '../utils/LocaleUtils';
import { themeColor } from '../utils/ThemeUtils';
import { getUnformattedAmount, getAmountFromSats } from '../utils/AmountUtils';
@@ -121,6 +121,7 @@ interface SendState {
additionalOutputs: Array<AdditionalOutput>;
fundMax: boolean;
validAmountToSwap: boolean;
nfcSupported: boolean;
}
@inject(
@@ -191,7 +192,8 @@ export default class Send extends React.Component<SendProps, SendState> {
account: 'default',
additionalOutputs: [],
fundMax: false,
validAmountToSwap: false
validAmountToSwap: false,
nfcSupported: false
};
}
@@ -333,6 +335,9 @@ export default class Send extends React.Component<SendProps, SendState> {
'hardwareBackPress',
this.backPressed.bind(this)
);
const nfcSupported = await NfcManager.isSupported();
this.setState({ nfcSupported });
}
componentWillUnmount(): void {
@@ -371,6 +376,9 @@ export default class Send extends React.Component<SendProps, SendState> {
enableNfc = async () => {
const { ModalStore } = this.props;
if (!(await checkNfcEnabled(ModalStore))) return;
this.disableNfc();
await NfcManager.start().catch((e) => console.warn(e.message));
@@ -746,7 +754,8 @@ export default class Send extends React.Component<SendProps, SendState> {
fundMax,
account,
validAmountToSwap,
utxos
utxos,
nfcSupported
} = this.state;
const {
confirmedBlockchainBalance,
@@ -846,17 +855,19 @@ export default class Send extends React.Component<SendProps, SendState> {
</TouchableOpacity>
</View>
)}
<View style={{ marginRight: 15 }}>
<TouchableOpacity
onPress={() => this.enableNfc()}
>
<NFC
stroke={themeColor('text')}
width={30}
height={30}
/>
</TouchableOpacity>
</View>
{nfcSupported && (
<View style={{ marginRight: 15 }}>
<TouchableOpacity
onPress={() => this.enableNfc()}
>
<NFC
stroke={themeColor('text')}
width={30}
height={30}
/>
</TouchableOpacity>
</View>
)}
<View>
<TouchableOpacity
onPress={() =>