diff --git a/lib/data/models/transaction.g.dart b/lib/data/models/transaction.g.dart index d7217394fe006c17a686ea513500d58529ce599d..6e57d124f37613e2ed8fb70a32e4734dfbba84b5 100644 --- a/lib/data/models/transaction.g.dart +++ b/lib/data/models/transaction.g.dart @@ -250,7 +250,7 @@ Transaction _$TransactionFromJson(Map<String, dynamic> json) => Transaction( Map<String, dynamic> _$TransactionToJson(Transaction instance) => <String, dynamic>{ - 'type': _$TransactionTypeEnumMap[instance.type], + 'type': _$TransactionTypeEnumMap[instance.type]!, 'from': instance.from, 'to': instance.to, 'amount': instance.amount, @@ -262,8 +262,7 @@ Map<String, dynamic> _$TransactionToJson(Transaction instance) => 'fromNick': instance.fromNick, }; -const Map<TransactionType, String> _$TransactionTypeEnumMap = - <TransactionType, String>{ +const _$TransactionTypeEnumMap = { TransactionType.sending: 'sending', TransactionType.received: 'received', TransactionType.receiving: 'receiving', diff --git a/lib/g1/api.dart b/lib/g1/api.dart index 93b9f489a9e4125df4463264da5de2f581d0eb1b..7564af17b6890e314a3c046b153a62135b5380d7 100644 --- a/lib/g1/api.dart +++ b/lib/g1/api.dart @@ -13,6 +13,7 @@ import '../data/models/node_manager.dart'; import '../data/models/node_type.dart'; import '../shared_prefs.dart'; import '../ui/logger.dart'; +import '../ui/ui_helpers.dart'; import 'g1_helper.dart'; // Tx history @@ -47,6 +48,30 @@ Future<Response> searchCPlusUser(String searchTerm) async { return response; } +Future<Contact> getProfile(String pubKey) async { + try { + final Response cPlusResponse = await requestCPlusWithRetry( + '/user/profile/$pubKey', + retryWith404: false); + final Map<String, dynamic> result = + const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>; + if (result['found'] == false) { + return Contact(pubkey: pubKey); + } + + final String? nick = await gvaNick(pubKey); + final Map<String, dynamic> profile = + const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>; + + final Contact c = contactFromResultSearch(profile); + logger('Contact retrieved in search $c'); + return c.copyWith(nick: nick); + } catch (e) { + logger('Error in getProfile $e'); + return Contact(pubkey: pubKey); + } +} + /* http://doc.e-is.pro/cesium-plus-pod/REST_API.html#userprofile Not found sample: @@ -431,14 +456,15 @@ Future<String> pay( return output; } -String getGvaNode() { +String getGvaNode([bool useProxy = false]) { final List<Node> nodes = nodesWorkingList(NodeType.gva); if (nodes.isNotEmpty) { // reorder list to use others nodes.shuffle(); // Reference of working proxy 'https://g1demo.comunes.net/proxy/g1v1.p2p.legal/gva/'; - final String node = - 'https://g1demo.comunes.net/proxy/${nodes.first.url.replaceFirst('https://', '').replaceFirst('http://', '')}/'; + final String node = useProxy + ? 'https://g1demo.comunes.net/proxy/${nodes.first.url.replaceFirst('https://', '').replaceFirst('http://', '')}/' + : nodes.first.url; return node; } else { return 'Sorry: I cannot find a working node to send the transaction'; @@ -446,6 +472,21 @@ String getGvaNode() { } Future<Map<String, dynamic>?> gvaHistoryAndBalance(String pubKey) async { + return gvaFunctionWrapper<Map<String, dynamic>>( + pubKey, (Gva gva) => gva.history(pubKey)); +} + +Future<double?> gvaBalance(String pubKey) async { + return gvaFunctionWrapper<double>(pubKey, (Gva gva) => gva.balance(pubKey)); +} + +Future<String?> gvaNick(String pubKey) async { + return gvaFunctionWrapper<String>( + pubKey, (Gva gva) => gva.getUsername(pubKey)); +} + +Future<T?> gvaFunctionWrapper<T>( + String pubKey, Future<T?> Function(Gva) specificFunction) async { final List<Node> nodes = NodeManager().nodeList(NodeType.gva); if (nodes.isEmpty) { nodes.addAll(defaultGvaNodes); @@ -461,7 +502,7 @@ Future<Map<String, dynamic>?> gvaHistoryAndBalance(String pubKey) async { if (Uri.tryParse(output) != null) { final String node = output; final Gva gva = Gva(node: node); - final Map<String, dynamic>? result = await gva.history(pubKey); + final T? result = await specificFunction(gva); return result; } } catch (e) { diff --git a/lib/g1/g1_helper.dart b/lib/g1/g1_helper.dart index 9224d576e86edbfbbf3af923e2e947e62c82da3a..490c4306f10db51004b611aa31f942e019d13784 100644 --- a/lib/g1/g1_helper.dart +++ b/lib/g1/g1_helper.dart @@ -60,7 +60,7 @@ String? parseHost(String endpointUnParsed) { /* print(lastPart); print(path); print(nextToLast); */ - final String port = path == '' + String port = path == '' ? (RegExp(r'^[0-9]+$').hasMatch(lastPart) ? lastPart : '443') : RegExp(r'^[0-9]+$').hasMatch(nextToLast) ? nextToLast @@ -81,7 +81,12 @@ String? parseHost(String endpointUnParsed) { if (endpointUnParsed.endsWith('gva')) { path = '/gva'; } - final String endpoint = '$protocol://$host:$port$path'.trim(); + if (port == '443') { + port = ''; + } else { + port = ':$port'; + } + final String endpoint = '$protocol://$host$port$path'.trim(); return endpoint; } catch (e) { // Don't do this here or tests will fail diff --git a/lib/ui/contacts_cache.dart b/lib/ui/contacts_cache.dart new file mode 100644 index 0000000000000000000000000000000000000000..8568ff6349d5b25c7068ed16b02b6d42ba90d544 --- /dev/null +++ b/lib/ui/contacts_cache.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; +import 'dart:html'; + +import 'package:flutter/foundation.dart'; + +import '../data/models/contact.dart'; +import '../g1/api.dart'; +import 'logger.dart'; + +class ContactsCacheBasic { + factory ContactsCacheBasic() { + _instance ??= ContactsCacheBasic._internal(); + return _instance!; + } + + ContactsCacheBasic._internal(); + + static ContactsCacheBasic? _instance; + + final Map<String, Contact> _cache = <String, Contact>{}; + + Future<Contact> getContact(String pubKey) async { + final String cacheKey = 'avatar-$pubKey'; + + final Contact? cachedContact = _cache[cacheKey]; + if (cachedContact != null) { + return cachedContact; + } + + final Contact contact = await getProfile(pubKey); + + _cache[cacheKey] = contact; + + return contact; + } +} + +class ContactsCache { + factory ContactsCache() { + _instance ??= ContactsCache._internal(); + return _instance!; + } + + ContactsCache._internal(); + + static ContactsCache? _instance; + + Future<Contact> getContact(String pubKey) async { + final String cacheKey = 'avatar-$pubKey'; + const Duration duration = Duration(days: 3); + + try { + final String? cachedValue = window.localStorage[cacheKey]; + if (cachedValue != null) { + final Map<String, dynamic> decodedValue = + json.decode(cachedValue) as Map<String, dynamic>; + final DateTime timestamp = + DateTime.parse(decodedValue['timestamp'] as String); + + if (DateTime.now().isBefore(timestamp.add(duration))) { + final Contact contact = + Contact.fromJson(decodedValue['data'] as Map<String, dynamic>); + if (!kReleaseMode) { + logger('Returning cached contact $contact'); + } + return contact; + } + } + } catch (e) { + logger('Error while retrieving contact from cache: $e, $pubKey'); + } + + final Contact contact = await getProfile(pubKey); + + final String encodedValue = json.encode(<String, dynamic>{ + 'timestamp': DateTime.now().toIso8601String(), + 'data': contact.toJson(), + }); + window.localStorage[cacheKey] = encodedValue; + if (!kReleaseMode) { + logger('Returning non cached contact $contact'); + } + return contact; + } +} diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart index 3ae8976fdb128d4c81a8c308c064279189883895..c96580962f902b2176d555955b2b41602cdbf4c9 100644 --- a/lib/ui/ui_helpers.dart +++ b/lib/ui/ui_helpers.dart @@ -5,7 +5,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:timeago/timeago.dart' as timeago; +import '../data/models/contact.dart'; import '../data/models/transaction_type.dart'; +import '../g1/api.dart'; import '../shared_prefs.dart'; import 'widgets/first_screen/circular_icon.dart'; @@ -117,3 +119,31 @@ bool isOutgoing(TransactionType type) { bool isIncoming(TransactionType type) { return type == TransactionType.receiving || type == TransactionType.received; } + +Contact contactFromResultSearch(Map<String, dynamic> record) { + final Map<String, dynamic> source = record['_source'] as Map<String, dynamic>; + final Uint8List? avatarBase64 = _getAvatarFromResults(source); + return Contact( + pubkey: record['_id'] as String, + name: source['title'] as String, + avatar: avatarBase64); +} + +Contact contactFromUserProfile(Map<String, dynamic> source) { + final Uint8List? avatarBase64 = _getAvatarFromResults(source); + return Contact( + pubkey: source['issuer'] as String, + name: source['title'] as String, + avatar: avatarBase64); +} + +Uint8List? _getAvatarFromResults(Map<String, dynamic> source) { + Uint8List? avatarBase64; + if (source['avatar'] != null) { + final Map<String, dynamic> avatar = + source['avatar'] as Map<String, dynamic>; + avatarBase64 = imageFromBase64String( + 'data:${avatar['_content_type']};base64,${avatar['_content']}'); + } + return avatarBase64; +} 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 31adea51209efc4ee54ca9dbee3a30b25a0efb09..9f32790af25ac2c3a6b9f6a3a364e1a7f5bc69bb 100644 --- a/lib/ui/widgets/first_screen/pay_contact_search_page.dart +++ b/lib/ui/widgets/first_screen/pay_contact_search_page.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:http/http.dart'; @@ -54,7 +53,8 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { .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 = _contactFromResult(hit as Map<String, dynamic>); + final Contact c = + contactFromResultSearch(hit as Map<String, dynamic>); logger('Contact retrieved in search $c'); _results.add(c); } @@ -234,20 +234,4 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> { }), ); } - - Contact _contactFromResult(Map<String, dynamic> record) { - final Map<String, dynamic> source = - record['_source'] as Map<String, dynamic>; - Uint8List? avatarBase64; - if (source['avatar'] != null) { - final Map<String, dynamic> avatar = - source['avatar'] as Map<String, dynamic>; - avatarBase64 = imageFromBase64String( - 'data:${avatar['_content_type']};base64,${avatar['_content']}'); - } - return Contact( - pubkey: record['_id'] as String, - name: source['title'] as String, - avatar: avatarBase64); - } } diff --git a/lib/ui/widgets/fourth_screen/transaction_item.dart b/lib/ui/widgets/fourth_screen/transaction_item.dart index 81b6dcab10f92842aef2e90bfee6d05e8c0f1205..4d6f23cf561bf14d86f375dde44e97a9fcb8e48b 100644 --- a/lib/ui/widgets/fourth_screen/transaction_item.dart +++ b/lib/ui/widgets/fourth_screen/transaction_item.dart @@ -3,110 +3,116 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import '../../../data/models/contact.dart'; import '../../../data/models/contact_cubit.dart'; import '../../../data/models/transaction.dart'; import '../../../data/models/transaction_cubit.dart'; import '../../../data/models/transaction_type.dart'; import '../../../shared_prefs.dart'; +import '../../contacts_cache.dart'; import '../../ui_helpers.dart'; class TransactionListItem extends StatelessWidget { const TransactionListItem({ super.key, + required this.pubKey, required this.transaction, - required this.avatar, required this.index, }); + final String pubKey; final Transaction transaction; - final Widget avatar; final int index; @override - Widget build(BuildContext context) => - BlocBuilder<TransactionsCubit, TransactionsAndBalanceState>(builder: - (BuildContext context, - TransactionsAndBalanceState transBalanceState) { - IconData? icon; - Color? iconColor; - String statusText; - final String amountS = - '${transaction.amount < 0 ? "" : "+"}${formatKAmount(context, transaction.amount)}'; - statusText = tr('transaction_${transaction.type.name}'); - switch (transaction.type) { - case TransactionType.pending: - icon = Icons.timelapse; - iconColor = Colors.grey; - break; - case TransactionType.sending: - icon = Icons.flight_takeoff; - iconColor = Colors.grey; - break; - case TransactionType.receiving: - icon = Icons.flight_land; - iconColor = Colors.grey; - break; - case TransactionType.sent: - break; - case TransactionType.received: - break; - } - final String myPubKey = SharedPreferencesHelper().getPubKey(); - final ContactsCubit contactsCubit = context.read<ContactsCubit>(); - return Slidable( - // Specify a key if the Slidable is dismissible. - key: const ValueKey<int>(0), - // The end action pane is the one at the right or the bottom side. - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: <SlidableAction>[ - SlidableAction( - onPressed: (BuildContext c) { - /* _addContact(transactions, index, - myPubKey, contactsCubit); - ScaffoldMessenger.of(context) - .showSnackBar( - SnackBar( - content: - Text(tr('contact_added')), - ), - ); */ - }, - backgroundColor: Theme.of(context).primaryColor, - foregroundColor: Colors.white, - icon: Icons.contacts, - label: tr('add_contact'), - ), - ], - ), - child: ListTile( - leading: (icon != null) - ? Icon( - icon, - color: iconColor, - ) - : null, - tileColor: tileColor(index, context), - title: Row( - children: <Widget>[ - // if (avatar != null) avatar, - const SizedBox(width: 8.0), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: <Widget>[ - Text( - statusText, - style: const TextStyle( - fontSize: 12.0, - color: Colors.grey, + Widget build(BuildContext context) => BlocBuilder<TransactionsCubit, + TransactionsAndBalanceState>( + builder: (BuildContext context, + TransactionsAndBalanceState transBalanceState) => + FutureBuilder<Contact>( + future: _fetchContact(pubKey, transaction), + builder: (BuildContext context, AsyncSnapshot<Contact> snapshot) { + if (snapshot.hasData) { + IconData? icon; + Color? iconColor; + String statusText; + final String amountS = + '${transaction.amount < 0 ? "" : "+"}${formatKAmount(context, transaction.amount)}'; + statusText = tr('transaction_${transaction.type.name}'); + + switch (transaction.type) { + case TransactionType.pending: + icon = Icons.timelapse; + iconColor = Colors.grey; + break; + case TransactionType.sending: + icon = Icons.flight_takeoff; + iconColor = Colors.grey; + break; + case TransactionType.receiving: + icon = Icons.flight_land; + iconColor = Colors.grey; + break; + case TransactionType.sent: + break; + case TransactionType.received: + break; + } + final String myPubKey = SharedPreferencesHelper().getPubKey(); + final ContactsCubit contactsCubit = + context.read<ContactsCubit>(); + return Slidable( + // Specify a key if the Slidable is dismissible. + key: const ValueKey<int>(0), + // The end action pane is the one at the right or the bottom side. + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: <SlidableAction>[ + SlidableAction( + onPressed: (BuildContext c) { + if (snapshot.hasData) + contactsCubit.addContact(snapshot.data!); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(tr('contact_added')), + ), + ); + }, + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + icon: Icons.contacts, + label: tr('add_contact'), ), - ), - const SizedBox(height: 4.0), - Text.rich( - TextSpan( - children: <InlineSpan>[ - /* TextSpan( + ], + ), + child: ListTile( + leading: (icon != null) + ? Icon( + icon, + color: iconColor, + ) + : null, + tileColor: tileColor(index, context), + title: Row( + children: <Widget>[ + // if (avatar != null) avatar, + const SizedBox(width: 8.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: <Widget>[ + Text( + statusText, + style: const TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + const SizedBox(height: 4.0), + Text.rich( + TextSpan( + children: <InlineSpan>[ + /* TextSpan( text: isIncoming(transaction.type) ? 'Recibido de ' : 'Pago a ', @@ -115,66 +121,84 @@ class TransactionListItem extends StatelessWidget { // fontWeight: FontWeight.bold, ), ), */ - /* WidgetSpan( + /* WidgetSpan( child: avatar != null ? const SizedBox(width: 8.0) : const SizedBox.shrink(), ), */ - WidgetSpan( - child: Text( - tr('transaction_from_to', - namedArgs: <String, String>{ - 'from': humanizeFromToPubKey( - myPubKey, transaction.from), - 'to': humanizeFromToPubKey( - myPubKey, transaction.to) - }), - style: const TextStyle( - fontSize: 14.0, - // fontWeight: FontWeight.bold, + WidgetSpan( + child: Text( + tr('transaction_from_to', + namedArgs: <String, String>{ + 'from': humanizeFromToPubKey( + myPubKey, + transaction.from), + 'to': humanizeFromToPubKey( + myPubKey, transaction.to) + }), + style: const TextStyle( + fontSize: 14.0, + // fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), ), - ), + ], ), - ], - ), + ), + ], + ), + subtitle: Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 0, 10), + child: Text(transaction.comment, + style: const TextStyle( + fontStyle: FontStyle.italic, + color: Colors.grey, + )), + ), + trailing: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: <Widget>[ + Text( + amountS, + style: TextStyle( + // fontWeight: FontWeight.bold, + color: transaction.type == + TransactionType.received || + transaction.type == + TransactionType.receiving + ? Colors.blue + : Colors.red, + ), + ), + const SizedBox(height: 4.0), + Text( + humanizeTime( + transaction.time, context.locale.toString())!, + style: const TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ], ), - ], - ), - ), - ], - ), - subtitle: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 0, 10), - child: Text(transaction.comment, - style: const TextStyle( - fontStyle: FontStyle.italic, - color: Colors.grey, - )), - ), - trailing: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: <Widget>[ - Text( - amountS, - style: TextStyle( - // fontWeight: FontWeight.bold, - color: transaction.type == TransactionType.received || - transaction.type == TransactionType.receiving - ? Colors.blue - : Colors.red, - ), - ), - const SizedBox(height: 4.0), - Text( - humanizeTime(transaction.time, context.locale.toString())!, - style: const TextStyle( - fontSize: 12.0, - color: Colors.grey, - ), - ), - ], - ), - )); - }); + )); + } else if (snapshot.hasError) { + return Text('Error ${snapshot.error}'); + } else { + return const Text('Loading'); + } + })); + + Future<Contact> _fetchContact(String pubKey, Transaction transaction) async { + return Contact(pubkey: pubKey); + if (pubKey == transaction.from) { + return ContactsCache().getContact(transaction.to); + } else { + return ContactsCache().getContact(transaction.from); + } + } } diff --git a/lib/ui/widgets/fourth_screen/transaction_page.dart b/lib/ui/widgets/fourth_screen/transaction_page.dart index 523a2ec79d5294d0396edf723af6a8141158c308..1e9965c7390db1f058bc2db2e793b49cad21fb37 100644 --- a/lib/ui/widgets/fourth_screen/transaction_page.dart +++ b/lib/ui/widgets/fourth_screen/transaction_page.dart @@ -5,11 +5,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../data/models/contact.dart'; -import '../../../data/models/contact_cubit.dart'; import '../../../data/models/node_list_cubit.dart'; import '../../../data/models/transaction.dart'; import '../../../data/models/transaction_cubit.dart'; import '../../../shared_prefs.dart'; +import '../../contacts_cache.dart'; +import '../../logger.dart'; import '../../ui_helpers.dart'; import 'transaction_chart.dart'; import 'transaction_item.dart'; @@ -48,10 +49,7 @@ class _TransactionsAndBalanceWidgetState } Future<void> _scrollListener() async { - if (/* _transScrollController.position.pixels == - _transScrollController.position.maxScrollExtent || */ - _transScrollController.offset == 0) { - // await _refreshTransactions(); + if (_transScrollController.offset == 0) { _refreshIndicatorKey.currentState?.show(); } } @@ -66,11 +64,17 @@ class _TransactionsAndBalanceWidgetState @override Widget build(BuildContext context) { final String myPubKey = SharedPreferencesHelper().getPubKey(); + if (!kReleaseMode) { + ContactsCache() + .getContact('6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH'); + ContactsCache() + .getContact('A1Fc1VoCLKHyPYmXimYECSmjmsceqwRSZcTBXfgG9JaB'); + ContactsCache() + .getContact('6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH'); + ContactsCache().getContact(myPubKey).then((Contact c) => logger(c)); + } return BlocBuilder<TransactionsCubit, TransactionsAndBalanceState>(builder: (BuildContext context, TransactionsAndBalanceState transBalanceState) { - // Fetch transactions - // TODO(vjrj): Only fetch last transactions and used persisted ones - final ContactsCubit contactsCubit = context.read<ContactsCubit>(); final List<Transaction> transactions = transBalanceState.transactions; final double balance = transBalanceState.balance; return BackdropScaffold( @@ -172,9 +176,9 @@ class _TransactionsAndBalanceWidgetState // itemExtent: 100, itemBuilder: (BuildContext context, int index) { return TransactionListItem( + pubKey: myPubKey, index: index, transaction: transactions[index], - avatar: avatar(null), ); /* Slidable( @@ -251,16 +255,4 @@ class _TransactionsAndBalanceWidgetState )); }); } - - void _addContact(List<Transaction> transactions, int index, String myPubKey, - ContactsCubit contactsCubit) { - final Transaction tx = transactions[index]; - final String fromPubKey = tx.from; - final String toPubKey = tx.to; - final bool useFrom = fromPubKey != myPubKey; - contactsCubit.addContact(Contact( - pubkey: useFrom ? fromPubKey : toPubKey, - nick: useFrom ? tx.fromNick : tx.toNick, - avatar: useFrom ? tx.fromAvatar : tx.toAvatar)); - } } diff --git a/test/keys_test.dart b/test/keys_test.dart index cf31bd916771c3a0c8f9eaa229b12900b28469dc..6936a4046f63874695292b5a69532baa37bba3c6 100644 --- a/test/keys_test.dart +++ b/test/keys_test.dart @@ -24,21 +24,21 @@ void main() { test('parse different networks/peers BMAS', () { expect( parseHost('BMAS g1.texu.es 7443'), equals('https://g1.texu.es:7443')); - expect(parseHost('BMAS g1.duniter.org 443'), - equals('https://g1.duniter.org:443')); + expect( + parseHost('BMAS g1.duniter.org 443'), equals('https://g1.duniter.org')); expect(parseHost('BMAS g1.leprette.fr 443 /bma'), - equals('https://g1.leprette.fr:443/bma')); + equals('https://g1.leprette.fr/bma')); expect(parseHost('BMAS g1-vijitatman.es 212.227.41.252 443'), - equals('https://g1-vijitatman.es:443')); + equals('https://g1-vijitatman.es')); expect( parseHost( 'BMAS monnaie-libre.ortie.org/bma/ 192.168.1.35 2a01:cb0d:5c2:fa00:21e:68ff:feab:389a 443'), - equals('https://monnaie-libre.ortie.org:443/bma')); + equals('https://monnaie-libre.ortie.org/bma')); }); test('parse different networks/peers GVA S', () { expect(parseHost('GVA S duniter.master.aya.autissier.net 443 gva'), - equals('https://duniter.master.aya.autissier.net:443/gva')); + equals('https://duniter.master.aya.autissier.net/gva')); }); test('validate pub keys', () {