mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-06 18:59:24 +00:00
feat: add NFC key exchange
This commit is contained in:
parent
81008841cc
commit
c7c6d61985
6 changed files with 270 additions and 76 deletions
4
android/app/src/main/res/values/strings.xml
Normal file
4
android/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<resources>
|
||||||
|
<string name="servicedesc">Flutter NFC HCE Service</string>
|
||||||
|
<string name="aiddescription">NFC Host Card Emulation AID</string>
|
||||||
|
</resources>
|
13
android/app/src/main/res/xml/apduservice.xml
Normal file
13
android/app/src/main/res/xml/apduservice.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<host-apdu-service
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:description="@string/servicedesc"
|
||||||
|
android:requireDeviceScreenOn="false"
|
||||||
|
android:requireDeviceUnlock="false">
|
||||||
|
|
||||||
|
<aid-group
|
||||||
|
android:description="@string/aiddescription"
|
||||||
|
android:category="other" >
|
||||||
|
<aid-filter android:name="D2760000850101"/>
|
||||||
|
</aid-group>
|
||||||
|
</host-apdu-service>
|
|
@ -6,7 +6,9 @@ import 'package:barcode_scan2/platform_wrapper.dart';
|
||||||
import 'package:bip48/bip48.dart';
|
import 'package:bip48/bip48.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_nfc_hce/flutter_nfc_hce.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:nfc_manager/nfc_manager.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
import '../../../../themes/stack_colors.dart';
|
import '../../../../themes/stack_colors.dart';
|
||||||
|
@ -31,8 +33,10 @@ import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import '../../../widgets/desktop/primary_button.dart';
|
import '../../../widgets/desktop/primary_button.dart';
|
||||||
import '../../../widgets/desktop/qr_code_scanner_dialog.dart';
|
import '../../../widgets/desktop/qr_code_scanner_dialog.dart';
|
||||||
import '../../../widgets/desktop/secondary_button.dart';
|
import '../../../widgets/desktop/secondary_button.dart';
|
||||||
|
import '../../../widgets/icon_widgets/clipboard_icon.dart';
|
||||||
import '../../../widgets/icon_widgets/copy_icon.dart';
|
import '../../../widgets/icon_widgets/copy_icon.dart';
|
||||||
import '../../../widgets/icon_widgets/qrcode_icon.dart';
|
import '../../../widgets/icon_widgets/qrcode_icon.dart';
|
||||||
|
import '../../../widgets/icon_widgets/share_icon.dart';
|
||||||
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart';
|
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart';
|
||||||
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart';
|
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart';
|
||||||
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart';
|
import '../../add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart';
|
||||||
|
@ -98,8 +102,9 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
String _myXpub = "";
|
String _myXpub = "";
|
||||||
late final bool isDesktop;
|
late final bool isDesktop;
|
||||||
|
|
||||||
// bool _isNfcAvailable = false;
|
final _flutterNfcHcePlugin = FlutterNfcHce();
|
||||||
// String _nfcStatus = 'Checking NFC availability...';
|
bool _isNfcAvailable = false;
|
||||||
|
String _nfcStatus = 'Checking NFC availability...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -146,7 +151,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// _checkNfcAvailability();
|
_checkNfcAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -157,73 +162,6 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> _checkNfcAvailability() async {
|
|
||||||
// try {
|
|
||||||
// final availability = await NfcManager.instance.isAvailable();
|
|
||||||
// setState(() {
|
|
||||||
// _isNfcAvailable = availability;
|
|
||||||
// _nfcStatus = _isNfcAvailable
|
|
||||||
// ? 'NFC is available'
|
|
||||||
// : 'NFC is not available on this device';
|
|
||||||
// });
|
|
||||||
// } catch (e) {
|
|
||||||
// setState(() {
|
|
||||||
// _nfcStatus = 'Error checking NFC: $e';
|
|
||||||
// _isNfcAvailable = false;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<void> _startNfcSession() async {
|
|
||||||
// if (!_isNfcAvailable) return;
|
|
||||||
//
|
|
||||||
// setState(() => _nfcStatus = 'Ready to exchange information...');
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// await NfcManager.instance.startSession(
|
|
||||||
// onDiscovered: (tag) async {
|
|
||||||
// try {
|
|
||||||
// final ndef = Ndef.from(tag);
|
|
||||||
//
|
|
||||||
// if (ndef == null) {
|
|
||||||
// setState(() => _nfcStatus = 'Tag is not NDEF compatible');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final setupData = ref.watch(multisigSetupStateProvider);
|
|
||||||
//
|
|
||||||
// if (ndef.isWritable) {
|
|
||||||
// final message = NdefMessage([
|
|
||||||
// NdefRecord.createMime(
|
|
||||||
// 'application/x-multisig-setup',
|
|
||||||
// Uint8List.fromList(
|
|
||||||
// utf8.encode(jsonEncode(setupData.toJson()))),
|
|
||||||
// ),
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// await ndef.write(message);
|
|
||||||
// setState(
|
|
||||||
// () => _nfcStatus = 'Configuration shared successfully');
|
|
||||||
// } catch (e) {
|
|
||||||
// setState(
|
|
||||||
// () => _nfcStatus = 'Failed to share configuration: $e');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// await NfcManager.instance.stopSession();
|
|
||||||
// } catch (e) {
|
|
||||||
// setState(() => _nfcStatus = 'Error during NFC exchange: $e');
|
|
||||||
// await NfcManager.instance.stopSession();
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// } catch (e) {
|
|
||||||
// setState(() => _nfcStatus = 'Error: $e');
|
|
||||||
// await NfcManager.instance.stopSession();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Background(
|
return Background(
|
||||||
|
@ -302,6 +240,30 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
SecondaryButton(
|
||||||
|
width: 44,
|
||||||
|
buttonHeight: ButtonHeight.xl,
|
||||||
|
icon: ShareIcon(
|
||||||
|
// TODO: Replace with NFC icon.
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondary,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await _showNfcDialog(
|
||||||
|
title: 'Tap to share xPub',
|
||||||
|
onStartNfc: () =>
|
||||||
|
_startNfcSessionShareXpub(
|
||||||
|
_myXpub),
|
||||||
|
isWriting: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
width: 44,
|
width: 44,
|
||||||
|
@ -391,6 +353,29 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
SecondaryButton(
|
||||||
|
width: 44,
|
||||||
|
buttonHeight: ButtonHeight.xl,
|
||||||
|
icon: ShareIcon(
|
||||||
|
// TODO: Replace with NFC icon.
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondary,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await _showNfcDialog(
|
||||||
|
title: 'Tap to scan xPub',
|
||||||
|
onStartNfc: () =>
|
||||||
|
_startNfcSessionScanXpub(
|
||||||
|
i - 1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
width: 44,
|
width: 44,
|
||||||
|
@ -408,7 +393,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
width: 44,
|
width: 44,
|
||||||
buttonHeight: ButtonHeight.xl,
|
buttonHeight: ButtonHeight.xl,
|
||||||
icon: CopyIcon(
|
icon: ClipboardIcon(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
|
@ -505,7 +490,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
final info = WalletInfo.createNew(
|
final info = WalletInfo.createNew(
|
||||||
coin: parentWallet.cryptoCurrency,
|
coin: BIP48Bitcoin(parentWallet.cryptoCurrency.network),
|
||||||
name:
|
name:
|
||||||
'widget.walletName', // TODO [prio=high]: Add wallet name input field to multisig setup view and pass it to the coordinator view here.
|
'widget.walletName', // TODO [prio=high]: Add wallet name input field to multisig setup view and pass it to the coordinator view here.
|
||||||
restoreHeight: await parentWallet.chainHeight,
|
restoreHeight: await parentWallet.chainHeight,
|
||||||
|
@ -637,6 +622,174 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _checkNfcAvailability() async {
|
||||||
|
try {
|
||||||
|
final isSupported = await _flutterNfcHcePlugin.isNfcHceSupported();
|
||||||
|
setState(() {
|
||||||
|
_isNfcAvailable = isSupported;
|
||||||
|
_nfcStatus = _isNfcAvailable
|
||||||
|
? 'NFC HCE is available'
|
||||||
|
: 'NFC HCE is not available on this device';
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_nfcStatus = 'Error checking NFC HCE: $e';
|
||||||
|
_isNfcAvailable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startNfcSessionShareXpub(String xpub) async {
|
||||||
|
if (!_isNfcAvailable) {
|
||||||
|
setState(() => _nfcStatus = 'NFC is not available on this device');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final isEnabled = await _flutterNfcHcePlugin.isNfcEnabled();
|
||||||
|
if (!isEnabled) {
|
||||||
|
setState(() => _nfcStatus = 'Please enable NFC on your device');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _nfcStatus = 'Starting NFC sharing...');
|
||||||
|
|
||||||
|
final result = await _flutterNfcHcePlugin.startNfcHce(
|
||||||
|
xpub,
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
persistMessage: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == 'success') {
|
||||||
|
setState(() => _nfcStatus = 'Sharing xPub: hold devices together');
|
||||||
|
} else {
|
||||||
|
setState(() => _nfcStatus = 'Failed to start NFC sharing');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _nfcStatus = 'Error: ${e.toString()}');
|
||||||
|
await _stopNfcSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startNfcSessionScanXpub(int cosignerIndex) async {
|
||||||
|
if (!_isNfcAvailable) {
|
||||||
|
setState(() => _nfcStatus = 'NFC is not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() => _nfcStatus = 'Ready to scan xPub...');
|
||||||
|
|
||||||
|
await NfcManager.instance.startSession(
|
||||||
|
onDiscovered: (NfcTag tag) async {
|
||||||
|
try {
|
||||||
|
final ndef = Ndef.from(tag);
|
||||||
|
if (ndef == null) {
|
||||||
|
setState(() => _nfcStatus = 'Invalid NFC tag format');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final message = await ndef.read();
|
||||||
|
if (message.records.isEmpty) {
|
||||||
|
setState(() => _nfcStatus = 'No data found on tag');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final record = message.records.first;
|
||||||
|
String xpub;
|
||||||
|
|
||||||
|
if (record.typeNameFormat == NdefTypeNameFormat.nfcWellknown &&
|
||||||
|
record.type.length == 1 &&
|
||||||
|
record.type[0] == 'T'.codeUnitAt(0)) {
|
||||||
|
final payload = record.payload;
|
||||||
|
final languageCodeLength = payload[0] & 0x3F;
|
||||||
|
xpub = utf8.decode(payload.sublist(1 + languageCodeLength));
|
||||||
|
} else {
|
||||||
|
xpub = utf8.decode(record.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xpub.startsWith('xpub') && !xpub.startsWith('tpub')) {
|
||||||
|
setState(() => _nfcStatus = 'Invalid xPub format: $xpub');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
xpubControllers[cosignerIndex].text = xpub;
|
||||||
|
_nfcStatus = 'Successfully read xPub';
|
||||||
|
});
|
||||||
|
|
||||||
|
ref
|
||||||
|
.read(multisigCoordinatorStateProvider.notifier)
|
||||||
|
.addCosignerXpub(xpub);
|
||||||
|
|
||||||
|
await NfcManager.instance.stopSession();
|
||||||
|
|
||||||
|
// Delay to show success message before dismissing.
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _nfcStatus = 'Error reading tag: ${e.toString()}');
|
||||||
|
await NfcManager.instance.stopSession();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _nfcStatus = 'Error: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _stopNfcSession() async {
|
||||||
|
await _flutterNfcHcePlugin.stopNfcHce();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showNfcDialog({
|
||||||
|
required String title,
|
||||||
|
required Future<void> Function() onStartNfc,
|
||||||
|
bool isWriting = false,
|
||||||
|
}) async {
|
||||||
|
setState(() => _nfcStatus = 'Initializing NFC...');
|
||||||
|
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (ctx) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => onStartNfc());
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(_nfcStatus),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (!_nfcStatus.contains('Error') &&
|
||||||
|
!_nfcStatus.contains('Success'))
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (_isNfcAvailable) {
|
||||||
|
if (isWriting) {
|
||||||
|
await _flutterNfcHcePlugin.stopNfcHce();
|
||||||
|
} else {
|
||||||
|
await NfcManager.instance.stopSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx.mounted) Navigator.of(ctx).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> scanQr(int cosignerIndex) async {
|
Future<void> scanQr(int cosignerIndex) async {
|
||||||
try {
|
try {
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
@ -655,7 +808,7 @@ class _MultisigSetupViewState extends ConsumerState<MultisigCoordinatorView> {
|
||||||
.read(multisigCoordinatorStateProvider.notifier)
|
.read(multisigCoordinatorStateProvider.notifier)
|
||||||
.addCosignerXpub(qrResult.rawContent);
|
.addCosignerXpub(qrResult.rawContent);
|
||||||
|
|
||||||
setState(() {}); // Trigger rebuild to update button state.
|
setState(() {});
|
||||||
} else {
|
} else {
|
||||||
// Platform.isLinux, Platform.isWindows, or Platform.isMacOS.
|
// Platform.isLinux, Platform.isWindows, or Platform.isMacOS.
|
||||||
final qrResult = await showDialog<String>(
|
final qrResult = await showDialog<String>(
|
||||||
|
|
12
pubspec.lock
12
pubspec.lock
|
@ -125,10 +125,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: bip48
|
name: bip48
|
||||||
sha256: c31fa9a3fc1d755048c49317aa33b4cc8a396af387ffa1561010a981e4c9e8ca
|
sha256: "0ab248477a7a290a938d9fc6aa118742d2a7ba87eb5a8046348047ed1d55d84d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.3"
|
version: "0.0.4"
|
||||||
bitbox:
|
bitbox:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -869,6 +869,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.7"
|
version: "2.3.7"
|
||||||
|
flutter_nfc_hce:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_nfc_hce
|
||||||
|
sha256: "4a8b12f87db6581e81653d5b6bbb8afa980c6171ab378e315a3f39fcde0d696c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.8"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
android:name="android.permission.READ_INTERNAL_STORAGE"/>
|
android:name="android.permission.READ_INTERNAL_STORAGE"/>
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||||
|
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<application
|
<application
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:label="PlaceHolderName"
|
android:label="PlaceHolderName"
|
||||||
|
@ -72,6 +76,17 @@
|
||||||
<!-- android:name="android.support.FILE_PROVIDER_PATHS"-->
|
<!-- android:name="android.support.FILE_PROVIDER_PATHS"-->
|
||||||
<!-- android:resource="@xml/provider_paths" />-->
|
<!-- android:resource="@xml/provider_paths" />-->
|
||||||
<!-- </provider>-->
|
<!-- </provider>-->
|
||||||
|
<service android:name="com.novice.flutter_nfc_hce.KHostApduService"
|
||||||
|
android:exported="true"
|
||||||
|
android:enabled="true"
|
||||||
|
android:permission="android.permission.BIND_NFC_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.nfc.cardemulation.host_apdu_service"
|
||||||
|
android:resource="@xml/apduservice"/>
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|
|
@ -204,8 +204,9 @@ dependencies:
|
||||||
cbor: ^6.3.3
|
cbor: ^6.3.3
|
||||||
cs_monero: 1.0.0-pre.1
|
cs_monero: 1.0.0-pre.1
|
||||||
cs_monero_flutter_libs: 1.0.0-pre.0
|
cs_monero_flutter_libs: 1.0.0-pre.0
|
||||||
nfc_manager: ^3.5.0
|
|
||||||
bip48: ^0.0.4
|
bip48: ^0.0.4
|
||||||
|
flutter_nfc_hce: ^0.1.8 # For writing.
|
||||||
|
nfc_manager: ^3.5.0 # For reading.
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue