From 2168badf863d5ecef12e27d9a62e4d7f898005d2 Mon Sep 17 00:00:00 2001 From: vjrj <vjrj@comunes.org> Date: Sun, 2 Jul 2023 12:35:50 +0200 Subject: [PATCH] Added NFC support (just testing) --- android/app/src/main/AndroidManifest.xml | 1 + assets/translations/en.json | 3 +- assets/translations/es.json | 3 +- lib/ui/nfc_helper.dart | 56 ++++ .../first_screen/pay_contact_search_page.dart | 249 ++++++++++-------- .../second_screen/card_terminal_screen.dart | 249 ++++++++++-------- .../second_screen/card_terminal_status.dart | 44 +++- pubspec.lock | 16 ++ pubspec.yaml | 4 +- test/g1_test.dart | 2 +- 10 files changed, 389 insertions(+), 238 deletions(-) create mode 100644 lib/ui/nfc_helper.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5e3c6a4d..c39b04d9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> diff --git a/assets/translations/en.json b/assets/translations/en.json index 0680d921..469230db 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -196,5 +196,6 @@ "share_export_title": "Share your wallet", "share_export_desc": "Your wallet has been exported locally. Would you like to additionally share it with yourself via email/chat/etc. for safekeeping", "share_export_subject": "My Äž1nkgo Wallet", - "share_export_button": "SHARE" + "share_export_button": "SHARE", + "pay_with_nfc_tooltip": "To receive a payment, simply bring this device close to the other wallet with NFC activated." } diff --git a/assets/translations/es.json b/assets/translations/es.json index 9b6fc210..aa83be37 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -198,5 +198,6 @@ "share_export_title": "Comporte tu monedero", "share_export_desc": "Tu monedero ha sido exportado localmente. ¿Te gustarÃa compartirlo adicionalmente contigo mismo vÃa email/chat/etc. para su resguardo?", "share_export_subject": "Mi monedero Äž1nkgo", - "share_export_button": "COMPARTIR" + "share_export_button": "COMPARTIR", + "pay_with_nfc_tooltip": "Para recibir un pago, simplemente acerca este dispositivo al otro monedero con NFC activado" } diff --git a/lib/ui/nfc_helper.dart b/lib/ui/nfc_helper.dart new file mode 100644 index 00000000..ad580807 --- /dev/null +++ b/lib/ui/nfc_helper.dart @@ -0,0 +1,56 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; +import 'package:ndef/ndef.dart' as ndef; +import 'package:ndef/record.dart'; +import 'package:ndef/record/uri.dart'; + +import 'logger.dart'; + +bool hasNft(AsyncSnapshot<NFCAvailability> snapshot) => + !kIsWeb && snapshot.hasData && snapshot.data == NFCAvailability.available; + +Future<void> writeNfcUrl(String url) async { + // timeout only works on Android, whereas the following two messages are only for iOS + final NFCTag tag = await FlutterNfcKit.poll( + timeout: const Duration(seconds: 10), + iosMultipleTagMessage: 'Multiple tags found!', + iosAlertMessage: 'Scan your tag', + ); + final bool? ndefAvailable = tag.ndefAvailable; + final bool? ndefWritable = tag.ndefWritable; + + if ((ndefAvailable == null || ndefWritable == null) && + (ndefAvailable != null && !ndefAvailable) || + (ndefWritable != null && !ndefWritable)) { + logger('Tag does not have NDEF capability or is not writable'); + return; + } + + try { + // Write a NDEF record with the URL to the tag + await FlutterNfcKit.writeNDEFRecords( + <NDEFRecord>[ndef.UriRecord.fromString(url)]); + + // iOS only: show an alert message + await FlutterNfcKit.finish(iosAlertMessage: 'Success'); + } catch (e) { + logger('Error while writing to tag: $e'); + await FlutterNfcKit.finish(iosErrorMessage: 'Failed'); + } +} + +Future<String?> readNfcUrl() async { + final NFCTag tag = + await FlutterNfcKit.poll(timeout: const Duration(seconds: 10)); + final bool? ndefAvailable = tag.ndefAvailable; + if (ndefAvailable != null && ndefAvailable) { + final List<NDEFRecord> records = await FlutterNfcKit.readNDEFRecords(); + for (final NDEFRecord record in records) { + if (record is UriRecord) { + return record.uri.toString(); + } + } + } + return null; +} diff --git a/lib/ui/widgets/first_screen/pay_contact_search_page.dart b/lib/ui/widgets/first_screen/pay_contact_search_page.dart index 97e445f7..0f9f35e1 100644 --- a/lib/ui/widgets/first_screen/pay_contact_search_page.dart +++ b/lib/ui/widgets/first_screen/pay_contact_search_page.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:http/http.dart'; import '../../../data/models/contact.dart'; @@ -14,6 +15,7 @@ import '../../../g1/api.dart'; import '../../../g1/g1_helper.dart'; import '../../contacts_cache.dart'; import '../../logger.dart'; +import '../../nfc_helper.dart'; import '../../qr_manager.dart'; import '../../ui_helpers.dart'; import '../connectivity_widget_wrapper_wrapper.dart'; @@ -63,11 +65,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { if (cPlusResponse.statusCode != 404) { // Add cplus users final List<dynamic> hits = ((const JsonDecoder() - .convert(cPlusResponse.body) as Map<String, dynamic>)['hits'] - as Map<String, dynamic>)['hits'] as List<dynamic>; + .convert(cPlusResponse.body) as Map<String, dynamic>)['hits'] + as Map<String, dynamic>)['hits'] as List<dynamic>; for (final dynamic hit in hits) { final Contact c = - await contactFromResultSearch(hit as Map<String, dynamic>); + await contactFromResultSearch(hit as Map<String, dynamic>); logger('Contact retrieved in c+ search $c'); ContactsCache().addContact(c); setState(() { @@ -87,11 +89,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { // retrieve extra results with c+ profile for (final Contact wotC in wotResults) { final Contact cachedWotProfile = - await ContactsCache().getContact(wotC.pubKey); + await ContactsCache().getContact(wotC.pubKey); if (cachedWotProfile.name == null) { // Users without c+ profile final Contact cPlusProfile = - await getProfile(cachedWotProfile.pubKey, true); + await getProfile(cachedWotProfile.pubKey, true); ContactsCache().addContact(cPlusProfile); } } @@ -122,107 +124,144 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { @override Widget build(BuildContext context) { - final PaymentCubit paymentCubit = context.read<PaymentCubit>(); - return Scaffold( - appBar: AppBar( - title: Text(tr('search_user_title')), - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: Theme.of(context).colorScheme.inversePrimary, - actions: <Widget>[ - IconButton( - icon: const Icon(Icons.qr_code_scanner), - onPressed: () async { - final String? scannedKey = await QrManager.qrScan(context); - if (scannedKey is String && - scannedKey != null && - scannedKey != '-1') { - final PaymentState? pay = parseScannedUri(scannedKey); - if (pay != null) { - logger('Scanned $pay'); - _searchTerm = extractPublicKey(pay.contact!.pubKey); - await _search(); - } - logger('QR result length ${_results.length}'); - if (_results.length == 1 && pay != null) { - final Contact contact = _results[0]; - final double? currentAmount = paymentCubit.state.amount; - paymentCubit.selectUser(contact); - if (pay.amount != null) { - paymentCubit.selectKeyAmount(contact, pay.amount); - } else { - paymentCubit.selectKeyAmount(contact, currentAmount); - } - if (pay.comment != null) { - paymentCubit.setComment(pay.comment); + return FutureBuilder<NFCAvailability>( + future: FlutterNfcKit.nfcAvailability, + builder: + (BuildContext context, AsyncSnapshot<NFCAvailability> snapshot) { + final bool nft = hasNft(snapshot); + + final PaymentCubit paymentCubit = context.read<PaymentCubit>(); + return Scaffold( + appBar: AppBar( + title: Text(tr('search_user_title')), + backgroundColor: Theme + .of(context) + .colorScheme + .primary, + foregroundColor: Theme + .of(context) + .colorScheme + .inversePrimary, + actions: <Widget>[ + if (nft) IconButton( + icon: const Icon(Icons.nfc), + onPressed: () async { + final String? nfcUrl = await readNfcUrl(); + if (nfcUrl is String && nfcUrl != null && nfcUrl != '-1') { + await _onKeyScanned(nfcUrl, paymentCubit); } - } - if (!mounted) { - return; - } - Navigator.pop(context); - } - }), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ) - ], - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: <Widget>[ - TextField( - controller: _searchController, - decoration: InputDecoration( - filled: true, - //fillColor: Colors.white, - labelText: tr('search_user'), - suffixIcon: IconButton( - icon: const Icon(Icons.search), - onPressed: () => _searchTerm.length < 3 ? null : _search(), + }, ), + IconButton( + icon: const Icon(Icons.qr_code_scanner), + onPressed: () async { + final String? scannedKey = await QrManager.qrScan( + context); + if (scannedKey is String && + scannedKey != null && + scannedKey != '-1') { + await _onKeyScanned(scannedKey, paymentCubit); + if (!mounted) { + return; + } + Navigator.pop(context); + } + }), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ) + ], + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: <Widget>[ + TextField( + controller: _searchController, + decoration: InputDecoration( + filled: true, + //fillColor: Colors.white, + labelText: tr('search_user'), + suffixIcon: IconButton( + icon: const Icon(Icons.search), + onPressed: () => + _searchTerm.length < 3 + ? null + : _search(), + ), + ), + onChanged: (String value) { + _searchTerm = value; + }, + onSubmitted: (_) { + _search(); + }, + ), + if (_isLoading) + const LoadingBox(simple: false) + else + if (_searchTerm.isNotEmpty && _results.isEmpty && + _isLoading) + const NoElements(text: 'nothing_found') + else + Expanded( + child: ListView.builder( + itemCount: _results.length, + itemBuilder: (BuildContext context, int index) { + final Contact contact = _results[index]; + return FutureBuilder<Contact>( + future: ContactsCache().getContact( + contact.pubKey), + builder: (BuildContext context, + AsyncSnapshot<Contact> snapshot) { + Widget widget; + if (snapshot.hasData) { + widget = + _buildItem( + snapshot.data!, index, context); + } else if (snapshot.hasError) { + widget = + CustomErrorWidget(snapshot.error); + } else { + // Contact without wot + widget = + _buildItem(contact, index, context); + } + return widget; + }); + }), + ) + ], ), - onChanged: (String value) { - _searchTerm = value; - }, - onSubmitted: (_) { - _search(); - }, ), - if (_isLoading) - const LoadingBox(simple: false) - else if (_searchTerm.isNotEmpty && _results.isEmpty && _isLoading) - const NoElements(text: 'nothing_found') - else - Expanded( - child: ListView.builder( - itemCount: _results.length, - itemBuilder: (BuildContext context, int index) { - final Contact contact = _results[index]; - return FutureBuilder<Contact>( - future: ContactsCache().getContact(contact.pubKey), - builder: (BuildContext context, - AsyncSnapshot<Contact> snapshot) { - Widget widget; - if (snapshot.hasData) { - widget = - _buildItem(snapshot.data!, index, context); - } else if (snapshot.hasError) { - widget = CustomErrorWidget(snapshot.error); - } else { - // Contact without wot - widget = _buildItem(contact, index, context); - } - return widget; - }); - }), - ) - ], - ), - ), - ); + ); + }); + } + + Future<void> _onKeyScanned(String scannedKey, + PaymentCubit paymentCubit) async { + final PaymentState? pay = parseScannedUri(scannedKey); + if (pay != null) { + logger('Scanned $pay'); + _searchTerm = extractPublicKey(pay.contact!.pubKey); + await _search(); + } + logger('QR result length ${_results.length}'); + if (_results.length == 1 && pay != null) { + final Contact contact = _results[0]; + final double? currentAmount = paymentCubit.state.amount; + paymentCubit.selectUser(contact); + if (pay.amount != null) { + paymentCubit.selectKeyAmount(contact, pay.amount); + } else { + paymentCubit.selectKeyAmount(contact, currentAmount); + } + if (pay.comment != null) { + paymentCubit.setComment(pay.comment); + } + } } Widget _buildItem(Contact contact, int index, BuildContext context) { @@ -236,9 +275,9 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { }, trailing: BlocBuilder<ContactsCubit, ContactsState>( builder: (BuildContext context, ContactsState state) { - return ContactFavIcon( - contact: contact, contactsCubit: context.read<ContactsCubit>()); - }), + return ContactFavIcon( + contact: contact, contactsCubit: context.read<ContactsCubit>()); + }), ); } } diff --git a/lib/ui/widgets/second_screen/card_terminal_screen.dart b/lib/ui/widgets/second_screen/card_terminal_screen.dart index 5036d1b9..3b9669b2 100644 --- a/lib/ui/widgets/second_screen/card_terminal_screen.dart +++ b/lib/ui/widgets/second_screen/card_terminal_screen.dart @@ -1,9 +1,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '../../../g1/g1_helper.dart'; import '../../../shared_prefs.dart'; +import '../../nfc_helper.dart'; import '../../tutorial_keys.dart'; import '../../ui_helpers.dart'; import '../connectivity_widget_wrapper_wrapper.dart'; @@ -16,134 +18,149 @@ class CardTerminalScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final String duniterUri = getQrUri( - pubKey: SharedPreferencesHelper().getPubKey(), - locale: context.locale.toLanguageTag(), - amount: amount); - return Card( - key: receiveQrKey, - elevation: 8, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - child: Container( - width: double.infinity, - height: smallScreen(context) ? 200 : 252, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: <Color>[ - Colors.blueGrey, - Colors.white, - ], - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: <Widget>[ - Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: <Color>[ - Color(0xFF3B3B3B), - Color(0xFF232323), - ], - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: <Widget>[ - ConnectivityWidgetWrapperWrapper( - offlineWidget: - const CardTerminalStatus(online: false), - child: const CardTerminalStatus(online: true)), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Text( - amount, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'LCDMono', - color: Colors.white, - fontSize: amount.length < 5 - ? 28 - : amount.length < 10 - ? 20 - : amount.length < 15 - ? 14 - : 12, - shadows: <Shadow>[ - Shadow( - offset: const Offset(1, 1), - blurRadius: 3, - color: Colors.black.withOpacity(0.4), - ), - ], - //softWrap: true, // Agrega esta lÃnea para permitir que el texto se envuelva a la siguiente lÃnea - ), - ), - ), - ])), - Expanded( - child: Column(children: <Widget>[ - if (!amount.contains('+')) - Expanded( - child: GestureDetector( - onTap: () => copyPublicKeyToClipboard(context, duniterUri), - child: QrImage(data: duniterUri), - // size: smallScreen(context) ? 95.0 : 140.0) - )) - ])), - Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(8), - bottomRight: Radius.circular(8), - ), - gradient: LinearGradient( + return FutureBuilder<NFCAvailability>( + future: FlutterNfcKit.nfcAvailability, + builder: + (BuildContext context, AsyncSnapshot<NFCAvailability> snapshot) { + final String duniterUri = getQrUri( + pubKey: SharedPreferencesHelper().getPubKey(), + locale: context.locale.toLanguageTag(), + amount: amount); + + final bool nft = hasNft(snapshot); + if (nft) { + writeNfcUrl(duniterUri); + } + return Card( + key: receiveQrKey, + elevation: 8, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + child: Container( + width: double.infinity, + height: smallScreen(context) ? 200 : 252, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: <Color>[ - Color(0xFF232323), - Color(0xFF3B3B3B), + Colors.blueGrey, + Colors.white, ], ), ), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 6), - child: Text.rich( - TextSpan( - children: <TextSpan>[ - TextSpan( - text: amount.isNotEmpty - ? tr('show_qr_to_client_amount') - : tr('show_qr_to_client'), + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: <Color>[ + Color(0xFF3B3B3B), + Color(0xFF232323), + ], + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + ConnectivityWidgetWrapperWrapper( + offlineWidget: + const CardTerminalStatus(online: false), + child: const CardTerminalStatus(online: true)), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10), + child: Text( + amount, + textAlign: TextAlign.right, style: TextStyle( - fontFamily: 'Roboto Mono', - color: Colors.grey, - fontSize: smallScreen(context) ? 12 : 14, + fontFamily: 'LCDMono', + color: Colors.white, + fontSize: amount.length < 5 + ? 28 + : amount.length < 10 + ? 20 + : amount.length < 15 + ? 14 + : 12, + shadows: <Shadow>[ + Shadow( + offset: const Offset(1, 1), + blurRadius: 3, + color: Colors.black.withOpacity(0.4), + ), + ], + //softWrap: true, // Agrega esta lÃnea para permitir que el texto se envuelva a la siguiente lÃnea ), ), - ], - ), - )), - ) + ), + ])), + Expanded( + child: Column(children: <Widget>[ + if (!amount.contains('+')) + Expanded( + child: GestureDetector( + onTap: () => + copyPublicKeyToClipboard( + context, duniterUri), + child: QrImage(data: duniterUri), + // size: smallScreen(context) ? 95.0 : 140.0) + )) + ])), + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: <Color>[ + Color(0xFF232323), + Color(0xFF3B3B3B), + ], + ), + ), + child: Row( + children: <Widget>[ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 6), + child: Text.rich( + TextSpan( + children: <TextSpan>[ + TextSpan( + text: amount.isNotEmpty + ? tr('show_qr_to_client_amount') + : tr('show_qr_to_client'), + style: TextStyle( + fontFamily: 'Roboto Mono', + color: Colors.grey, + fontSize: + smallScreen(context) ? 12 : 14, + ), + ), + ], + ), + )), + ) + ], + ), + ), ], ), ), - ], - ), - ), - ); + ); + }); } } diff --git a/lib/ui/widgets/second_screen/card_terminal_status.dart b/lib/ui/widgets/second_screen/card_terminal_status.dart index 29f44e91..154f5811 100644 --- a/lib/ui/widgets/second_screen/card_terminal_status.dart +++ b/lib/ui/widgets/second_screen/card_terminal_status.dart @@ -1,5 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; + +import '../../nfc_helper.dart'; +import '../../ui_helpers.dart'; class CardTerminalStatus extends StatelessWidget { const CardTerminalStatus({super.key, required this.online}); @@ -8,18 +12,32 @@ class CardTerminalStatus extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: <Widget>[ - Tooltip( - message: online ? tr('online_terminal') : tr('offline_terminal'), - child: Icon( - Icons.payment, - color: online ? Colors.green : Colors.red, - )), - ], - ), - ); + return FutureBuilder<NFCAvailability>( + future: FlutterNfcKit.nfcAvailability, + builder: + (BuildContext context, AsyncSnapshot<NFCAvailability> snapshot) { + final bool nft = hasNft(snapshot); + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: <Widget>[ + Tooltip( + message: + online ? tr('online_terminal') : tr('offline_terminal'), + child: Icon( + Icons.payment, + color: online ? Colors.green : Colors.red, + )), + if (nft || inDevelopment) + Tooltip( + message: tr(''), + child: Icon( + Icons.nfc, + color: nft ? Colors.green : Colors.red, + )), + ], + ), + ); + }); } } diff --git a/pubspec.lock b/pubspec.lock index 9a9adac0..941c148a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -596,6 +596,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + flutter_nfc_kit: + dependency: "direct main" + description: + name: flutter_nfc_kit + sha256: "47f0f5cda9343489ae729c3eedaae3b69d63564af8fe9c78496d6715f6a2e899" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_slidable: dependency: "direct main" description: @@ -918,6 +926,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + ndef: + dependency: "direct main" + description: + name: ndef + sha256: e40ece11d1cac52cba2b7d0211228c1b5c278032cce3f5bf3e2eefe3762fde6b + url: "https://pub.dev" + source: hosted + version: "0.3.1" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a0694d43..5630d3ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.2.2 +version: 0.2.3-SNAPSHOT environment: sdk: ">=2.17.1 <3.0.0" @@ -90,6 +90,8 @@ dependencies: toggle_switch: ^2.1.0 fast_base58: ^0.2.1 crypto: ^3.0.3 + flutter_nfc_kit: ^3.3.1 + ndef: ^0.3.1 dev_dependencies: flutter_test: diff --git a/test/g1_test.dart b/test/g1_test.dart index c56b6b2f..ccedb7cd 100644 --- a/test/g1_test.dart +++ b/test/g1_test.dart @@ -58,7 +58,7 @@ void main() { const String baseKey = 'FRYyk57Pi456EJRu9vqVfSHLgmUfx4Qc3goS62a7dUSm'; final String publicKeyWithChecksum = getFullPubKey(baseKey); - final List<String> keys = [baseKey, publicKeyWithChecksum]; + final List<String> keys = <String>[baseKey, publicKeyWithChecksum]; for (final String publicKey in keys) { final String uriA = getQrUri(pubKey: publicKey, amount: '10'); -- GitLab