Skip to content
Snippets Groups Projects
Commit 331bcb37 authored by vjrj's avatar vjrj
Browse files

G1 searchfix. NFC tags read/write

parent 397d0173
No related branches found
No related tags found
No related merge requests found
...@@ -93,7 +93,8 @@ class ContactsCubit extends HydratedCubit<ContactsState> { ...@@ -93,7 +93,8 @@ class ContactsCubit extends HydratedCubit<ContactsState> {
emit(state.copyWith(filteredContacts: contacts)); emit(state.copyWith(filteredContacts: contacts));
} }
List<Contact> search(String query) { List<Contact> search(String initialQuery) {
final String query = normalizeQuery(initialQuery);
if (query.isEmpty) { if (query.isEmpty) {
return state.contacts; return state.contacts;
} }
......
...@@ -50,7 +50,8 @@ Future<Response> getPeers() async { ...@@ -50,7 +50,8 @@ Future<Response> getPeers() async {
} }
} }
Future<Response> searchCPlusUser(String searchTerm) async { Future<Response> searchCPlusUser(String initialSearchTerm) async {
final String searchTerm = normalizeQuery(initialSearchTerm);
final String searchTermLower = searchTerm.toLowerCase(); final String searchTermLower = searchTerm.toLowerCase();
final String searchTermCapitalized = final String searchTermCapitalized =
searchTermLower[0].toUpperCase() + searchTermLower.substring(1); searchTermLower[0].toUpperCase() + searchTermLower.substring(1);
...@@ -105,11 +106,8 @@ Not found sample: ...@@ -105,11 +106,8 @@ Not found sample:
"found": false "found": false
} }
*/ */
Future<List<Contact>> searchWot(String searchTermRaw) async { Future<List<Contact>> searchWot(String initialSearchTerm) async {
// If pubkey, remove checksum final String searchTerm = normalizeQuery(initialSearchTerm);
final String searchTerm = validateKey(searchTermRaw)
? extractPublicKey(searchTermRaw)
: searchTermRaw;
final Response response = await requestDuniterWithRetry( final Response response = await requestDuniterWithRetry(
'/wot/lookup/$searchTerm', '/wot/lookup/$searchTerm',
retryWith404: false); retryWith404: false);
......
...@@ -257,3 +257,12 @@ extension Ex on double { ...@@ -257,3 +257,12 @@ extension Ex on double {
} }
String extractPublicKey(String key) => key.split(':')[0]; String extractPublicKey(String key) => key.split(':')[0];
String normalizeQuery(String initialQuery) {
String query = initialQuery;
if (validateKey(query)) {
// Is a pubKey
query = extractPublicKey(initialQuery);
}
return query;
}
...@@ -4,6 +4,7 @@ import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; ...@@ -4,6 +4,7 @@ import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';
import 'package:ndef/ndef.dart' as ndef; import 'package:ndef/ndef.dart' as ndef;
import 'package:ndef/record.dart'; import 'package:ndef/record.dart';
import 'package:ndef/record/uri.dart'; import 'package:ndef/record/uri.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'logger.dart'; import 'logger.dart';
...@@ -23,6 +24,8 @@ Future<void> writeNfcUrl(String url) async { ...@@ -23,6 +24,8 @@ Future<void> writeNfcUrl(String url) async {
if ((ndefAvailable == null || ndefWritable == null) && if ((ndefAvailable == null || ndefWritable == null) &&
(ndefAvailable != null && !ndefAvailable) || (ndefAvailable != null && !ndefAvailable) ||
(ndefWritable != null && !ndefWritable)) { (ndefWritable != null && !ndefWritable)) {
await Sentry.captureMessage(
'Tag does not have NDEF capability or is not writable');
logger('Tag does not have NDEF capability or is not writable'); logger('Tag does not have NDEF capability or is not writable');
return; return;
} }
...@@ -35,6 +38,7 @@ Future<void> writeNfcUrl(String url) async { ...@@ -35,6 +38,7 @@ Future<void> writeNfcUrl(String url) async {
// iOS only: show an alert message // iOS only: show an alert message
await FlutterNfcKit.finish(iosAlertMessage: 'Success'); await FlutterNfcKit.finish(iosAlertMessage: 'Success');
} catch (e) { } catch (e) {
// await Sentry.captureMessage('Error while writing to tag: $e');
logger('Error while writing to tag: $e'); logger('Error while writing to tag: $e');
await FlutterNfcKit.finish(iosErrorMessage: 'Failed'); await FlutterNfcKit.finish(iosErrorMessage: 'Failed');
} }
...@@ -48,9 +52,12 @@ Future<String?> readNfcUrl() async { ...@@ -48,9 +52,12 @@ Future<String?> readNfcUrl() async {
final List<NDEFRecord> records = await FlutterNfcKit.readNDEFRecords(); final List<NDEFRecord> records = await FlutterNfcKit.readNDEFRecords();
for (final NDEFRecord record in records) { for (final NDEFRecord record in records) {
if (record is UriRecord) { if (record is UriRecord) {
return record.uri.toString(); // record.uri.toString() contains the URL but does not respect upper/lower case
return record.iriString;
} }
} }
} else {
// await Sentry.captureMessage('NFT no available');
} }
return null; return null;
} }
...@@ -25,7 +25,9 @@ import '../third_screen/contacts_page.dart'; ...@@ -25,7 +25,9 @@ import '../third_screen/contacts_page.dart';
import 'contact_fav_icon.dart'; import 'contact_fav_icon.dart';
class PayContactSearchPage extends StatefulWidget { class PayContactSearchPage extends StatefulWidget {
const PayContactSearchPage({super.key}); const PayContactSearchPage({super.key, this.uri});
final String? uri;
@override @override
State<PayContactSearchPage> createState() => _PayContactSearchPageState(); State<PayContactSearchPage> createState() => _PayContactSearchPageState();
...@@ -65,11 +67,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -65,11 +67,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
if (cPlusResponse.statusCode != 404) { if (cPlusResponse.statusCode != 404) {
// Add cplus users // Add cplus users
final List<dynamic> hits = ((const JsonDecoder() final List<dynamic> hits = ((const JsonDecoder()
.convert(cPlusResponse.body) as Map<String, dynamic>)['hits'] .convert(cPlusResponse.body) as Map<String, dynamic>)['hits']
as Map<String, dynamic>)['hits'] as List<dynamic>; as Map<String, dynamic>)['hits'] as List<dynamic>;
for (final dynamic hit in hits) { for (final dynamic hit in hits) {
final Contact c = final Contact c =
await contactFromResultSearch(hit as Map<String, dynamic>); await contactFromResultSearch(hit as Map<String, dynamic>);
logger('Contact retrieved in c+ search $c'); logger('Contact retrieved in c+ search $c');
ContactsCache().addContact(c); ContactsCache().addContact(c);
setState(() { setState(() {
...@@ -89,11 +91,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -89,11 +91,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
// retrieve extra results with c+ profile // retrieve extra results with c+ profile
for (final Contact wotC in wotResults) { for (final Contact wotC in wotResults) {
final Contact cachedWotProfile = final Contact cachedWotProfile =
await ContactsCache().getContact(wotC.pubKey); await ContactsCache().getContact(wotC.pubKey);
if (cachedWotProfile.name == null) { if (cachedWotProfile.name == null) {
// Users without c+ profile // Users without c+ profile
final Contact cPlusProfile = final Contact cPlusProfile =
await getProfile(cachedWotProfile.pubKey, true); await getProfile(cachedWotProfile.pubKey, true);
ContactsCache().addContact(cPlusProfile); ContactsCache().addContact(cPlusProfile);
} }
} }
...@@ -134,29 +136,30 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -134,29 +136,30 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(tr('search_user_title')), title: Text(tr('search_user_title')),
backgroundColor: Theme backgroundColor: Theme.of(context).colorScheme.primary,
.of(context) foregroundColor: Theme.of(context).colorScheme.inversePrimary,
.colorScheme
.primary,
foregroundColor: Theme
.of(context)
.colorScheme
.inversePrimary,
actions: <Widget>[ actions: <Widget>[
if (nft) IconButton( if (nft)
icon: const Icon(Icons.nfc), IconButton(
onPressed: () async { icon: const Icon(Icons.nfc),
final String? nfcUrl = await readNfcUrl(); onPressed: () async {
if (nfcUrl is String && nfcUrl != null && nfcUrl != '-1') { final String? nfcUrl = await readNfcUrl();
await _onKeyScanned(nfcUrl, paymentCubit); if (nfcUrl is String &&
} nfcUrl != null &&
}, nfcUrl != '-1') {
), await _onKeyScanned(nfcUrl, paymentCubit);
if (!mounted) {
return;
}
Navigator.pop(context);
}
},
),
IconButton( IconButton(
icon: const Icon(Icons.qr_code_scanner), icon: const Icon(Icons.qr_code_scanner),
onPressed: () async { onPressed: () async {
final String? scannedKey = await QrManager.qrScan( final String? scannedKey =
context); await QrManager.qrScan(context);
if (scannedKey is String && if (scannedKey is String &&
scannedKey != null && scannedKey != null &&
scannedKey != '-1') { scannedKey != '-1') {
...@@ -187,9 +190,7 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -187,9 +190,7 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
onPressed: () => onPressed: () =>
_searchTerm.length < 3 _searchTerm.length < 3 ? null : _search(),
? null
: _search(),
), ),
), ),
onChanged: (String value) { onChanged: (String value) {
...@@ -201,38 +202,36 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -201,38 +202,36 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
), ),
if (_isLoading) if (_isLoading)
const LoadingBox(simple: false) const LoadingBox(simple: false)
else if (_searchTerm.isNotEmpty &&
_results.isEmpty &&
_isLoading)
const NoElements(text: 'nothing_found')
else else
if (_searchTerm.isNotEmpty && _results.isEmpty && Expanded(
_isLoading) child: ListView.builder(
const NoElements(text: 'nothing_found') itemCount: _results.length,
else itemBuilder: (BuildContext context, int index) {
Expanded( final Contact contact = _results[index];
child: ListView.builder( return FutureBuilder<Contact>(
itemCount: _results.length, future:
itemBuilder: (BuildContext context, int index) { ContactsCache().getContact(contact.pubKey),
final Contact contact = _results[index]; builder: (BuildContext context,
return FutureBuilder<Contact>( AsyncSnapshot<Contact> snapshot) {
future: ContactsCache().getContact( Widget widget;
contact.pubKey), if (snapshot.hasData) {
builder: (BuildContext context, widget = _buildItem(
AsyncSnapshot<Contact> snapshot) { snapshot.data!, index, context);
Widget widget; } else if (snapshot.hasError) {
if (snapshot.hasData) { widget = CustomErrorWidget(snapshot.error);
widget = } else {
_buildItem( // Contact without wot
snapshot.data!, index, context); widget =
} else if (snapshot.hasError) { _buildItem(contact, index, context);
widget = }
CustomErrorWidget(snapshot.error); return widget;
} else { });
// Contact without wot }),
widget = )
_buildItem(contact, index, context);
}
return widget;
});
}),
)
], ],
), ),
), ),
...@@ -240,8 +239,8 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -240,8 +239,8 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
}); });
} }
Future<void> _onKeyScanned(String scannedKey, Future<void> _onKeyScanned(
PaymentCubit paymentCubit) async { String scannedKey, PaymentCubit paymentCubit) async {
final PaymentState? pay = parseScannedUri(scannedKey); final PaymentState? pay = parseScannedUri(scannedKey);
if (pay != null) { if (pay != null) {
logger('Scanned $pay'); logger('Scanned $pay');
...@@ -264,6 +263,22 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -264,6 +263,22 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
} }
} }
@override
void initState() {
super.initState();
_handleUri(widget.uri);
}
Future<void> _handleUri(String? uri) async {
if (uri != null) {
final PaymentCubit paymentCubit = context.read<PaymentCubit>();
await _onKeyScanned(uri, paymentCubit);
if (mounted) {
Navigator.pop(context);
}
}
}
Widget _buildItem(Contact contact, int index, BuildContext context) { Widget _buildItem(Contact contact, int index, BuildContext context) {
return contactToListItem( return contactToListItem(
contact, contact,
...@@ -275,9 +290,9 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { ...@@ -275,9 +290,9 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
}, },
trailing: BlocBuilder<ContactsCubit, ContactsState>( trailing: BlocBuilder<ContactsCubit, ContactsState>(
builder: (BuildContext context, ContactsState state) { builder: (BuildContext context, ContactsState state) {
return ContactFavIcon( return ContactFavIcon(
contact: contact, contactsCubit: context.read<ContactsCubit>()); contact: contact, contactsCubit: context.read<ContactsCubit>());
}), }),
); );
} }
} }
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import '../../../g1/g1_helper.dart'; import '../../../g1/g1_helper.dart';
import '../../../shared_prefs.dart'; import '../../../shared_prefs.dart';
import '../../nfc_helper.dart';
import '../../tutorial_keys.dart'; import '../../tutorial_keys.dart';
import '../../ui_helpers.dart'; import '../../ui_helpers.dart';
import '../connectivity_widget_wrapper_wrapper.dart'; import '../connectivity_widget_wrapper_wrapper.dart';
...@@ -18,149 +16,139 @@ class CardTerminalScreen extends StatelessWidget { ...@@ -18,149 +16,139 @@ class CardTerminalScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder<NFCAvailability>( final String duniterUri = getQrUri(
future: FlutterNfcKit.nfcAvailability, pubKey: SharedPreferencesHelper().getPubKey(),
builder: locale: context.locale.toLanguageTag(),
(BuildContext context, AsyncSnapshot<NFCAvailability> snapshot) { amount: amount);
final String duniterUri = getQrUri( final String duniterUriNoSha = getQrUri(
pubKey: SharedPreferencesHelper().getPubKey(), pubKey: extractPublicKey(SharedPreferencesHelper().getPubKey()),
locale: context.locale.toLanguageTag(), locale: context.locale.toLanguageTag(),
amount: amount); amount: amount);
return Card(
final bool nft = hasNft(snapshot); key: receiveQrKey,
if (nft) { elevation: 8,
writeNfcUrl(duniterUri); shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
} child: Container(
return Card( width: double.infinity,
key: receiveQrKey, height: smallScreen(context) ? 200 : 252,
elevation: 8, decoration: BoxDecoration(
shape: borderRadius: BorderRadius.circular(8),
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), gradient: const LinearGradient(
child: Container( begin: Alignment.topLeft,
width: double.infinity, end: Alignment.bottomRight,
height: smallScreen(context) ? 200 : 252, colors: <Color>[
decoration: BoxDecoration( Colors.blueGrey,
borderRadius: BorderRadius.circular(8), Colors.white,
gradient: const LinearGradient( ],
),
),
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: CardTerminalStatus(
online: false, uri: duniterUriNoSha),
child: CardTerminalStatus(
online: true, uri: duniterUriNoSha)),
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(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: <Color>[ colors: <Color>[
Colors.blueGrey, Color(0xFF232323),
Colors.white, Color(0xFF3B3B3B),
], ],
), ),
), ),
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
Container( Expanded(
decoration: const BoxDecoration( child: Padding(
borderRadius: BorderRadius.only( padding: const EdgeInsets.symmetric(
topLeft: Radius.circular(8), horizontal: 10, vertical: 6),
topRight: Radius.circular(8), child: Text.rich(
), TextSpan(
gradient: LinearGradient( children: <TextSpan>[
begin: Alignment.topLeft, TextSpan(
end: Alignment.bottomRight, text: amount.isNotEmpty
colors: <Color>[ ? tr('show_qr_to_client_amount')
Color(0xFF3B3B3B), : tr('show_qr_to_client'),
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( style: TextStyle(
fontFamily: 'LCDMono', fontFamily: 'Roboto Mono',
color: Colors.white, color: Colors.grey,
fontSize: amount.length < 5 fontSize: smallScreen(context) ? 12 : 14,
? 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,
),
),
],
),
)),
)
],
),
),
], ],
), ),
), ),
); ],
}); ),
),
);
} }
} }
...@@ -6,9 +6,11 @@ import '../../nfc_helper.dart'; ...@@ -6,9 +6,11 @@ import '../../nfc_helper.dart';
import '../../ui_helpers.dart'; import '../../ui_helpers.dart';
class CardTerminalStatus extends StatelessWidget { class CardTerminalStatus extends StatelessWidget {
const CardTerminalStatus({super.key, required this.online}); const CardTerminalStatus(
{super.key, required this.online, required this.uri});
final bool online; final bool online;
final String uri;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -30,11 +32,17 @@ class CardTerminalStatus extends StatelessWidget { ...@@ -30,11 +32,17 @@ class CardTerminalStatus extends StatelessWidget {
)), )),
if (nft || inDevelopment) if (nft || inDevelopment)
Tooltip( Tooltip(
message: tr(''), message: tr('pay_with_nfc_tooltip'),
child: Icon( child: GestureDetector(
Icons.nfc, onTap: () {
color: nft ? Colors.green : Colors.red, ScaffoldMessenger.of(context).showSnackBar(SnackBar(
)), content: Text(tr('pay_with_nfc_tooltip'))));
writeNfcUrl(uri);
},
child: Icon(
Icons.nfc,
color: nft ? Colors.green : Colors.red,
))),
], ],
), ),
); );
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment