fix: clean up nfc-related code

This commit is contained in:
sneurlax 2024-12-30 18:09:28 -06:00
parent c7c6d61985
commit 3c871e7d9d

View file

@ -227,7 +227,8 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
Expanded( Expanded(
child: TextField( child: TextField(
controller: TextEditingController( controller: TextEditingController(
text: _myXpub), text: _myXpub,
),
enabled: false, enabled: false,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Loading xPub...", hintText: "Loading xPub...",
@ -240,30 +241,27 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
), ),
), ),
), ),
if (Platform.isAndroid) if (Platform.isAndroid) ...[
const SizedBox(width: 8), const SizedBox(width: 8),
if (Platform.isAndroid)
SecondaryButton( SecondaryButton(
width: 44, width: 44,
buttonHeight: ButtonHeight.xl, buttonHeight: ButtonHeight.xl,
icon: ShareIcon( icon: ShareIcon(
// TODO: Replace with NFC icon.
width: 20, width: 20,
height: 20, height: 20,
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.buttonTextSecondary, .buttonTextSecondary,
), ),
onPressed: () async { onPressed: () => _showNfcDialog(
await _showNfcDialog( title: 'Tap to share xPub',
title: 'Tap to share xPub', onStartNfc: () =>
onStartNfc: () => _startNfcSessionShareXpub(
_startNfcSessionShareXpub( _myXpub),
_myXpub), isWriting: true,
isWriting: true, ),
);
},
), ),
],
const SizedBox(width: 8), const SizedBox(width: 8),
SecondaryButton( SecondaryButton(
width: 44, width: 44,
@ -344,18 +342,17 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
if (value.isNotEmpty) { if (value.isNotEmpty) {
ref ref
.read( .read(
multisigCoordinatorStateProvider multisigCoordinatorStateProvider
.notifier) .notifier,
)
.addCosignerXpub(value); .addCosignerXpub(value);
} }
setState( setState(() {});
() {}); // Trigger rebuild to update button state.
}, },
), ),
), ),
if (Platform.isAndroid) if (Platform.isAndroid) ...[
const SizedBox(width: 8), const SizedBox(width: 8),
if (Platform.isAndroid)
SecondaryButton( SecondaryButton(
width: 44, width: 44,
buttonHeight: ButtonHeight.xl, buttonHeight: ButtonHeight.xl,
@ -367,15 +364,13 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
.extension<StackColors>()! .extension<StackColors>()!
.buttonTextSecondary, .buttonTextSecondary,
), ),
onPressed: () async { onPressed: () => _showNfcDialog(
await _showNfcDialog( title: 'Tap to scan xPub',
title: 'Tap to scan xPub', onStartNfc: () =>
onStartNfc: () => _startNfcSessionScanXpub(i - 1),
_startNfcSessionScanXpub( ),
i - 1),
);
},
), ),
],
const SizedBox(width: 8), const SizedBox(width: 8),
SecondaryButton( SecondaryButton(
width: 44, width: 44,
@ -387,7 +382,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
.extension<StackColors>()! .extension<StackColors>()!
.buttonTextSecondary, .buttonTextSecondary,
), ),
onPressed: () => {scanQr(i - 1)}, onPressed: () => scanQr(i - 1),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
SecondaryButton( SecondaryButton(
@ -402,17 +397,18 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
), ),
onPressed: () async { onPressed: () async {
final data = await Clipboard.getData( final data = await Clipboard.getData(
'text/plain'); 'text/plain',
);
if (data?.text != null) { if (data?.text != null) {
xpubControllers[i - 1].text = xpubControllers[i - 1].text =
data!.text!; data!.text!;
ref ref
.read( .read(
multisigCoordinatorStateProvider multisigCoordinatorStateProvider
.notifier) .notifier,
)
.addCosignerXpub(data.text!); .addCosignerXpub(data.text!);
setState( setState(() {});
() {}); // Trigger rebuild to update button state.
} }
}, },
), ),
@ -423,11 +419,11 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
), ),
const Spacer(), const Spacer(),
PrimaryButton( PrimaryButton(
label: "Create multisignature account", label: "Create multisignature account",
enabled: xpubControllers.every( enabled: xpubControllers.every(
(controller) => controller.text.isNotEmpty), (controller) => controller.text.isNotEmpty,
),
onPressed: () async { onPressed: () async {
final _newWallet = _multisigWallet; final _newWallet = _multisigWallet;
@ -476,8 +472,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
final parentWallet = final parentWallet =
await (ref.read(pWallets).getWallet(widget.walletId) as Bip39HDWallet); await (ref.read(pWallets).getWallet(widget.walletId) as Bip39HDWallet);
String? otherDataJsonString; final otherDataJsonString = jsonEncode({
otherDataJsonString = jsonEncode({
'threshold': widget.threshold, 'threshold': widget.threshold,
'participants': widget.participants, 'participants': widget.participants,
'coinType': 'coinType':
@ -526,14 +521,10 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
var node = ref var node = ref
.read(nodeServiceChangeNotifierProvider) .read(nodeServiceChangeNotifierProvider)
.getPrimaryNodeFor(currency: parentWallet.cryptoCurrency); .getPrimaryNodeFor(currency: parentWallet.cryptoCurrency);
node ??= parentWallet.cryptoCurrency.defaultNode;
if (node == null) { await ref
node = parentWallet.cryptoCurrency.defaultNode; .read(nodeServiceChangeNotifierProvider)
await ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor( .setPrimaryNodeFor(coin: parentWallet.cryptoCurrency, node: node);
coin: parentWallet.cryptoCurrency,
node: node,
);
}
final txTracker = TransactionNotificationTracker(walletId: info.walletId); final txTracker = TransactionNotificationTracker(walletId: info.walletId);
@ -563,9 +554,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
if (mounted) { if (mounted) {
if (isDesktop) { if (isDesktop) {
Navigator.of(context).popUntil( Navigator.of(context).popUntil(
ModalRoute.withName( ModalRoute.withName(DesktopHomeView.routeName),
DesktopHomeView.routeName,
),
); );
} else { } else {
unawaited( unawaited(
@ -640,104 +629,107 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
} }
Future<void> _startNfcSessionShareXpub(String xpub) async { Future<void> _startNfcSessionShareXpub(String xpub) async {
setState(() => _nfcStatus = 'Starting NFC sharing...');
if (!_isNfcAvailable) { if (!_isNfcAvailable) {
setState(() => _nfcStatus = 'NFC is not available on this device'); setState(() => _nfcStatus = 'NFC not available on this device.');
return; return;
} }
try { try {
final isEnabled = await _flutterNfcHcePlugin.isNfcEnabled(); final isEnabled = await _flutterNfcHcePlugin.isNfcEnabled();
if (!isEnabled) { if (!isEnabled) {
setState(() => _nfcStatus = 'Please enable NFC on your device'); setState(
() => _nfcStatus = 'Please enable NFC in your device settings.');
return; return;
} }
setState(() => _nfcStatus = 'Starting NFC sharing...');
final result = await _flutterNfcHcePlugin.startNfcHce( final result = await _flutterNfcHcePlugin.startNfcHce(
xpub, xpub,
mimeType: 'text/plain', mimeType: 'text/plain', // We want text record
persistMessage: false, persistMessage: false,
); );
if (result == 'success') { if (result == 'success') {
setState(() => _nfcStatus = 'Sharing xPub: hold devices together'); setState(() => _nfcStatus = 'Sharing xPub! Hold devices together.');
} else { } else {
setState(() => _nfcStatus = 'Failed to start NFC sharing'); setState(() => _nfcStatus = 'Failed to start NFC sharing.');
} }
} catch (e) { } catch (e) {
setState(() => _nfcStatus = 'Error: ${e.toString()}'); setState(() => _nfcStatus = 'Error: $e');
await _stopNfcSession(); await _stopNfcSession();
} }
} }
Future<void> _startNfcSessionScanXpub(int cosignerIndex) async { Future<void> _startNfcSessionScanXpub(int cosignerIndex) async {
setState(() => _nfcStatus = 'Starting NFC reading...');
if (!_isNfcAvailable) { if (!_isNfcAvailable) {
setState(() => _nfcStatus = 'NFC is not available'); setState(() => _nfcStatus = 'NFC not available.');
return; return;
} }
try { try {
setState(() => _nfcStatus = 'Ready to scan xPub...');
await NfcManager.instance.startSession( await NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async { onDiscovered: (NfcTag tag) async {
try { try {
final ndef = Ndef.from(tag); final ndef = Ndef.from(tag);
if (ndef == null) { if (ndef == null) {
setState(() => _nfcStatus = 'Invalid NFC tag format'); setState(() => _nfcStatus = 'Invalid tag format.');
return; return;
} }
final message = await ndef.read(); final message = await ndef.read();
if (message.records.isEmpty) { if (message.records.isEmpty) {
setState(() => _nfcStatus = 'No data found on tag'); setState(() => _nfcStatus = 'No data on tag.');
return; return;
} }
// Parse xPub from the first record.
final record = message.records.first; final record = message.records.first;
String xpub; String xpub;
if (record.typeNameFormat == NdefTypeNameFormat.nfcWellknown && if (record.typeNameFormat == NdefTypeNameFormat.nfcWellknown &&
record.type.length == 1 && record.type.length == 1 &&
record.type[0] == 'T'.codeUnitAt(0)) { record.type[0] == 'T'.codeUnitAt(0)) {
final payload = record.payload; final payload = record.payload;
final languageCodeLength = payload[0] & 0x3F; final langLen = payload[0] & 0x3F;
xpub = utf8.decode(payload.sublist(1 + languageCodeLength)); xpub = utf8.decode(payload.sublist(1 + langLen));
} else { } else {
xpub = utf8.decode(record.payload); xpub = utf8.decode(record.payload);
} }
if (!xpub.startsWith('xpub') && !xpub.startsWith('tpub')) { if (!xpub.startsWith(
setState(() => _nfcStatus = 'Invalid xPub format: $xpub'); 'xpub') /* && !xpub.startsWith('tpub') and so on. */) {
setState(() => _nfcStatus = 'Invalid xPub: $xpub');
return; return;
} }
// Success: fill field, show success text.
setState(() { setState(() {
xpubControllers[cosignerIndex].text = xpub; xpubControllers[cosignerIndex].text = xpub;
_nfcStatus = 'Successfully read xPub'; _nfcStatus = 'Successfully read xPub';
}); });
ref ref.read(multisigCoordinatorStateProvider.notifier).addCosignerXpub(
.read(multisigCoordinatorStateProvider.notifier) xpub,
.addCosignerXpub(xpub); );
await NfcManager.instance.stopSession(); await NfcManager.instance.stopSession();
// Delay to show success message before dismissing. // Delay just to show message.
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
if (context.mounted) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} catch (e) { } catch (e) {
setState(() => _nfcStatus = 'Error reading tag: ${e.toString()}'); setState(() => _nfcStatus = 'Error reading tag: $e');
await NfcManager.instance.stopSession(); await NfcManager.instance.stopSession();
} }
}, },
); );
} catch (e) { } catch (e) {
setState(() => _nfcStatus = 'Error: ${e.toString()}'); setState(() => _nfcStatus = 'Error: $e');
} }
} }
@ -766,7 +758,8 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
Text(_nfcStatus), Text(_nfcStatus),
const SizedBox(height: 16), const SizedBox(height: 16),
if (!_nfcStatus.contains('Error') && if (!_nfcStatus.contains('Error') &&
!_nfcStatus.contains('Success')) !_nfcStatus.contains('Success') &&
!_nfcStatus.contains('hold devices together'))
const CircularProgressIndicator(), const CircularProgressIndicator(),
], ],
), ),
@ -795,19 +788,14 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
if (Platform.isAndroid || Platform.isIOS) { if (Platform.isAndroid || Platform.isIOS) {
if (FocusScope.of(context).hasFocus) { if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
await Future<void>.delayed( await Future<void>.delayed(const Duration(milliseconds: 75));
const Duration(milliseconds: 75),
);
} }
final qrResult = await BarcodeScanner.scan(); final qrResult = await BarcodeScanner.scan();
xpubControllers[cosignerIndex].text = qrResult.rawContent; xpubControllers[cosignerIndex].text = qrResult.rawContent;
ref.read(multisigCoordinatorStateProvider.notifier).addCosignerXpub(
ref qrResult.rawContent,
.read(multisigCoordinatorStateProvider.notifier) );
.addCosignerXpub(qrResult.rawContent);
setState(() {}); setState(() {});
} else { } else {
// Platform.isLinux, Platform.isWindows, or Platform.isMacOS. // Platform.isLinux, Platform.isWindows, or Platform.isMacOS.
@ -815,25 +803,19 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
context: context, context: context,
builder: (context) => const QrCodeScannerDialog(), builder: (context) => const QrCodeScannerDialog(),
); );
if (qrResult == null) { if (qrResult == null) {
Logging.instance.log( Logging.instance.log("QR scanning cancelled", level: LogLevel.Info);
"QR scanning cancelled",
level: LogLevel.Info,
);
} else { } else {
xpubControllers[cosignerIndex].text = qrResult; xpubControllers[cosignerIndex].text = qrResult;
ref ref
.read(multisigCoordinatorStateProvider.notifier) .read(multisigCoordinatorStateProvider.notifier)
.addCosignerXpub(qrResult); .addCosignerXpub(qrResult);
setState(() {});
setState(() {}); // Trigger rebuild to update button state.
} }
} }
} on PlatformException catch (e, s) { } on PlatformException catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Failed to get camera permissions while trying to scan qr code: $e\n$s", "Failed to get camera permissions while scanning QR: $e\n$s",
level: LogLevel.Warning, level: LogLevel.Warning,
); );
} }