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

Merge branch 'master' into weblate

parents 7ab778a4 e8acbd2c
No related branches found
No related tags found
No related merge requests found
...@@ -119,5 +119,6 @@ ...@@ -119,5 +119,6 @@
"long_press_to_edit": "Tap and hold to edit", "long_press_to_edit": "Tap and hold to edit",
"form_contact_pub_key": "Public key", "form_contact_pub_key": "Public key",
"payment_error": "Payment error", "payment_error": "Payment error",
"payment_error_desc": "Oops! the payment failed. More details: {error}" "payment_error_desc": "Oops! the payment failed. More details: {error}",
"payment_error_retry": "Oops! the payment failed. Please retry in some minutes."
} }
...@@ -119,5 +119,6 @@ ...@@ -119,5 +119,6 @@
"form_contact_pub_key": "Clave pública", "form_contact_pub_key": "Clave pública",
"reloading_nodes": "Refrescando nodos del tipo {type}", "reloading_nodes": "Refrescando nodos del tipo {type}",
"payment_error": "Error en el pago", "payment_error": "Error en el pago",
"payment_error_desc": "¡Vaya! el pago falló. Más detalles: {error}" "payment_error_desc": "¡Vaya! el pago falló. Más detalles: {error}",
"payment_error_retry": "¡Vaya! el pago falló. Inténtalo de nuevo en unos minutos"
} }
...@@ -7,21 +7,55 @@ import 'node_type.dart'; ...@@ -7,21 +7,55 @@ import 'node_type.dart';
class NodeListCubit extends HydratedCubit<NodeListState> { class NodeListCubit extends HydratedCubit<NodeListState> {
NodeListCubit() : super(NodeListState()); NodeListCubit() : super(NodeListState());
void shuffle(NodeType type) { void shuffle(NodeType type, bool withPenalty) {
switch (type) { switch (type) {
case NodeType.duniter: case NodeType.duniter:
emit(state.copyWith(duniterNodes: shuffleFirstN(state.duniterNodes))); if (withPenalty) {
emit(state.copyWith(
duniterNodes: shuffleFirstNWithPenalty(state.duniterNodes)));
} else {
emit(state.copyWith(duniterNodes: shuffleFirstN(state.duniterNodes)));
}
break; break;
case NodeType.cesiumPlus: case NodeType.cesiumPlus:
emit(state.copyWith( if (withPenalty) {
cesiumPlusNodes: shuffleFirstN(state.cesiumPlusNodes))); emit(state.copyWith(
cesiumPlusNodes: shuffleFirstNWithPenalty(
state.cesiumPlusNodes)));
} else {
emit(state.copyWith(
cesiumPlusNodes: shuffleFirstN(state.cesiumPlusNodes)));
}
break; break;
case NodeType.gva: case NodeType.gva:
emit(state.copyWith(gvaNodes: shuffleFirstN(state.gvaNodes))); if (withPenalty) {
emit(state.copyWith(
gvaNodes: shuffleFirstNWithPenalty(state.gvaNodes)));
} else {
emit(state.copyWith(gvaNodes: shuffleFirstN(state.gvaNodes)));
}
break; break;
} }
} }
// shuffle first n nodes, keeping the first node last
List<Node> shuffleFirstNWithPenalty(List<Node> list, [int n = 5]) {
if (list.length <= n) {
final Node firstElement = list.removeAt(0);
list.shuffle();
list.add(firstElement);
} else {
final List<Node> subList = list.sublist(0, n);
final Node firstElement = subList.removeAt(0);
subList.shuffle();
subList.add(firstElement);
for (int i = 0; i < n; i++) {
list[i] = subList[i];
}
}
return list;
}
// shuffle fist n nodes // shuffle fist n nodes
List<Node> shuffleFirstN(List<Node> list, [int n = 5]) { List<Node> shuffleFirstN(List<Node> list, [int n = 5]) {
if (list.length <= n) { if (list.length <= n) {
......
...@@ -444,7 +444,10 @@ Future<http.Response> _requestWithRetry( ...@@ -444,7 +444,10 @@ Future<http.Response> _requestWithRetry(
} }
Future<String> pay( Future<String> pay(
{required String to, required double amount, String? comment}) async { {required String to,
required double amount,
String? comment,
bool? useMempool}) async {
final String output = getGvaNode(); final String output = getGvaNode();
if (Uri.tryParse(output) != null) { if (Uri.tryParse(output) != null) {
final String node = output; final String node = output;
...@@ -459,6 +462,7 @@ Future<String> pay( ...@@ -459,6 +462,7 @@ Future<String> pay(
amount: amount, amount: amount,
comment: comment ?? '', comment: comment ?? '',
cesiumSeed: wallet.seed, cesiumSeed: wallet.seed,
useMempool: useMempool ?? false,
raiseException: true); raiseException: true);
logger('GVA replied with "$response"'); logger('GVA replied with "$response"');
return response; return response;
......
...@@ -77,45 +77,11 @@ class _PayFormState extends State<PayForm> { ...@@ -77,45 +77,11 @@ class _PayFormState extends State<PayForm> {
!_weHaveBalance(context, state.amount!)) !_weHaveBalance(context, state.amount!))
? null ? null
: () async { : () async {
// We disable the number, anyway try {
context.read<PaymentCubit>().sending(); await payWithRetry(context, state, false);
final String contactPubKey = state.contact!.pubKey; } on RetryException {
final bool? confirmed = await _confirmSend( // Here the transactions can be lost, so we must implement some manual retry use
context, await payWithRetry(context, state, true);
state.amount.toString(),
humanizePubKey(contactPubKey));
if (!mounted) {
return;
}
if (confirmed == null || !confirmed) {
context.read<PaymentCubit>().sentFailed();
} else {
final String response = await pay(
to: contactPubKey,
comment: state.comment,
amount: state.amount!);
if (!mounted) {
// Cannot show a tooltip if the widget is not now visible
return;
}
if (response == 'success') {
context.read<PaymentCubit>().sent();
showTooltip(context, tr('payment_successful'),
tr('payment_successful_desc'));
} else {
showTooltip(
context,
tr('payment_error'),
tr('payment_error_desc',
namedArgs: <String, String>{
// We try to translate the error, like "insufficient balance"
'error': tr(response)
}));
context.read<PaymentCubit>().sentFailed();
// Shuffle the nodes so we can retry with other
context.read<NodeListCubit>().shuffle(NodeType.gva);
// FIXME - retry manually with other node
}
} }
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
...@@ -181,6 +147,60 @@ class _PayFormState extends State<PayForm> { ...@@ -181,6 +147,60 @@ class _PayFormState extends State<PayForm> {
}, },
); );
} }
Future<void> payWithRetry(
BuildContext context, PaymentState state, bool useMempool) async {
logger('Trying to pay state with useMempool: $useMempool');
// We disable the number, anyway
context.read<PaymentCubit>().sending();
final String contactPubKey = state.contact!.pubKey;
final bool? confirmed = await _confirmSend(
context, state.amount.toString(), humanizePubKey(contactPubKey));
if (!mounted) {
return;
}
if (confirmed == null || !confirmed) {
context.read<PaymentCubit>().sentFailed();
} else {
final String response = await pay(
to: contactPubKey, comment: state.comment, amount: state.amount!);
if (!mounted) {
// Cannot show a tooltip if the widget is not now visible
return;
}
if (response == 'success') {
context.read<PaymentCubit>().sent();
showTooltip(
context, tr('payment_successful'), tr('payment_successful_desc'));
} else {
/* this retry didn't work
if (!useMempool) {
throw RetryException();
} */
final bool failedWithBalance = response == 'insufficient balance' &&
_weHaveBalance(context, state.amount!);
showPayError(
context,
failedWithBalance
? tr('payment_error_retry')
: tr('payment_error_desc', namedArgs: <String, String>{
// We try to translate the error, like "insufficient balance"
'error': tr(response)
}));
}
}
}
void showPayError(BuildContext context, String desc) {
showTooltip(context, tr('payment_error'), desc);
context.read<PaymentCubit>().sentFailed();
// Shuffle the nodes so we can retry with other
context.read<NodeListCubit>().shuffle(NodeType.gva, true);
}
}
class RetryException implements Exception {
RetryException();
} }
class NoNewLineTextInputFormatter extends TextInputFormatter { class NoNewLineTextInputFormatter extends TextInputFormatter {
......
...@@ -26,7 +26,7 @@ class NodeInfoCard extends StatelessWidget { ...@@ -26,7 +26,7 @@ class NodeInfoCard extends StatelessWidget {
: state.gvaNodes; : state.gvaNodes;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
context.read<NodeListCubit>().shuffle(type); context.read<NodeListCubit>().shuffle(type, true);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(tr('long_press_to_refresh')), content: Text(tr('long_press_to_refresh')),
......
...@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev ...@@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.0.12-SNAPSHOT version: 0.0.13
environment: environment:
sdk: ">=2.17.1 <3.0.0" sdk: ">=2.17.1 <3.0.0"
...@@ -169,5 +169,5 @@ sentry: ...@@ -169,5 +169,5 @@ sentry:
log_level: info # possible values: trace, debug, info, warn, error log_level: info # possible values: trace, debug, info, warn, error
# release: default: name@version from pubspec # release: default: name@version from pubspec
#web_build_path: ... #web_build_path: ...
commits: auto # commits: auto
#ignore_missing: true ignore_missing: true
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